/usr/local/CPAN/Class-HPLOO/Class/HPLOO/Base.pm


#############################################################################
## Name:        Base.pm
## Purpose:     Base class for HPLOO classes.
## Author:      Graciliano M. P.
## Modified by:
## Created:     30/10/2004
## RCS-ID:      
## Copyright:   (c) 2004 Graciliano M. P.
## Licence:     This program is free software; you can redistribute it and/or
##              modify it under the same terms as Perl itself
#############################################################################

package Class::HPLOO::Base ;

use 5.006 ;
use strict qw(vars) ;

use vars qw($VERSION $SYNTAX @ISA) ;

$VERSION = '0.17';

############
# EXPORTER #
############

require Exporter;
@ISA = qw(Exporter UNIVERSAL) ;

our @EXPORT = qw(SUPER new GET_CLASS_HPLOO_HASH CLASS_HPLOO_TIE_KEYS ATTRS CLASS_HPLOO_ATTR CLASS_HPLOO_ATTR_TYPE) ;

our @EXPORT_OK = @EXPORT ;
  
########################
# GET_CLASS_HPLOO_HASH #
########################

sub GET_CLASS_HPLOO_HASH {
  my $pack = ref($_[0]) || ($_[1] ? $_[1] : $_[0]) ;
  return \%{$pack . '::CLASS_HPLOO'} ;
}

#########
# SUPER #
#########

