AcePerl
view release on metacpan or search on metacpan
Ace/Object.pm view on Meta::CPAN
use Carp qw(:DEFAULT cluck);
# $Id: Object.pm,v 1.60 2005/04/13 14:26:08 lstein Exp $
use overload
'""' => 'name',
'==' => 'eq',
'!=' => 'ne',
'fallback' => 'TRUE';
use vars qw($AUTOLOAD $DEFAULT_WIDTH %MO $VERSION);
use Ace 1.50 qw(:DEFAULT rearrange);
# if set to 1, will conflate tags in XML output
use constant XML_COLLAPSE_TAGS => 1;
use constant XML_SUPPRESS_CONTENT=>1;
use constant XML_SUPPRESS_CLASS=>1;
use constant XML_SUPPRESS_VALUE=>0;
use constant XML_SUPPRESS_TIMESTAMPS=>0;
require AutoLoader;
$DEFAULT_WIDTH=25; # column width for pretty-printing
$VERSION = '1.66';
# Pseudonyms and deprecated methods.
*isClass = \&isObject;
*pick = \&fetch;
*get = \&search;
*add = \&add_row;
sub AUTOLOAD {
my($pack,$func_name) = $AUTOLOAD=~/(.+)::([^:]+)$/;
my $self = $_[0];
# This section works with Autoloader
my $presumed_tag = $func_name =~ /^[A-Z]/ && $self->isObject; # initial_cap
if ($presumed_tag) {
croak "Invalid object tag \"$func_name\""
if $self->db && $self->model && !$self->model->valid_tag($func_name);
shift(); # get rid of the object
my $no_dereference;
if (defined($_[0])) {
if ($_[0] eq '@') {
$no_dereference++;
shift();
} elsif ($_[0] =~ /^\d+$/) {
$no_dereference++;
}
}
$self = $self->fetch if !$no_dereference &&
!$self->isRoot && $self->db; # dereference, if need be
croak "Null object tag \"$func_name\"" unless $self;
return $self->search($func_name,@_) if wantarray;
my ($obj) = @_ ? $self->search($func_name,@_) : $self->search($func_name,1);
# these nasty heuristics simulate aql semantics.
# undefined return
return unless defined $obj;
# don't dereference object if '@' symbol specified
return $obj if $no_dereference;
# don't dereference if an offset was explicitly specified
return $obj if defined($_[0]) && $_[0] =~ /\d+/;
# otherwise dereference if the current thing is an object or we are at a tag
# and the thing to the right is an object.
return $obj->fetch if $obj->isObject && !$obj->isRoot; # always dereference objects
# otherwise return the thing itself
return $obj;
} elsif ($func_name =~ /^[A-Z]/ && $self->isTag) { # follow tag
return $self->search($func_name);
} else {
$AutoLoader::AUTOLOAD = __PACKAGE__ . "::$func_name";
goto &AutoLoader::AUTOLOAD;
}
}
sub DESTROY {
my $self = shift;
return unless defined $self->{class}; # avoid working with temp objects from a search()
return if caller() =~ /^(Cache\:\:|DB)/; # prevent recursion in FileCache code
my $db = $self->db or return;
return if $self->{'.nocache'};
return unless $self->isRoot;
if ($self->_dirty) {
warn "Destroy for ",overload::StrVal($self)," ",$self->class,':',$self->name if Ace->debug;
$self->_dirty(0);
$db->file_cache_store($self);
}
# remove our in-memory cache
# shouldn't be necessary with weakref
# $db->memory_cache_delete($self);
}
###################### object constructor #################
# IMPORTANT: The _clone subroutine will copy all instance variables that
# do NOT begin with a dot (.). If you do not want an instance variable
# shared with cloned copies, proceed them with a dot!!!
#
sub new {
my $pack = shift;
my($class,$name,$db,$isRoot) = rearrange([qw/CLASS NAME/,[qw/DATABASE DB/],'ROOT'],@_);
$pack = ref($pack) if ref($pack);
my $self = bless { 'name' => $name,
'class' => $class
},$pack;
$self->db($db) if $self->isObject;
$self->{'.root'}++ if defined $isRoot && $isRoot;
# $self->_dirty(1) if $isRoot;
return $self
}
Ace/Object.pm view on Meta::CPAN
push (@r,$right->col);
}
}
return @r;
}
#### Search for a tag, and return the column ####
#### Uses a breadth-first search (cols then rows) ####
sub search {
my $self = shift;
my $tag = shift unless $_[0]=~/^-/;
my ($subtag,$pos,$filled) = rearrange(['SUBTAG','POS',['FILL','FILLED']],@_);
my $lctag = lc $tag;
# With caching, the old way of following ends up cloning the object
# -- which we don't want. So more-or-less emulate the earlier
# behavior with an explicit get and fetch
# return $self->follow(-tag=>$tag,-filled=>$filled) if $filled;
if ($filled) {
my @node = $self->search($tag) or return; # watch out for recursion!
my @obj = map {$_->fetch} @node;
foreach (@obj) {$_->right if defined $_}; # trigger a fill
return wantarray ? @obj : $obj[0];
}
TRY: {
# look in our tag cache first
if (exists $self->{'.PATHS'}) {
# we've already cached the desired tree
last TRY if exists $self->{'.PATHS'}{$lctag};
# not cached, so try parents of tag
my $m = $self->model;
my @parents = $m->path($lctag) if $m;
my $tree;
foreach (@parents) {
($tree = $self->{'.PATHS'}{lc $_}) && last;
}
if ($tree) {
$self->{'.PATHS'}{$lctag} = $tree->search($tag);
$self->_dirty(1);
last TRY;
}
}
# If the object hasn't been filled already, then we can use
# acedb's query mechanism to fetch the subobject. This is a
# big win for large objects. ...However, we have to disable
# this feature if timestamps are active.
unless ($self->filled) {
my $subobject = $self->newFromText(
$self->db->show($self->class,$self->name,$tag),
$self->db
);
if ($subobject) {
$subobject->{'.nocache'}++;
$self->_attach_subtree($lctag => $subobject);
} else {
$self->{'.PATHS'}{$lctag} = undef;
}
$self->_dirty(1);
last TRY;
}
my @col = $self->col;
foreach (@col) {
next unless $_->isTag;
if (lc $_ eq $lctag) {
$self->{'.PATHS'}{$lctag} = $_;
$self->_dirty(1);
last TRY;
}
}
# if we get here, we didn't find it in the column,
# so we call ourselves recursively to find it
foreach (@col) {
next unless $_->isTag;
if (my $r = $_->search($tag)) {
$self->{'.PATHS'}{$lctag} = $r;
$self->_dirty(1);
last TRY;
}
}
# If we got here, we just didn't find it. So tag the cache
# as empty so that we don't try again
$self->{'.PATHS'}{$lctag} = undef;
$self->_dirty(1);
}
my $t = $self->{'.PATHS'}{$lctag};
return unless $t;
if (defined $subtag) {
if ($subtag =~ /^\d+$/) {
$pos = $subtag;
} else { # position on subtag and search again
return $t->fetch->search($subtag,$pos)
if $t->isObject || (defined($t->right) and $t->right->isObject);
return $t->search($subtag,$pos);
}
}
return defined $pos ? $t->right($pos) : $t unless wantarray;
# We do something verrrry interesting in an array context.
# If no position is defined, we return the column to the right.
# If a position is defined, we return everything $POS tags
# to the right (so-called tag[2] system).
return $t->col($pos);
}
# utility routine used in partial tree caching
sub _attach_subtree {
my $self = shift;
my ($tag,$subobject) = @_;
my $lctag = lc($tag);
my $obj;
if (lc($subobject->right) eq $lctag) { # new version of aceserver as of 11/30/98
$obj = $subobject->right;
} else { # old version of aceserver
$obj = $self->new('tag',$tag,$self->db);
$obj->{'.right'} = $subobject->right;
}
$self->{'.PATHS'}->{$lctag} = $obj;
}
sub _dirty {
my $self = shift;
$self->{'.dirty'} = shift if @_ && $self->isRoot;
$self->{'.dirty'};
}
#### return true if tree is populated, without populating it #####
sub filled {
my $self = shift;
return exists($self->{'.right'}) || exists($self->{'.raw'});
}
#### return true if you can follow the object in the database (i.e. a class ###
sub isPickable {
return shift->isObject;
}
#### Return a string representation of the object subject to Ace escaping rules ###
sub escape {
my $self = shift;
Ace/Object.pm view on Meta::CPAN
}
$current_obj->{'.right'} = $obj_right;
$self->_dirty(1) if $changed;
delete @{$self}{qw[.raw .start_row .end_row .col]};
}
sub _fromRaw {
my $pack = shift;
# this breaks inheritance...
# $pack = $pack->factory();
my ($raw,$start_row,$col,$end_row,$db) = @_;
$db = "$db" if ref $db;
return unless defined $raw->[$start_row][$col];
# HACK! Some LongText entries may begin with newlines. This is within the Acedb spec.
# Let's purge text entries of leading space and format them appropriate.
# This should probably be handled in Freesubs.xs / Ace::split
my $temp = $raw->[$start_row][$col];
# if ($temp =~ /^\?txt\?\s*\n*/) {
# $temp =~ s/^\?txt\?(\s*\\n*)/\?txt\?/;
# $temp .= '?';
# }
my ($class,$name,$ts) = Ace->split($temp);
my $self = $pack->new($class,$name,$db,!($start_row || $col));
@{$self}{qw(.raw .start_row .end_row .col db)} = ($raw,$start_row,$end_row,$col,$db);
$self->{'.timestamp'} = $ts if defined $ts;
return $self;
}
# Return partial ace subtree at indicated tag
sub _at {
my ($self,$tag) = @_;
my $pos=0;
# Removed a $` here to increase speed -- tim.cutts@incyte.com 2 Sep 1999
if ($tag=~/(.*?)\[(\d+)\]$/) {
$pos=$2;
$tag=$1;
}
my $p;
my $o = $self->right;
while ($o) {
return ($o->right($pos),$p,$self) if (lc($o) eq lc($tag));
$p = $o;
$o = $o->down;
}
return;
}
# Used to munge special data types. Right now dates are the
# only examples.
sub _ace_format {
my $self = shift;
my ($class,$name) = @_;
return undef unless defined $class && defined $name;
return $class eq 'date' ? $self->_to_ace_date($name) : $name;
}
# It's an object unless it is one of these things
sub _isObject {
return unless defined $_[0];
$_[0] !~ /^(float|int|date|tag|txt|peptide|dna|scalar|[Tt]ext|comment)$/;
}
# utility routine used to split a tag path into individual components
# allows components to contain dots.
sub _split_tags {
my $self = shift;
my $tag = shift;
$tag =~ s/\\\./$;/g; # protect backslashed dots
return map { (my $x=$_)=~s/$;/./g; $x } split(/\./,$tag);
}
1;
__END__
=head1 NAME
Ace::Object - Manipulate Ace Data Objects
=head1 SYNOPSIS
# open database connection and get an object
use Ace;
$db = Ace->connect(-host => 'beta.crbm.cnrs-mop.fr',
-port => 20000100);
$sequence = $db->fetch(Sequence => 'D12345');
# Inspect the object
$r = $sequence->at('Visible.Overlap_Right');
@row = $sequence->row;
@col = $sequence->col;
@tags = $sequence->tags;
# Explore object substructure
@more_tags = $sequence->at('Visible')->tags;
@col = $sequence->at("Visible.$more_tags[1]")->col;
# Follow a pointer into database
$r = $sequence->at('Visible.Overlap_Right')->fetch;
$next = $r->at('Visible.Overlap_left')->fetch;
# Classy way to do the same thing
$r = $sequence->Overlap_right;
$next = $sequence->Overlap_left;
# Pretty-print object
print $sequence->asString;
print $sequence->asTabs;
print $sequence->asHTML;
# Update object
$sequence->replace('Visible.Overlap_Right',$r,'M55555');
Ace/Object.pm view on Meta::CPAN
Return the class of the object. The return value may be one of
"float," "int," "date," "tag," "txt," "dna," "peptide," and "scalar."
(The last is used internally by Perl to represent objects created
programatically prior to committing them to the database.) The class
may also be a user-constructed type such as Sequence, Clone or
Author. These user-constructed types usually have an initial capital
letter.
=head2 db() method
$db = $object->db();
Return the database that the object is associated with.
=head2 isClass() method
$bool = $object->isClass();
Returns true if the object is a class (can be fetched from the
database).
=head2 isTag() method
$bool = $object->isTag();
Returns true if the object is a tag.
=head2 tags() method
@tags = $object->tags();
Return all the top-level tags in the object as a list. In the Author
example above, the returned list would be
('Full_name','Laboratory','Address','Paper').
You can fetch tags more deeply nested in the structure by navigating
inwards using the methods listed below.
=head2 right() and down() methods
$subtree = $object->right;
$subtree = $object->right($position);
$subtree = $object->down;
$subtree = $object->down($position);
B<right()> and B<down()> provide a low-level way of traversing the
tree structure by following the tree's right and down pointers.
Called without any arguments, these two methods will move one step.
Called with a numeric argument >= 0 they will move the indicated
number of steps (zero indicates no movement).
$full_name = $object->right->right;
$full_name = $object->right(2);
$city = $object->right->down->down->right->right->down->down;
$city = $object->right->down(2)->right(2)->down(2);
If $object contains the "Thierry-Mieg J" Author object, then the first
series of accesses shown above retrieves the string "Jean
Thierry-Mieg" and the second retrieves "34033 Montpellier." If the
right or bottom pointers are NULL, these methods will return undef.
In addition to being somewhat awkard, you will probably never need to
use these methods. A simpler way to retrieve the same information
would be to use the at() method described in the next section.
The right() and down() methods always walk through the tree of the
current object. They do not follow object pointers into the database.
Use B<fetch()> (or the deprecated B<pick()> or B<follow()> methods)
instead.
=head2 at() method
$subtree = $object->at($tag_path);
@values = $object->at($tag_path);
at() is a simple way to fetch the portion of the tree that you are
interested in. It takes a single argument, a simple tag or a path. A
simple tag, such as "Full_name", must correspond to a tag in the
column immediately to the right of the root of the tree. A path such
as "Address.Mail" is a dot-delimited path to the subtree. Some
examples are given below.
($full_name) = $object->at('Full_name');
@address_lines = $object->at('Address.Mail');
The second line above is equivalent to:
@address = $object->at('Address')->at('Mail');
Called without a tag name, at() just dereferences the object,
returning whatever is to the right of it, the same as
$object->right
If a path component already has a dot in it, you may escape the dot
with a backslash, as in:
$s=$db->fetch('Sequence','M4');
@homologies = $s->at('Homol.DNA_homol.yk192f7\.3';
This also demonstrates that path components don't necessarily have to
be tags, although in practice they usually are.
at() returns slightly different results depending on the context in
which it is called. In a list context, it returns the column of
values to the B<right> of the tag. However, in a scalar context, it
returns the subtree rooted at the tag. To appreciate the difference,
consider these two cases:
$name1 = $object->at('Full_name');
($name2) = $object->at('Full_name');
After these two statements run, $name1 will be the tag object named
"Full_name", and $name2 will be the text object "Jean Thierry-Mieg",
The relationship between the two is that $name1->right leads to
$name2. This is a powerful and useful construct, but it can be a trap
for the unwary. If this behavior drives you crazy, use this
construct:
$name1 = $object->at('Full_name')->at();
Ace/Object.pm view on Meta::CPAN
object's full address without having to refer to each of the
intervening "Mail", "E_Mail" and "Phone" tags explicitly.
@address = $object->at('Address[2]');
--> ('CRBM duCNRS','BP 5051','34033 Montpellier','FRANCE',
'mieg@kaa.cnrs-mop.fr,'33-67-613324','33-67-521559')
Similarly, "tag[3]" will return the column of data three hops to the
right of the tag. "tag[1]" is identical to "tag" (with no index), and
will return the column of data to the immediate right. There is no
special behavior associated with using "tag[0]" in an array context;
it will always return the subtree rooted at the indicated tag.
Internal indices such as "Homol[2].BLASTN", do not have special
behavior in an array context. They are always treated as if they were
called in a scalar context.
Also see B<col()> and B<get()>.
=head2 get() method
$subtree = $object->get($tag);
@values = $object->get($tag);
@values = $object->get($tag, $position);
@values = $object->get($tag => $subtag, $position);
The get() method will perform a breadth-first search through the
object (columns first, followed by rows) for the tag indicated by the
argument, returning the column of the portion of the subtree it points
to. For example, this code fragment will return the value of the
"Fax" tag.
($fax_no) = $object->get('Fax');
--> "33-67-521559"
The list versus scalar context semantics are the same as in at(), so
if you want to retrieve the scalar value pointed to by the indicated
tag, either use a list context as shown in the example, above, or a
dereference, as in:
$fax_no = $object->get('Fax');
--> "Fax"
$fax_no = $object->get('Fax')->at;
--> "33-67-521559"
An optional second argument to B<get()>, $position, allows you to
navigate the tree relative to the retrieved subtree. Like the B<at()>
navigational indexes, $position must be a number greater than or equal
to zero. In a scalar context, $position moves rightward through the
tree. In an array context, $position implements "tag[2]" semantics.
For example:
$fax_no = $object->get('Fax',0);
--> "Fax"
$fax_no = $object->get('Fax',1);
--> "33-67-521559"
$fax_no = $object->get('Fax',2);
--> undef # nothing beyond the fax number
@address = $object->get('Address',2);
--> ('CRBM duCNRS','BP 5051','34033 Montpellier','FRANCE',
'mieg@kaa.cnrs-mop.fr,'33-67-613324','33-67-521559')
It is important to note that B<get()> only traverses tags. It will
not traverse nodes that aren't tags, such as strings, integers or
objects. This is in keeping with the behavior of the Ace query
language "show" command.
This restriction can lead to confusing results. For example, consider
the following object:
Clone: B0280 Position Map Sequence-III Ends Left 3569
Right 3585
Pmap ctg377 -1040 -1024
Positive Positive_locus nhr-10
Sequence B0280
Location RW
FingerPrint Gel_Number 0
Canonical_for T20H1
K10E5
Bands 1354 18
The following attempt to fetch the left and right positions of the
clone will fail, because the search for the "Left" and "Right" tags
cannot traverse "Sequence-III", which is an object, not a tag:
my $left = $clone->get('Left'); # will NOT work
my $right = $clone->get('Right'); # neither will this one
You must explicitly step over the non-tag node in order to make this
query work. This syntax will work:
my $left = $clone->get('Map',1)->get('Left'); # works
my $left = $clone->get('Map',1)->get('Right'); # works
Or you might prefer to use the tag[2] syntax here:
my($left,$right) = $clone->get('Map',1)->at('Ends[2]');
Although not frequently used, there is a form of get() which allows
you to stack subtags:
$locus = $object->get('Positive'=>'Positive_locus');
Only on subtag is allowed. You can follow this by a position if wish
to offset from the subtag.
$locus = $object->get('Positive'=>'Positive_locus',1);
=head2 search() method
This is a deprecated synonym for get().
=head2 Autogenerated Access Methods
$scalar = $object->Name_of_tag;
$scalar = $object->Name_of_tag($position);
Ace/Object.pm view on Meta::CPAN
deletes the address line "FRANCE" from the Author's mailing address:
$object->delete('Address.Mail','FRANCE');
No actual database deletion occurs until you call commit(). The
delete() result code indicates whether the deletion was successful.
Currently it is always true, since the database model is not checked.
=head2 replace() method
$result_code = $object->replace($tag_path,$oldvalue,$newvalue);
$result_code = $object->replace(-path=>$tag_path,
-old=>$oldvalue,
-new=>$newvalue);
Replaces the indicated tag and value with the new value. This example
changes the address line "FRANCE" to "LANGUEDOC" in the Author's
mailing address:
$object->delete('Address.Mail','FRANCE','LANGUEDOC');
No actual database changes occur until you call commit(). The
delete() result code indicates whether the replace was successful.
Currently is true if the old value was identified.
=head2 commit() method
$result_code = $object->commit;
Commits all add(), replace() and delete() operations to the database.
It can also be used to write a completely new object into the
database. The result code indicates whether the object was
successfully written. If an error occurred, further details can be
found in the Ace->error() error string.
=head2 rollback() method
$object->rollback;
Discard all adds, deletions and replacements, returning the object to
the state it was in prior to the last commit().
rollback() works by deleting the object from Perl memory and fetching
the object anew from AceDB. If someone has changed the object in the
database while you were working with it, you will see this version,
ot the one you originally fetched.
If you are creating an entirely new object, you I<must> add at least
one tag in order to enter the object into the database.
=head2 kill() method
$result_code = $object->kill;
This will remove the object from the database immediately and
completely. It does not wait for a commit(), and does not respond to
a rollback(). If successful, you will be left with an empty object
that contains just the class and object names. Use with care!
In the case of failure, which commonly happens when the database is
not open for writing, this method will return undef. A description of
the problem can be found by calling the error() method.
=head2 date_style() method
$object->date_style('ace');
This is a convenience method that can be used to set the date format
for all objects returned by the database. It is exactly equivalent to
$object->db->date_style('ace');
Note that the text representation of the date will change for all
objects returned from this database, not just the current one.
=head2 isRoot() method
print "Top level object" if $object->isRoot;
This method will return true if the object is a "top level" object,
that is the root of an object tree rather than a subtree.
=head2 model() method
$model = $object->model;
This method will return the object's model as an Ace::Model object, or
undef if the object does not have a model. See L<Ace::Model> for
details.
=head2 timestamp() method
$stamp = $object->timestamp;
The B<timestamp()> method will retrieve the modification time and date
from the object. This works both with top level objects and with
subtrees. Timestamp handling must be turned on in the database, or
B<timestamp()> will return undef.
The returned timestamp is actually a UserSession object which can be
printed and explored like any other object. However, there is
currently no useful information in UserSession other than its name.
=head2 comment() method
$comment = $object->comment;
This returns the comment attached to an object or object subtree, if
any. Comments are I<Comment> objects and have the interesting
property that a single comment can refer to multiple objects. If
there is no comment attached to the current subtree, this method will
return undef.
Currently you cannot create a new comment in AcePerl or edit an old
one.
=head2 error() method
$error = $object->error;
Returns the error from the previous operation, if any. As in
Ace::error(), this string will only have meaning if the previous
operation returned a result code indicating an error.
=head2 factory() method
WARNING - THIS IS DEFUNCT AND NO LONGER WORKS. USE THE Ace->class() METHOD INSTEAD
$package = $object->factory;
When a root Ace object instantiates its tree of tags and values, it
creates a hierarchical structure of Ace::Object objects. The
factory() method determines what class to bless these subsidiary
objects into. By default, they are Ace::Object objects, but you can
override this method in a child class in order to create more
specialized Ace::Object classes. The method should return a string
corresponding to the package to bless the object into. It receives
the current Ace::Object as its first argument.
=head2 debug() method
$object->debug(1);
Change the debugging mode. A zero turns off debugging messages.
Integer values produce debug messages on standard error. Higher
integers produce progressively more verbose messages. This actually
is just a front end to Ace->debug(), so the debugging level is global.
=head1 SEE ALSO
L<Ace>, L<Ace::Model>, L<Ace::Object>, L<Ace::Local>,
L<Ace::Sequence>,L<Ace::Sequence::Multi>
=head1 AUTHOR
Lincoln Stein <lstein@cshl.org> with extensive help from Jean
Thierry-Mieg <mieg@kaa.crbm.cnrs-mop.fr>
Copyright (c) 1997-1998, Lincoln D. Stein
This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself. See DISCLAIMER.txt for
disclaimers of warranty.
=cut
# AUTOLOADED METHODS GO HERE
### Return the pretty-printed HTML table representation ###
### may pass a code reference to add additional formatting to cells ###
sub asHTML {
Ace/Object.pm view on Meta::CPAN
################# kill an object ################
# Removes the object from the database immediately.
sub kill {
my $self = shift;
return unless my $db = $self->db;
return 1 unless $db->count($self->class,$self->name);
my $result = $db->raw_query("kill");
if (defined($result) and $result=~/write access/im) { # this keeps changing
$Ace::Error = "Write access denied";
return;
}
# uncache cached values and clear the object out
# as best we can
delete @{$self}{qw[.PATHS .right .raw .down]};
1;
}
# sub isTimestamp {
# my $self = shift;
# return 1 if $self->class eq 'UserSession';
# return;
# }
sub isComment {
my $self = shift;
return 1 if $self->class eq 'Comment';
return;
}
################# add a new row #############
# Only changes local copy until you perform commit() #
# returns true if this is a valid thing to do #
sub add_row {
my $self = shift;
my($tag,@newvalue) = rearrange([['TAG','PATH'],'VALUE'],@_);
# flatten array refs into array
my @values = map { ref($_) && ref($_) eq 'ARRAY' ? @$_ : $_ } @newvalue;
# make sure that this entry doesn't already exist
unless ($tag =~ /\./) {
my $model = $self->model;
my @intermediate_tags = $model->path($tag);
$tag = join '.',@intermediate_tags,$tag;
}
my $row = join(".",($tag,map { (my $x = $_) =~s/\./\\./g; $x } @values));
return if $self->at($row); # an identical row already exists in the object
# If we get here then we need to turn @values into an array of Ace::Objects
# for insertion. Also need to link them together into a row.
my $previous;
foreach (@values) {
if (ref($_) && $_->isa('Ace::Object')) {
$_ = $_->_clone;
} else {
$_ = $self->new('scalar',$_);
}
$previous->{'.right'} = $_ if defined $previous;
$previous = $_;
$_->{'.right'} = undef; # make sure it doesn't automatically expand!
}
# position at the indicated tag (creating it if necessary)
my (@tags) = $self->_split_tags($tag);
my $p = $self;
foreach (@tags) {
$p = $p->_insert($_);
}
if ($p->{'.right'}) {
$p = $p->{'.right'};
while (1) {
last unless $p->{'.down'};
$p = $p->{'.down'};
}
$p->{'.down'} = $values[0];
} else {
$p->{'.right'} = $values[0];
}
push(@{$self->{'.update'}},join(' ',map { Ace->freeprotect($_) } (@tags,@values)));
delete $self->{'.PATHS'}; # uncache cached values
$self->_dirty(1);
1;
}
# Use this method to add an entire subobject to the right of the tag.
# The tree may come from another database.
sub add_tree {
my $self = shift;
my($tag,$value,@rest) = rearrange([['TAG','PATH'],['VALUE','TREE']],@_);
croak "Value must be an Ace::Object" unless ref($value) && $value->isa('Ace::Object');
unless ($tag =~ /\./) {
my $model = $self->model;
my @intermediate_tags = $model->path($tag);
$tag = join '.',@intermediate_tags,$tag;
}
# position at the indicated tag, creating it if necessary
my (@tags) = $self->_split_tags($tag);
my $p = $self;
foreach (@tags) {
$p = $p->_insert($_);
}
# Copy the subtree too
if ($p->{'.right'}) {
$p = $p->{'.right'};
while (1) {
last unless $p->{'.down'};
$p = $p->{'.down'};
}
$p->{'.down'} = $value->{'.right'};
} else {
$p->{'.right'} = $value->{'.right'};
}
push(@{$self->{'.update'}},map { join(' ',@tags,$_) } split("\n",$value->asAce));
delete $self->{'.PATHS'}; # uncache cached values
$self->_dirty(1);
1;
}
################# delete a portion of the tree #############
# Only changes local copy until you perform commit() #
# returns true if this is a valid thing to do.
sub delete {
my $self = shift;
my($tag,$oldvalue,@rest) = rearrange([['TAG','PATH'],['VALUE','OLDVALUE','OLD']],@_);
# flatten array refs into array
my @values;
@values = map { ref($_) && ref($_) eq 'ARRAY' ? @$_ : $_ } ($oldvalue,@rest)
if defined($oldvalue);
unless ($tag =~ /\./) {
my $model = $self->model;
my @intermediate_tags = $model->path($tag);
$tag = join '.',@intermediate_tags,$tag;
}
my $row = join(".",($tag,map { (my $x = $_) =~s/\./\\./g; $x } @values));
my $subtree = $self->at($row,undef,1); # returns the parent
if (@values
&& defined($subtree->{'.right'})
&& "$subtree->{'.right'}" eq $oldvalue) {
$subtree->{'.right'} = $subtree->{'.right'}->down;
} else {
$subtree->{'.down'} = $subtree->{'.down'}->{'.down'}
}
push(@{$self->{'.update'}},join(' ','-D',
map { Ace->freeprotect($_) } ($self->_split_tags($tag),@values)));
delete $self->{'.PATHS'}; # uncache cached values
$self->_dirty(0);
$self->db->file_cache_delete($self);
1;
}
################# delete a portion of the tree #############
# Only changes local copy until you perform commit() #
# returns true if this is a valid thing to do #
sub replace {
my $self = shift;
my($tag,$oldvalue,$newvalue,@rest) = rearrange([['TAG','PATH'],
['OLDVALUE','OLD'],
['NEWVALUE','NEW']],@_);
$self->delete($tag,$oldvalue);
$self->add($tag,$newvalue,@rest);
delete $self->{'.PATHS'}; # uncache cached values
1;
}
# commit changes from local copy to database copy
sub commit {
my $self = shift;
return unless my $db = $self->db;
my ($retval,@cmd);
my $name = $self->{'name'};
return unless defined $name;
$name =~ s/([^a-zA-Z0-9_-])/\\$1/g;
return 1 unless exists $self->{'.update'} && $self->{'.update'};
$Ace::Error = '';
my $result = '';
# bad design alert: the following breaks encapsulation
if ($db->db->can('write')) { # new way for socket server
my $cmd = join "\n","$self->{'class'} : $name",@{$self->{'.update'}};
warn $cmd if $self->debug;
$result = $db->raw_query($cmd,0,'parse'); # sets Ace::Error for us
} else { # old way for RPC server and local
my $cmd = join('; ',"$self->{'class'} : $name",
@{$self->{'.update'}});
warn $cmd if $self->debug;
$result = $db->raw_query("parse = $cmd");
}
if (defined($result) and $result=~/write( or admin)? access/im) { # this keeps changing
$Ace::Error = "Write access denied";
} elsif (defined($result) and $result =~ /sorry|parse error/mi) {
$Ace::Error = $result;
}
return if $Ace::Error;
undef $self->{'.update'};
# this will force a fresh retrieval of the object
# and synchronize our in-memory copy with the db
delete $self->{'.right'};
delete $self->{'.PATHS'};
return 1;
}
# undo changes
sub rollback {
my $self = shift;
undef $self->{'.update'};
# this will force object to be reloaded from database
# next time it is needed.
delete $self->{'.right'};
delete $self->{'.PATHS'};
1;
}
sub debug {
my $self = shift;
Ace->debug(@_);
}
### Get or set the date style (actually calls through to the database object) ###
sub date_style {
my $self = shift;
return unless $self->db;
return $self->db->date_style(@_);
}
sub _asHTML {
my($self,$out,$position,$level,$morph_code) = @_;
do {
$$out .= "<TR ALIGN=LEFT VALIGN=TOP>" unless $position;
$$out .= "<TD></TD>" x ($level-$position-1);
my ($cell,$prune,$did_it_myself) = $morph_code->($self);
$$out .= $did_it_myself ? $cell : "<TD>$cell</TD>";
if ($self->comment) {
my ($cell,$p,$d) = $morph_code->($self->comment);
$$out .= $d ? $cell : "<TD>$cell</TD>";
$$out .= "</TR>\n" . "<TD></TD>" x $level unless $self->down && !defined($self->right);
}
$level = $self->right->_asHTML($out,$level,$level+1,$morph_code) if defined($self->right) && !$prune;
$$out .= "</TR>\n" if defined($self = $self->down);
$position = 0;
} while defined $self;
return --$level;
}
# This function is overly long because it is optimized to prevent parsing
# parts of the tree that haven't previously been parsed.
sub _asTable {
my($self,$out,$position,$level) = @_;
do {
if ($self->{'.raw'}) { # we still have raw data, so we can optimize
my ($a,$start,$end) = @{$self}{ qw(.col .start_row .end_row) };
my @to_append = map { join("\t",@{$_}[$a..$#{$_}]) } @{$self->{'.raw'}}[$start..$end];
my $new_row;
foreach (@to_append) {
# hack alert
s/(\?.*?[^\\]\?.*?[^\\]\?)\S*/$self->_ace_format(Ace->split($1))/eg;
if ($new_row++) {
$$out .= "\n";
$$out .= "\t" x ($level-1)
}
$$out .= $_;
}
return $level-1;
}
( run in 1.200 second using v1.01-cache-2.11-cpan-d80b1682f3f )