# # Copyright (C) 1998,1999 Ken MacLeod # Data::Grove::Parent is free software; you can redistribute it and/or # modify it under the same terms as Perl itself. # # $Id: Parent.pm,v 1.2 1999/12/22 21:15:00 kmacleod Exp $ # ### ### WARNING ### ### ### This code has a bug in it that renders it useless. In the FETCH ### routines, the new object created should have a reference to the ### the tied object that has $self as the underlying value. As of ### this version, I don't know of a way to get to the tied object. ### # Search for places marked `VALIDATE' to see where validation hooks # may be added in the future. use strict; #-------------------------------------------------------------------------- # Data::Grove::Parent #-------------------------------------------------------------------------- package Data::Grove::Parent; use UNIVERSAL; use Carp; use vars qw{ $VERSION }; # will be substituted by make-rel script $VERSION = "0.07"; sub new { my $type = shift; my $raw = shift; my $parent = shift; if (UNIVERSAL::isa($raw, 'Data::Grove::Parent')) { return $raw; } my @properties = ( Raw => $raw ); if (defined $parent) { push @properties, Parent => $parent; } my $dummy = bless {}, ref($raw); tie %$dummy, $type, @properties; return $dummy; } sub TIEHASH { my $type = shift; return bless { @_ }, $type; } sub STORE { my $self = shift; my $key = shift; my $value = shift; if (exists $self->{$key}) { $self->{$key} = $value; } else { # VALIDATE if (UNIVERSAL::isa($value, 'Data::Grove::Parent')) { $value = $value->{Raw}; } elsif (UNIVERSAL::isa($value, 'Data::Grove::ParentList')) { $value = $value->[0]; } $self->{Raw}{$key} = $value; } } sub FETCH { my $self = shift; my $key = shift; if (exists $self->{$key}) { return $self->{$key}; } else { my $value = $self->{Raw}{$key}; if (ref($value) eq 'ARRAY') { $value = Data::Grove::ParentList->new($value, $self); } return $value; } } sub FIRSTKEY { my $self = shift; my $raw = $self->{Raw}; $self->{'__each_in_raw'} = 1; my $a = scalar keys %$raw; each %$raw; } sub NEXTKEY { my $self = shift; my $raw = $self->{Raw}; my ($key, $value); if ($self->{'__each_in_raw'}) { if (($key, $value) = each %$raw) { return $key; } delete $self->{'__each_in_raw'}; my $a = scalar keys %$self; } return each %$self; } sub EXISTS { my $self = shift; my $key = shift; return (exists $self->{Raw}{$key}) || (exists $self->{$key}); } sub DELETE { my $self = shift; my $key = shift; if (exists $self->{$key}) { croak "can't delete \`Parent' or \`Raw' properties\n" if ($key eq 'Parent' || $key eq 'Raw'); delete $self->{$key}; } else { delete $self->{'Raw'}{$key}; } } sub CLEAR { my $self = shift; %{ $self->{Raw} } = (); } #-------------------------------------------------------------------------- # Data::Grove::ParentList #-------------------------------------------------------------------------- package Data::Grove::ParentList; use UNIVERSAL; sub new { my $type = shift; my $raw = shift; my $parent = shift; if (UNIVERSAL::isa($raw, 'Data::Grove::ParentList')) { return $raw; } my $dummy = []; tie @$dummy, $type, $raw, $parent; return $dummy; } sub TIEARRAY { my $type = shift; return bless [ @_ ], $type; } sub FETCHSIZE { scalar @{$_[0][0]}; } sub STORESIZE { $#{$_[0][0]} = $_[1]-1; } sub STORE { my $self = shift; my $index = shift; my $value = shift; # VALIDATE if (UNIVERSAL::isa($value, 'Data::Grove::Parent')) { $value = $value->{Raw}; } elsif (UNIVERSAL::isa($value, 'Data::Grove::ParentList')) { $value = $value->[0]; } $self->[0][$index] = $value; } sub FETCH { my $self = shift; my $index = shift; my $value = $self->[0][$index]; if (defined $value) { if (ref($value)) { return Data::Grove::Parent->new($value, $self->[1]); } else { return Data::Grove::Parent->new({ Data => $value }, $self->[1]); } } return $value; } sub CLEAR { @{$_[0][0]} = (); } sub POP { pop(@{$_[0][0]}); } sub PUSH { my $o = shift; foreach my $value (@_) { # VALIDATE if (UNIVERSAL::isa($value, 'Data::Grove::Parent')) { $value = $value->{Raw}; } elsif (UNIVERSAL::isa($value, 'Data::Grove::ParentList')) { $value = $value->[0]; } } push(@{$o->[0]},@_); } sub SHIFT { shift(@{$_[0][0]}); } sub UNSHIFT { my $o = shift; foreach my $value (@_) { # VALIDATE if (UNIVERSAL::isa($value, 'Data::Grove::Parent')) { $value = $value->{Raw}; } elsif (UNIVERSAL::isa($value, 'Data::Grove::ParentList')) { $value = $value->[0]; } } unshift(@{$o->[0]},@_); } sub SPLICE { my $ob = shift; my $sz = $ob->FETCHSIZE; my $off = @_ ? shift : 0; $off += $sz if $off < 0; my $len = @_ ? shift : $sz-$off; foreach my $value (@_) { # VALIDATE if (UNIVERSAL::isa($value, 'Data::Grove::Parent')) { $value = $value->{Raw}; } elsif (UNIVERSAL::isa($value, 'Data::Grove::ParentList')) { $value = $value->[0]; } } return splice(@{$ob->[0]},$off,$len,@_); } #-------------------------------------------------------------------------- # Data::Grove #-------------------------------------------------------------------------- package Data::Grove; sub root { my $self = shift; return $self if !defined $self->{Parent}; return $self->{Parent}->root(@_); } sub rootpath { my $self = shift; if (defined $self->{Parent}) { return ($self->{Parent}->rootpath, $self); } else { return ($self); } } sub add_magic { my $self = shift; my $parent = shift; return Data::Grove::Parent->new($self, $parent); } 1; __END__ =head1 NAME Data::Grove::Parent - provide parent properties to Data::Grove objects =head1 SYNOPSIS use Data::Grove::Parent; $root = $object->root; $rootpath = $object->rootpath; $tied = $object->add_magic([ $parent ]); $node = Data::Grove::Parent->new($hash [, $parent]); $node_list = Data::Grove::ParentList->new($array [, $parent]); =head1 DESCRIPTION Data::Grove::Parent is an extension to Data::Grove that adds `C' and `C' properties to Data::Grove objects and methods for returning the root node of a grove, a list of nodes between and including the root node and the current node, and a method that creates parented nodes. Data::Grove::Parent works by creating a Perl ``tied'' object that contains a parent reference (`C') and a reference to the original Data::Grove object (`C'). Tying-magic is used so that every time you reference the Data::Grove::Parent object it actually references the underlying raw object. When you retrieve a list or a property of the Raw object, Data::Grove::Parent automatically adds magic to the returned list or node. This means you only call `add_magic()' once to create the first Data::Grove::Parent object and then use the grove objects like you normally would. The most obvious use of this is so you don't have to call a `C' method when you want to release a grove or part of a grove; since Data::Grove and Data::Grove::Parent objects have no cyclic references, Perl can garbage collect them normally. A secondary use is to allow you to reuse grove or property set fragments in multiple trees. WARNING: Data::Grove currently does not protect you from creating your B cyclic references! This could lead to infinite loops if you don't take care to avoid them. =head1 METHODS =over 4 =item $object->root() =item $object->rootpath() `C' returns the root node if `C<$object>' is a `C' object. `C' returns an array of all the nodes between and including the root node and `C<$object>'. =item $tied = $object->add_magic([ $parent ]) `C' returns a C object with `C<$object>' as it's `C' object. If `C<$parent>' is given, that becomes the tied object's parent object. =back =head1 AUTHOR Ken MacLeod, ken@bitsko.slc.ut.us =head1 SEE ALSO perl(1), Data::Grove(3) =cut