sub SUPER {
  my ($prev_pack , undef , undef , $sub0) = caller(1) ;
  $prev_pack = undef if $prev_pack eq 'Class::HPLOO::Base' ;
  
  my ($pack,$sub) = ( $sub0 =~ /^(?:(.*?)::|)(\w+)$/ );

  my $sub_is_new_hploo = $sub0 =~ /^(.*?(?:::)?$sub)\::$sub$/ ? 1 : undef ;
  
  ##print "SUPER[$sub_is_new_hploo]>> @_ >> $pack,$sub >> $prev_pack\n" ;

  unshift(@_ , $prev_pack) if ( $sub_is_new_hploo && $prev_pack && ((!ref($_[0]) && $_[0] ne $prev_pack && !UNIVERSAL::isa($_[0] , $prev_pack)) || (ref($_[0]) && !UNIVERSAL::isa($_[0] , $prev_pack)) ) ) ;

  if ( defined @{"$pack\::ISA"} ) {
    my $isa_sub = ISA_FIND_NEW($pack, ($sub_is_new_hploo?'new':$sub) ,1) ;

    my ($sub_name) = ( $isa_sub =~ /(\w+)$/gi );
    if ( $sub0 ne $isa_sub && !ref($_[0]) && $isa_sub =~ /^(.*?(?:::)?$sub_name)\::$sub_name$/ ) {
      @_ = ( bless({},$_[0]) , @_[1..$#_] ) ;
    }
    
    if ( $sub0 eq $isa_sub && UNIVERSAL::isa($_[0] , $pack) ) {
      my @isa = Class::HPLOO::Base::FIND_SUPER_WALK( ref($_[0]) , $pack ) ;
      my $pk = $isa[-1] ;
      if ( $sub_is_new_hploo ) {
        if ( UNIVERSAL::isa($pk , 'Class::HPLOO::Base') ) {
          ($sub) = ( $pk =~ /(\w+)$/gi );        
        }
        else { $sub = 'new' ;}
      }
      
      my $isa_sub = $pk->can($sub) ;
      ##print "SUPER WALK>> $pk , $sub >> $isa_sub\n" ;
      return &$isa_sub( ARGS_WRAPPER(@_) ) if $isa_sub ;
    }
    
    ##print "SUPER ISA>> $isa_sub >> $pack >> ". join(',',@{"$pack\::ISA"}) ."\n" ;
    return &$isa_sub(@_) if $isa_sub && defined &$isa_sub && $sub0 ne $isa_sub ;
  }
  
  $sub = $sub_is_new_hploo ? 'new' : $sub ;
  ##print "SUPER CALL>> $pack $sub >> @_\n" ;

  die("Can't find SUPER method for $sub0!") if "$pack\::$sub" eq $sub0 ;
  
  return &{"$pack\::$sub"}(@_) ;
}

###################
# FIND_SUPER_WALK #
###################

sub FIND_SUPER_WALK {
  my $class_main = shift ;
  my $class_end = shift ;
  my $only_stak = shift ;
  
  my (@stack) ;
  my $stack = $only_stak || {} ;
  
  ##print "FIND>> $class_main , $class_end\n" ;
  
  my $found ;
  foreach my $isa_i ( @{"$class_main\::ISA"} ) {
    next if $$stack{$isa_i}++ ;
    $found = 1 if $isa_i eq $class_end ;
    push(@stack , $isa_i , FIND_SUPER_WALK($isa_i , $class_end , $stack) );
  }
  
  return ($found ? @stack : ()) if $only_stak ;
  return @stack ;
}

################
# ISA_FIND_NEW #
################

sub ISA_FIND_NEW {
  my $pack = shift ;
  my $sub = shift ;
  my $look_deep = shift ;
  my $count = shift ;
  return if $count > 100 ;
  
  ##print "ISA_FIND_NEW>> $pack >> $sub\n" ;
    
  my ($sub_name) ;
  if ( UNIVERSAL::isa($pack , 'Class::HPLOO::Base') ) {
    ($sub_name) = $sub eq 'new' ? ( $pack =~ /(\w+)$/ ) : ($sub) ;
  }
  else { $sub_name = $sub ;}
  
  my $isa_sub = "$pack\::$sub_name" ;
  
  if ( $look_deep || !defined &$isa_sub ) {
    foreach my $isa_i ( @{"$pack\::ISA"} ) {
      next if $isa_i eq $pack || $isa_i eq 'Class::HPLOO::Base' ;
      last if $isa_i eq 'UNIVERSAL' ;
      $isa_sub = ISA_FIND_NEW($isa_i , $sub , 0 , $count+1) ;
      last if $isa_sub ;
    }
  }
  
  $isa_sub = undef if !defined &$isa_sub ; 
  
  ##print "%%> $pack >> $isa_sub\n" ;
  return $isa_sub ;
}

##################
# NEW_CALL_BEGIN #
##################

sub new_call_BEGIN {
  my $class = shift ; $class = ref($class) if ref($class) ;
  my $this = $class ;
    
  my @isas = \@{"$class\::ISA"} ;
  
  foreach my $isas_i ( @isas ) {
    foreach my $ISA_i ( @$isas_i ) {
      if ( defined @{"$ISA_i\::ISA"} && @{"$ISA_i\::ISA"} > 2 ) {
        push(@isas , \@{"$ISA_i\::ISA"}) ;
      }
      last if $ISA_i eq 'Class::HPLOO::Base' ;
      my $ret ;
      my ($sub) = ( $ISA_i =~ /(\w+)$/ );
      $sub = "$ISA_i\::$sub\_BEGIN" ;
      $ret = &$sub($this,@_) if defined &$sub ;
      $this = $ret if $ret && UNIVERSAL::isa($ret,$class) ;
    }
  }
  
  return $this ;
}

################
# NEW_CALL_END #
################

sub new_call_END {
  my $class = shift ; $class = ref($class) if ref($class) ;
  
  my @isas = \@{"$class\::ISA"} ;
  
  foreach my $isas_i ( @isas ) {
    foreach my $ISA_i ( @$isas_i ) {
      if ( defined @{"$ISA_i\::ISA"} && @{"$ISA_i\::ISA"} > 2 ) {
        push(@isas , \@{"$ISA_i\::ISA"}) ;
      }
      last if $ISA_i eq 'Class::HPLOO::Base' ;
      my ($sub) = ( $ISA_i =~ /(\w+)$/ );
      $sub = "$ISA_i\::$sub\_END" ;
      &$sub(@_) if defined &$sub ;
    }
  }
  
  return ;
}

#######
# NEW #
#######

my $NEW_REF  = \&new ;

sub new {
  my $class = shift ;
  my $this = ref($class) ? $class : undef ;
  
  my $class_bless = $class ;
  ($class,$class_bless,$this) = @$class if ref($class) eq 'ARRAY' ;

  $class = ref($class) if ref($class) ;
  $class_bless = ref($class_bless) if ref($class_bless) ;
  
  my ($class_end) = ( $class =~ /(\w+)$/ );
  
  ##print "BASE NEW>> $class,$class_bless,$this >> $class_end >> @_ >> [". join(" ", @{"$class\::ISA"}) ."]\n" ;
  
  if ( !defined &{"$class\::$class_end"} && @{"$class\::ISA"} > 1 ) {
    foreach my $ISA_i ( @{"$class\::ISA"} ) {
      last if $ISA_i eq 'Class::HPLOO::Base' ;
      my $sub = "$ISA_i\::new" ;
      if ( defined &$sub ) {
        my $new_ref = \&$sub ;
        return &$sub([$ISA_i,$class,$this],@_) if $new_ref == $NEW_REF || (defined &{"$ISA_i\::__CLASS__"} && defined &{"$ISA_i\::SUPER"} && defined &{"$ISA_i\::new_call_END"} ) ;
        return &$sub($class,@_) ;
      }
    }
  }

  $this ||= $class ;
  $this = new_call_BEGIN( $this , @_) ;
  $this = bless({} , $class_bless) if !ref($this) || !UNIVERSAL::isa($this,$class_bless) ;
  
  no warnings ;
  
  my $undef = \'' ;
  sub UNDEF {$undef} ;
  
  my $CLASS_HPLOO = GET_CLASS_HPLOO_HASH($this) ;
  
  if ( $$CLASS_HPLOO{ATTR} ) { CLASS_HPLOO_TIE_KEYS($this) }
  
  my $ret_this = defined &{"$class\::$class_end"} ? $this->$class_end(@_) : undef ;
  
  if ( ref($ret_this) && UNIVERSAL::isa($ret_this,$class_bless) ) {
    $this = $ret_this ;
    if ( $$CLASS_HPLOO{ATTR} && UNIVERSAL::isa($this,'HASH') ) { CLASS_HPLOO_TIE_KEYS($this) }
  }
  elsif ( $ret_this == $undef ) { $this = undef }
  
  new_call_END($class,$this,@_) ;

  return $this ;
}

########################
# CLASS_HPLOO_TIE_KEYS #
########################
    
sub CLASS_HPLOO_TIE_KEYS {
  my $this = shift ;
  my $CLASS_HPLOO = GET_CLASS_HPLOO_HASH($this) ;
  if ( $$CLASS_HPLOO{ATTR} ) {
    foreach my $Key ( keys %{$$CLASS_HPLOO{ATTR}} ) {
      tie( $this->{$Key} => 'Class::HPLOO::Base::HPLOO_TIESCALAR' , $this , $Key , $$CLASS_HPLOO{ATTR}{$Key}{tp} , $$CLASS_HPLOO{ATTR}{$Key}{pr} , \$this->{CLASS_HPLOO_ATTR}{$Key} , \$this->{CLASS_HPLOO_CHANGED} , ref($this) ) if !exists $this->{$Key} ;
    }
  }
}

#########
# ATTRS #
#########

sub ATTRS { return @{[@{ GET_CLASS_HPLOO_HASH($_[0] , scalar caller)->{ATTR_ORDER} }]} } ;

####################
# CLASS_HPLOO_ATTR #
####################

sub CLASS_HPLOO_ATTR {
  my $class = caller ;

  my @attrs = split(/\s*,\s*/ , $_[0]) ;
  
  my $CLASS_HPLOO = GET_CLASS_HPLOO_HASH( undef , $class ) ;

  foreach my $attrs_i ( @attrs ) {
    $attrs_i =~ s/^\s+//s ;
    $attrs_i =~ s/\s+$//s ;
    my ($name) = ( $attrs_i =~ /(\w+)$/gi ) ;
    my ($type) = ( $attrs_i =~ /^((?:\w+\s+)*?&?\w+|(?:\w+\s+)*?\w+(?:(?:::|\.)\w+)*)\s+\w+$/gi ) ;
    
    my $type0 = $type ;
    $type0 =~ s/\s+/ /gs ;
    
    $type = lc($type) ;
    $type =~ s/(?:^|\s*)bool$/boolean/gs ;
    $type =~ s/(?:^|\s*)int$/integer/gs ;
    $type =~ s/(?:^|\s*)float$/floating/gs ;
    $type =~ s/(?:^|\s*)str$/string/gs ;
    $type =~ s/(?:^|\s*)sub$/sub_$name/gs ;
    $type =~ s/\s//gs ;
    
    $type = 'any' if $type !~ /^(?:(?:ref)|(?:ref)?(?:array|hash)(?:boolean|integer|floating|string|sub_\w+|any|&\w+)|(?:ref)?(?:array|hash)|(?:array|hash)?(?:boolean|integer|floating|string|sub_\w+|any|&\w+))$/ ;

    if ( $type eq 'any' && $type0 =~ /^((?:ref\s*)?(?:array|hash) )?(\w+(?:(?:::|\.)\w+)*)$/ ) {
      my ($tp1 , $tp2) = ($1 , $2) ;
      $tp1 =~ s/\s+//gs ;
      $tp2 = 'UNIVERSAL' if $tp2 =~ /^(?:obj|object)$/i ;
      $tp2 =~ s/\.+/::/gs ;
      $type = "$tp1$tp2" ;      
    }
    
    my $parse_ref = $type =~ /^(?:array|hash)/ ? 1 : 0 ;
    
    push(@{ $$CLASS_HPLOO{ATTR_ORDER} } , $name) if !$$CLASS_HPLOO{ATTR}{$name} ;
    
    $$CLASS_HPLOO{ATTR}{$name}{tp} = $type ;
    $$CLASS_HPLOO{ATTR}{$name}{pr} = $parse_ref ;

    my $return ;

    if ( $type =~ /^sub_(\w+)$/ ) {
      my $sub = $1 ;
      $return = qq~
                return (&$sub(\$this,\@_))[0] if defined &$sub ;
                return undef ;
            ~ ;
    }
    else {
       $return = $parse_ref ? qq~
                                      ref(\$this->{CLASS_HPLOO_ATTR}{$name}) eq 'ARRAY' ? \@{\$this->{CLASS_HPLOO_ATTR}{$name}} :
                                      ref(\$this->{CLASS_HPLOO_ATTR}{$name}) eq 'HASH' ? \%{\$this->{CLASS_HPLOO_ATTR}{$name}} :
                                      \$this->{CLASS_HPLOO_ATTR}{$name}
                                  ~ :
                 "\$this->{CLASS_HPLOO_ATTR}{$name}" ;
    }

    eval(qq~
        package $class ;
        sub set_$name {
            my \$this = shift ;
            if ( !defined \$this->{$name} ) {
                tie( \$this->{$name} => 'Class::HPLOO::Base::HPLOO_TIESCALAR' , \$this , '$name' , '$type' , $parse_ref , \\\$this->{CLASS_HPLOO_ATTR}{$name} , \\\$this->{CLASS_HPLOO_CHANGED} , ref(\$this) ) ;
            }
            
            \$this->{CLASS_HPLOO_CHANGED}{$name} = 1 ;
            \$this->{CLASS_HPLOO_ATTR}{$name} = CLASS_HPLOO_ATTR_TYPE( ref(\$this) , '$type',\@_) ;
        }
        ~) if !defined &{"$class\::set_$name"} ;
    
    eval(qq~
        package $class ;
        sub get_$name {
            my \$this = shift ;
            $return ;
        }
        ~) if !defined &{"$class\::get_$name"} ;
  }
}

#########################
# CLASS_HPLOO_ATTR_TYPE #
#########################

sub CLASS_HPLOO_ATTR_TYPE {
  my $class = shift ;
  my $type = shift ;
  
  if ($type eq 'any') { return $_[0] }
  elsif ($type eq 'string') {
    return "$_[0]" ;
  }
  elsif ($type eq 'boolean') {
    return if $_[0] =~ /^(?:false|null|undef)$/i ;
    return 1 if $_[0] ;
    return ;
  }
  elsif ($type eq 'integer') {
    my $val = $_[0] ;
    my ($sig) = ( $val =~ /^(-)/ );
    $val =~ s/[^0-9]//gs ;
    $val = "$sig$val" ;
    return $val ;
  }
  elsif ($type eq 'floating') {
    my $val = $_[0] ;
    $val =~ s/[\s_]+//gs ;
    if ( $val !~ /^\d+\.\d+$/ ) {
      ($val) = ( $val =~ /(\d+)/ ) ;
      $val .= '.0' ;
    }
    return $val ;
  }
  elsif ($type =~ /^sub_(\w+)$/) {
    my $sub = $1 ;
    return (&$sub(@_))[0] if defined &$sub ;
  }
  elsif ($type =~ /^&(\w+)$/) {
    my $sub = $1 ;
    return (&$sub(@_))[0] if defined &$sub ;
  }
  elsif ($type eq 'ref') {
    my $val = $_[0] ;
    return $val if ref($val) ;
  }
  elsif ($type eq 'array') {
    my @val = @_ ;
    return \@val ;
  }
  elsif ($type eq 'hash') {
    my %val = @_ ;
    return \%val ;
  }
  elsif ($type eq 'refarray') {
    my $val = $_[0] ;
    return $val if ref($val) eq 'ARRAY' ;
  }
  elsif ($type eq 'refhash') {
    my $val = $_[0] ;
    return $val if ref($val) eq 'HASH' ;
  }
  elsif ($type =~ /^array(&?[\w:]+)/ ) {
    my $tp = $1 ;
    my @val = @_ ;
    my $accept_undef = $tp =~ /^(?:any|string|boolean|integer|floating|sub_\w+|&\w+)$/ ? 1 : undef ;
    if ( $accept_undef ) {
      return [map { CLASS_HPLOO_ATTR_TYPE($class , $tp , $_) } @val] ;
    }
    else {
      return [map { CLASS_HPLOO_ATTR_TYPE($class , $tp , $_) || () } @val] ;
    }
  }
  elsif ($type =~ /^hash(&?[\w:]+)/ ) {
    my $tp = $1 ;
    my %val = @_ ;
    foreach my $Key ( keys %val ) {
      $val{$Key} = CLASS_HPLOO_ATTR_TYPE($class , $tp , $val{$Key}) ;
    }
    return \%val ;
  }
  elsif ($type =~ /^refarray(&?[\w:]+)/ ) {
    my $tp = $1 ;
    return undef if ref($_[0]) ne 'ARRAY' ;
    my $ref = CLASS_HPLOO_ATTR_TYPE($class , "array$tp" , @{$_[0]}) ;
    @{$_[0]} = @{$ref} ;
    return $_[0] ;
  }
  elsif ($type =~ /^refhash(&?[\w:]+)/ ) {
    my $tp = $1 ;
    return undef if ref($_[0]) ne 'HASH' ;
    my $ref = CLASS_HPLOO_ATTR_TYPE($class , "hash$tp" , %{$_[0]}) ;
    %{$_[0]} = %{$ref} ;
    return $_[0] ;
  }
  elsif ($type =~ /^\w+(?:::\w+)*$/ ) {
    return( UNIVERSAL::isa($_[0] , $type) ? $_[0] : undef ) ;
  }
  return undef ;
}

#######################################
# CLASS::HPLOO::BASE::HPLOO_TIESCALAR #
#######################################

package Class::HPLOO::Base::HPLOO_TIESCALAR ;

sub TIESCALAR {
  shift ;
  my $obj = shift ;
  my $this = bless( { nm => $_[0] , tp => $_[1] , pr => $_[2] , rf => $_[3] , rfcg => $_[4] , pk => ($_[5] || scalar caller) } , __PACKAGE__ ) ;
        
  if ( $this->{tp} =~ /^sub_(\w+)$/ ) {
    my $CLASS_HPLOO = Class::HPLOO::Base::GET_CLASS_HPLOO_HASH( undef , $this->{pk} ) ;
  
    if ( !ref($$CLASS_HPLOO{OBJ_TBL}) ) {
      eval { require Hash::NoRef } ;
      if ( !$@ ) {
        $$CLASS_HPLOO{OBJ_TBL} = {} ;
        tie( %{$$CLASS_HPLOO{OBJ_TBL}} , 'Hash::NoRef') ;
      }
      else { $@ = undef }
    }

    $$CLASS_HPLOO{OBJ_TBL}{ ++$$CLASS_HPLOO{OBJ_TBL}{x} } = $obj ;
    $this->{oid} = $$CLASS_HPLOO{OBJ_TBL}{x} ;
  }

  return $this ;
}

sub STORE {
  my $this = shift ;
  my $ref = $this->{rf} ;
  my $ref_changed = $this->{rfcg} ;

  if ( $ref_changed ) {
    if ( ref $$ref_changed ne 'HASH' ) { $$ref_changed = {} }
    $$ref_changed->{$this->{nm}} = 1 ;
  }

  if ( $this->{pr} ) {
    my $tp = $this->{tp} =~ /^ref/ ? $this->{tp} : 'ref' . $this->{tp} ;
    $$ref = &{"$this->{pk}::CLASS_HPLOO_ATTR_TYPE"}($this->{pk} , $tp , @_) ;
  }
  else {
    $$ref = &{"$this->{pk}::CLASS_HPLOO_ATTR_TYPE"}($this->{pk} , $this->{tp} , @_) ;  
  }
}

sub FETCH {
  my $this = shift ;
  my $ref = $this->{rf} ;
  
  if ( $this->{tp} =~ /^sub_(\w+)$/ ) {
    my $CLASS_HPLOO = Class::HPLOO::Base::GET_CLASS_HPLOO_HASH( undef , $this->{pk} ) ;
    my $sub = $this->{pk} . '::' . $1 ;
    my $obj = $$CLASS_HPLOO{OBJ_TBL}{ $this->{oid} } ;
    return (&$sub($obj,@_))[0] if defined &$sub ;
  }
  else {
    if ( $this->{tp} =~ /^(?:ref)?(?:array|hash)/ ) {
      my $ref_changed = $this->{rfcg} ;
      if ( $ref_changed ) {
        if ( ref $$ref_changed ne 'HASH' ) { $$ref_changed = {} }
        $$ref_changed->{$this->{nm}} = 1 ;
      }
    }
    return $$ref ;
  }
}

sub UNTIE {}
sub DESTROY {}


#######
# END #
#######

1;