Perlbug::Object - Object handler for Perlbug database


Perlbug documentation Contained in the Perlbug distribution.

Index


Code Index:

NAME

Top

Perlbug::Object - Object handler for Perlbug database

DESCRIPTION

Top

Handles Perlbug database objects, typically bug, group, message, patch, note, test, user, and severity, status etc....

Methods included to recognise objects by their id or by their also unique name.

SYNOPSIS

Top

	my $o_obj 	= Perlbug::Object->new(\%init); # see L<new()>

	$o_obj 		= $o_obj->read($oid);		# data

	my $name   	= $o_obj->data('name'); 	# Bug

	# ALL bugids (optionally) constrained by sql 'where' clause
	my @ids         = $o_obj->ids($where);	# where

	# Relation ids
	my @patchids	= $o_obj->rel_ids('patch');	# relids 

	print = $o_obj->format('h');		

METHODS

Top

new

Create a new object, you need to supply up to three (3) things:

	1. A pre-initialised Perlbug::Base->new() object:

	2. Attribute pairs: 

	3. Relation array refs:

		b<float> is a straight column related to our id and has no distinct object handler, 

		b<from> and B<to> are related with full ids, handlers, etc. treatment.

Example:

	my $o_obj = Perlbug::Object->new( 
		# Optional base object, useful to maintain transactions
			$o_Perlbug_Base_Object, 	# 
		# Attributes
			'name'	=> 'Bug',		# mandatory key 
		# Relationships	
			'float'	=> [qw(change)],			
			'from'	=> [],
			'to'	=> [qw(message note patch test user)], 
	};

init_data

Initialise generic object attr, columns and column_types from table in db (specific).

	my $o_obj = $o_obj->init_data($table);

init_types

Initialise generic object attr based on table from db, returns relation names.

	my @rels = $o_obj->init_types(@rel_types);

reinit

Reset object to default values, with optional object_id where different, returns object

	$o_obj->reinit($oid);

To check whether the object was succesfully reinit, ask:

	my $i_isok = $o_obj->REINIT; # ?

refresh_relations

Refresh relation data, optionally restricted to only those given, others are cleared.

	$self->refresh_relations([\@wanted]);

check

Check all attr are initialised

	my $i_ok = $o_obj->check(\%attributes);

REINIT

Returns 0|1 depending on whether object has been reinit

	my $i_isok = $o_obj->REINIT;

exists

Examines the database to see if current object exists already.

Second optional parameter overrides sql caching

Return @ids

	print "yup\n" if $o_obj->exists([$oid]);

_exists

Examines the database to see if current object exists by identifier already.

Second optional parameter overrides sql caching

	print "yup\n" if $o_obj->_exists(\@ids);

fields

Returns all valid data field names for this object

	my @fields = $o_obj->fields;

str2ids

Return appropriate (match_oid)s found in given string

	my @ids = $o_obj->str2ids($str);

ok_ids

Checks to see if given oid/s look anything like we are expecting them to.

Returns list of acceptable object ids

	my @ok_ids = $o_obj->ok_ids(\@some_ids);

primary_key

Wrapper to only get primary_key.

	my $pri = $o_obj->primary_key;

key

Wrapper to get and set key.

	my $key = $o_obj->key($key);

objectid

Wrapper to get and set objectid, and data(<objectid>) at the same time.

	my $oid = $o_obj->objectid($id);

id

Returns any ok_ids found in given data structure under id or ${obj}_id

	my @ids = $o_obj->id({
		'id'	  => [(23, 44, 7)], 
		'testid'  => [(23, 44, 7)], 
		'testids' => [(23, 44, 7)], 
		'test_id' => [(23, 44, 7)],
	});

ids

Gets DISTINCT ids, for this object

	my @all_ids  = $o_obj->ids(); 

Which is a bit like an unrestricted col($primary_key, '') call.

More useful are the following examples, restrained by object, or sql WHERE statement:

	my @rel_ids  = $o_obj->ids($o_rel, [$further_restrained_by_sql], 'refresh');

	my @selected = $o_obj->ids($where);

names

Get DISTINCT names for this object.

If there is no ident=name, or no names, for the object, returns empty list().

For restraints/parameters see ids()

	my @names = $o_obj->names();

col

Gets DISTINCT column, from all or with a where sql statement

	my @all_cols = $o_obj->cols('name');

	my @rel_cols = $o_obj->cols('name, $o_rel);

	my @selected = $o_obj->cols('name', $where);

identifier

Return identifying string key for this object, 'name' or whatever

id2name

Convert ids to names

	my @names = $o_obj->id2name(\@ids);

name2id

Convert names to ids

	my @ids = $o_obj->name2id(\@names);

count

Return number of objects, optionally restrained by argument given

	my $i_cnt = $o_obj->count;

	my $i_cnt = $o_obj->count($o_rel); # uses o_rel(objectid) 

	my $i_cnt = $o_obj->count("$objectid Like '$criteria'");

trim

Return args trimmed of whitespace, ready for comparison checks

	my @trimmed = $o_obj->trim([qw(this and that)]);

keys_sorted_by_value

Return list of keys sorted by values

	my @sorted = $o_obj->keys_sorted_by_value(\%hash);

Return an href link to this object given optional ids, or search link if none given, (eg with a o_test object):

	my $link = $o_obj->link($fmt, \@testids, $js); # bugcgi?req=test_id&test_id=37&format=h&etc.

choice

Returns appropriate popup() or selector() for object, based on prejudicial setting.

	print $o_obj->choice($unique_name, [$selected]); # or none('') 

Create scrolling web list popup with given pre-selection (or any), with (alphabetically sorted) names where possible, and optional WHERE clause

	my $popup = $o_obj->popup('unique_name', $selected, [$where]); 

selector

Create scrolling web list selector with given pre-selections, with names where possible. Also appends simple list of selected items.

	my $selctr = $o_obj->selector('unique_name', \@pre_selected);

text_area

Create text_area with given args, prep for select(js)

	my $ta = $o_obj->text_area('unique_name', 'value', [etc.]);

text_field

Create text_field with given args, prep for select(js)

	my $tf = $o_obj->text_field('unique_name', 'value', [etc.]);

htmlify

Returns args with select this object inserted, calls Format::htmlify

	my \%data = $o_obj->htmlify(\%data);

form

Return a web form for this object

	print $o_obj->form($fmt);

Return a web search form for this object

	print $o_obj->search($fmt);

initform

Return an web based object initialisation form.

	my $nix = $o_obj->initform(); # N.B. <-- actually prints the form!




_gen_field_handler

Generate code to handle get and set object fields, returns 1|0

	my $i_ok = $o_obj->_gen_field_handler('header');

	my $var = $o_obj->header($msg); # var has msg

base

Return application specific Perlbug::Base object, given as $o_obj->new($o_base) or new object altogether.

_oref

Unsupported method to retrieve hash ref of requested type

	my $h_ref = $o_obj->_oref('attr');

# # ===============================================================================

RELATIONS

Top

Object relations are handled by a group of methods:

	my @rellies 	= $o_obj->relations('to');	# patch, message, note, test, user, 

	my $o_patch     = $o_obj->relation('patch');	# handler

	my @pids	= $o_patch->ids($o_obj);	# or

	my @pids     	= $o_obj->relation_ids('patch');# ids

Note that relations are between one object and (from or to) another, or of a 'floating' kind.

If it's another object you want, see "object()".

relation_types

Return relation types for current object

	my @types = $self->relation_types; # from, to

isarel

Returns 1|0 dependant on whether relation($rel), is of given type (or any), or not

	print "yup\n" if $o_obj->isarel($rel);

eg:

	print "patch is related to a bug\n" if $o_pat->isarel('bug');

relations

Return relations, filtered by arg, or all if none given

	my @rellies = $self->relations('from'); # patch, user, etc.

relation

Return object handler for given relation

	my $o_b2p = $o_bug->relation('patch');

	print $o_b2p->assign(\@list_new_patch_ids_2_bug);

If the original (in this case bug) object had already an oid() assigned, (it knew which bug it represented), the relation will be pre-initialised with the relevant bugid, by for example a read() call. Note, however, that where the sourceid is unknown, then only a generic relationship object is returned. eg; this should explicitly work:

	print $o_bug->read('19870502.007')->relation('patch')->assign(\@pids);

Note that the read() method takes a single liberty, in that it calls Perlbug::Relation::set_source() on the retrieved relation, thus ensuring said relation knows which object, (of the two that it holds) to regard as source.

See Perlbug::Relation for more info on relation methods.

relation_ids

Return relation IDs for given object

	my @patch_ids = $o_obj->relation_ids('patch');

_rel_ids

Refresh rel_ids

relation_names

Return relation names for given object, or empty list() if no names, or not ident=name

	my @os_names = $o_obj->relation_names('osname');

relate

Work through the given hash using the objects' relations():

	B<assign()>ing any relation-ids found

	B<_assign()>ing any relation-names found

Prejudicial against $o_rel->attr('prejudicial') relationships, and is designed to take the output of Perlbug::Base::parse_str().

Returns name of objects assigned to.

	my $i_rels = my @rels = $o_obj->relate(\%relationships);

	where B<%relationships> = (
		'address'	=> {
			'ids'	=> [qw(7 223 78 26 13)],
		},
		'address'	=> {
			'names'	=> [qw(me@home.net buggy@system.com etc@the.net)],
		},
		'bug'		=> {
			'ids'	=> [qw(19870502.007)],
		},
		'group'		=> {
			'ids'	=> [qw()],
		},
		'osname'	=> {
			'ids'	=> [qw(3 7 21 23)],
			'names'		=> [qw(aix irix macos win32)],
		},
		'status'	=> {
			'names'	=> [qw(open)],	
		},
		'version'	=> {
			'ids'	=> [qw(4 28 273)],
			'names'	=> [qw(5.7.3)],
		},
	); 

See also parse_str() and rtrack()

appropriate

Attempts to relate relatable bug relations to relevant bugs :-)

The idea is that a test can call appropriate() after a relate(), and this will apply appropriate status flags to any bugids found, etc.

See relate() for more info.

	my @bugids = $o_obj->appropriate(\%rels);

# =============================================================================

RECORDS

Top

Record handling methods for Perlbug::Object::\w+'s

read

Read the data, from the db, by id, and load into current object.

After this it is possible to get to meaningful relations via rel_ids(), and correct format()ing

Returns object so it is possible to chain calls.

	$o_obj->read($id);

And...

	print $o_obj->read($id)->format('h'); # etc.

To check whether the object was succesfully read, ask:

	my $i_isok = $o_obj->READ; 

READ

Returns 0|1 depending on whether object has had a successful read, post new/init/reinit

	my $i_isok = $o_obj->READ;

_read

Wrap read() call to operate by name (if possible)

	print $o_obj->_read($name)->format('h'); # etc.

column_type

Return sql type for given column name

	my $datetime = $o_obj->column_type('created'); # DATETIME

	my $integer  = $o_obj->column_type('created'); # INTEGER 

	my $varchar  = $o_obj->column_type('created'); # VARCHAR <default>

to_date

Currently redundant, because Mysql takes care of this, but Oracle may want to do more than this...

	my $sql_date = $o_obj->to_date($date_string);

prep

Quote (or not) given data, ready to go into our table

	my $sql = $o_obj->prep('insert', $h_data); # or 'update'

massage

Massage given o_cgi data into a form appropriate for query, update or create() usage

Returns only object data specific reference!

	my $h_data = $o_obj->massage(\%query);

minimal_create_info

Pad out data for new object creation, only adds to data if nothing found.

	my $h_out = $o_obj->minimal_create_info(\%in);

query

Setup and execute sensible SQL, returning ids found, from given h_data for relevant object fields.

	my @ids = $o_obj->query(\%query);

create

Creates a new system object via inserting the given data into the db, loaded from current object, or given data.

Returns $o_object->read($id).

	$o_obj->create(); 		# using object data

	$o_obj->create($h_data);	# using given data, note B<only> this data is used!

	$o_obj->create($h_data, 'relation');	# ignore exists call

N.B. caller must set up the appropriate objectid (\d+|<bugid>|NULL|Sequence|...) previously.

To check whether the object was succesfully created, ask:

	my $i_isok = $o_obj->CREATED; #

CREATED

Returns 0|1 depending on whether object has been succesfully created

	my $i_isok = $o_obj->CREATED;

store

Stores the given data into the db, (creates new data record), loaded from current object, or given data.

Executes an insert() or update() dependent on whether the object pre-exists or not.

For more info see create() and update().

Returns $o_object->read($id).

	$o_obj->store(); 		# using object data

	$o_obj->store($h_data);	# using given data, note B<only> this data is used!

To check whether the object was succesfully stored, ask:

	my $i_isok = $o_obj->STORED; # ?

STORED

Returns 0|1 depending on whether object has been succesfully stored

	my $i_isok = $o_obj->STORED;

transfer

Transfer the data to another object (type)

	my $new_oid = $o_obj->transfer(\%data, $oid);

webupdate

Update object via web interface, accepts relations via param('_opts')

Generically does not update object data itself.

	$oid = $o_obj->webupdate(\%cgidata, $oid);

update

Update the given data into the db, loaded from current object, or given data.

	$o_obj->update(); 			# using object data

	$o_obj->update(\%data);		# using given data, note B<only> this data is used!

To check whether the object was succesfully updated, ask:

	my $i_isok = $o_obj->UPDATED; # ?

UPDATED

Returns 0|1 depending on whether object has been succesfully updated

	my $i_isok = $o_obj->UPDATED;

delete

Delete the given objectid/s or current object, and all it's relationships

	$o_obj->delete(); 				# this object

	$o_obj->delete(\@oids);			# list ref

To check whether the object/s was succesfully deleted, ask:

	my $i_isok = $o_obj->DELETED; # 0|1

DELETED

Returns 0|1 depending on whether object has been succesfully deleted

	my $i_isok = $o_obj->DELETED;

updatable

Check if current object(type) is allowed to be updated

Returns updatable ids

    print 'updatable: '.join(', ', $o_obj->updatable(\@ids));

insertid

Returns newly inserted id from database statement handle

	my $new_oid = $o_obj->insertid($sth, $oid);

new_id

Return valid new object id for given object, usually NULL, as Mysql generates own.

	my $new_oid = $o_obj->new_id

	# Bug/User expected to generate it's own
	# Mysql specific
	# Oracle requires SELECT FROM SEQUENCE ...
	# Relations map differently...

# =============================================================================

CONVENIENCE

Top

Convenient wrappers for the following methods are supported, for more details see Perlbug::Base

error

Wrapper for $o_obj->base->error()

debug

Wrapper for $o_obj->base->method()

object

Wrapper for $o_obj->base->method()

format

Simple wrapper for FORMAT()

	my $str = $o_obj->format('h');

template

Applies appropriate template to this object, based on optional format, h_data, h_rels.

	my $str = $o_obj->template($fmt, [$h_data, [$h_rels]]); # [ahl...]

diff

Returns differences between two (format|templat)ed strings, on a per line basis.

Note that multiple blank lines are reduced to a single blank line.

	my $diff = $o_obj->diff("this\nand\that", "this\nor\nthat\netc.");

Produces:

	old:
		2  and
		4

	new:
		2  or
		4  etc.

rtrack

Tracks object administration (relations), where %entry is the relevant relate() data, etc.

	$o_obj = $o_obj->rtrack(\%data, [$obj, [$objectid]]);

TRACKED

Returns 0|1 depending on whether object has been succesfully TRACKED

	my $i_isok = $o_obj->TRACKED;

attr

Get and set attributes

	my $objectid = $o_obj->attr('objectid');			# get

	my $newobjid = $o_obj->attr({'objectid', $newid});	# set

data

Get and set data by hash ref.

Returns data values, all if none specified.

	$o_obj->data({
		'this' 	=> 'that',
		'and'	=> 'so on',
	});

	my $name = $o_obj->data('name');

	my @vals = $o_obj->data;

flag

Get and set flags

	my $i_read = $o_obj->flag('read');			# get

var

Note that to set any of these you have to send in a hashref!

Returns keys of succesful updates

	my $attr = $self->attr('objectid'); 			# get

	my @keys = $self->data();						# get

	my $data = $self->flag({'created' => 1}); 		# set $data=created

	my @data = $self->data({'name' => 'newname', 'body'	=> 'stuff'}); # set 

AUTHOR

Top

Richard Foley perlbug@rfi.net 2000 2001 2002


Perlbug documentation Contained in the Perlbug distribution.
# Perlbug object attribute handler
# (C) 2000 Richard Foley RFI perlbug@rfi.net
# $Id: Object.pm,v 1.53 2002/02/01 08:36:45 richardf Exp $
#

package Perlbug::Object;
use strict;
use vars(qw($VERSION @ISA $AUTOLOAD));
@ISA = qw(Perlbug::Format); 
$VERSION = do { my @r = (q$Revision: 1.53 $ =~ /\d+/go); sprintf "%d."."%02d" x $#r, @r }; 
$|=1;

use Carp;
use CGI;
use Data::Dumper;
use Perlbug::Format; 
# use Perlbug::Template; 
my $o_Perlbug_Base = '';
%Perlbug::Object::Data = (); # ?
%Perlbug::Object::Data = (
	'relation'	=> {
		'from'	=> [],
		'to'	=> [],
	},
	'type'		=> {
		'field'	=> 'VARCHAR',
	},
);

sub new { # table, key
    my $proto = shift;
    my $class = ref($proto) || $proto; 
	$o_Perlbug_Base = (ref($_[0])) ? shift : Perlbug::Base->new;
	my %input  = @_;

	my $name = ucfirst($input{'name'});
	my $key  = lc($name);
	unless ($key =~ /\w+/o) {
		$o_Perlbug_Base->error("Fatal error: no keyname($name) given!\n".Dumper(\%input)."\n");
	}

	my $self = { 												# eg:
		'_attr'	=> {			
			'float'		=> [],				# rel
			'from'		=> [],				# rel
			'hint'		=> "$name($key)",	# Bug(Child)...
		    'key'		=> $key,			# bug
		    'name'  	=> $name,			# Bug
			'match_oid'	=> '[\b\D]*(\d+)[\b\D]*',  		# default
		    'objectid'	=> '',				# 21, 200011122.003
			'primary_key'=> $key.'id',		# bugid
			'printed'	=> 0,				# i_cnt
			'prejudicial'=> 0,				# single only (ie; status, severity, etc.)?
			'sql_clean'	=> 1,				# clean sql on create, update, delete, etc.
		    'table' 	=> 'pb_'.$key,		# db_bug
			'track'		=> '1',				# usually
			'to'		=> [],				# rel
			'types'		=> [qw(from to)], 	# of rels
			%input,
		},  	
		'_data'			=> {}, 		 		# 'field' 	=> 'value' ...
		'_type'			=> {},				# 'field'	=> 'DATE|INTEGER|VARCHAR'...
		'_relation'		=> {},				# 'from' => [], 'to' => [qw(patch status ...)],
		'_flag'			=> {
			'assigned'	=> 0,				# flags
			'created'	=> 0,				#  -"- 
			'deleted'	=> 0,				#  -"- 
			'read'		=> 0,				#  -"- 
			'reset'		=> 0,				#  -"- 
			'stored'	=> 0,				#  -"-
			'tracked'	=> 0,				#  -"-
			'updated'	=> 0,				#  -"-
		},
	};

	$self = bless($self, $class);

	$self->data; $self->attr; $self->flag; # prime
	$self = $self->reinit; # inc. check()

	return $self;
}


sub init_data { # generic attr from db
	my $self  = shift;
	my $table = shift || $self->attr('table');

	$self->{'_data'} = {};
	$self->{'_type'} = {};

	# my $fields = "SELECT * FROM $table WHERE 1 = 0";
	my $fields = "SHOW fields FROM $table"; # Mysql specific?
	my @fields = $self->base->get_data($fields);
	FIELD:
	foreach my $f (@fields) { 
		next FIELD unless ref($f) eq 'HASH';
		my $field = $$f{'Field'};
		my $type  = 'VARCHAR'; # default
		$type = 'DATETIME' if $$f{'Type'} =~ /^DATE(TIME)*$/io;
		$type = 'INTEGER'  if $$f{'Type'} =~ /^(BIG|SMALL)*INT(EGER)*(\(\d+\))*/io;
		# $Perlbug::Object{'_data'}{$field} = '';
		# $Perlbug::Object{'_type'}{$field} = $type;
		$self->{'_data'}{$field} = ''; 		# init	
		$self->{'_type'}{$field} = $type; 	# init	
		# $self->_gen_field_handler($field);# don't call 
	}

	return $self;
}

sub init_types { # generic attr from db
	my $self  = shift;
	my @types = @_;
	my @rels  = ();
	
	$self->{'_relation'} = {}; # 
	foreach my $type (@types) { # float|from|to
		foreach my $targ ( $self->attr($type) ) { # patch change bug address user
			$self->{'_relation'}{$targ}{'type'} = $type;
			push(@rels, $targ);
		}
	}

	return @rels;
}

sub reinit { 
	my $self = shift; 
	my $oid = shift || ''; #  || $self->oid;

	$self->CREATED(0);
	$self->READ(0);
	$self->UPDATED(0);
	$self->DELETED(0); 
	$self->STORED(0);
	$self->TRACKED(0);
	$self->REINIT(1);

	my @fields = $self->init_data($self->attr('table'));
	my $i_ok   = $self->check();
	my @types  = $self->attr('types');
	my @rels   = $self->init_types(@types);

	$self->attr( { 'objectid', $oid} ); # explicit	
	$self->data( { $self->attr('primary_key'), $oid} );

	$self->debug(3, "object($oid) reinit(".$self->attr('key').") types(@types) rels(@rels)") if $Perlbug::DEBUG;

	return $self;
}

sub refresh_relations {
	my $self = shift;
	my @args = my @rels = @_;
	my $obj  = $self->key;

	my $rellies = join('|', my @rellies = $self->rels);
	@rels = grep(/($rellies)/, @args);
	$self->debug(2, ref($self).": args(@args) rellies($rellies) => rels(@rels)") if $Perlbug::DEBUG;

	$self->{'_relation'} = {};	

	REL:
	foreach my $rel (@rels) {
		next REL if $rel =~ /^$obj$/i; # no recurse
		my @rids  = $self->rel_ids($rel, '', 'refresh');		# if ids	
		# my @names = $self->rel_names($rel, '', 'refresh');	# 
		my @names = $self->object($rel)->id2name(\@rids);		# if names
		$self->{'_relation'}{$rel}{'count'} =  @rids;	
		$self->{'_relation'}{$rel}{'ids'}   = \@rids; 
		$self->{'_relation'}{$rel}{'names'} = \@names;	
	}
	$self->debug(3, 'relations: '.Dumper($self->{'_relation'})) if $Perlbug::DEBUG;

	return $self;
}

sub check {
	my $self  = shift;
	my $h_ref = shift || $self->_oref('attr');

	my $i_ok  = 1;

	CHECK:
	foreach my $key (keys %{$h_ref}) {
		unless ($key =~ /(debug|objectid)/io) {
			if ($$h_ref{$key} !~ /\w+/) {
				$i_ok = 0;
				$self->error(" is incomplete key($key) val($$h_ref{$key}): ".Dumper($h_ref));
			}
		}
	}

	return $i_ok;
}

sub REINIT {
	my $self = shift;
	my $i_flag = shift || ''; 

	$self->flag('reinit', $1) if $i_flag =~ /^(1|0)$/o;	

	$i_flag = $self->flag('reinit');

	return $i_flag;	
}

sub exists {
	my $self = shift;
	my $a_oids = shift || [ $self->oid ];
	my @IDS = ();

	if (ref($a_oids) ne 'ARRAY') {
		$self->error("requires array ref($a_oids) of oids!");
	} else {
		my $ids = join("', '", @{$a_oids});
		my $pri = $self->attr('primary_key');
		my $sql = "SELECT DISTINCT $pri FROM ".$self->attr('table'). 
				  # " WHERE $pri Like '_%' AND $pri IN ('$ids')"
				  " WHERE $pri IN ('$ids')"
		;
		@IDS = $self->base->get_list($sql);
	}

	return @IDS;
}

sub _exists {
	my $self = shift;
	my $a_ids = shift || [ $self->attr($self->identifier) ];
	my @IDS = ();

	if (ref($a_ids) ne 'ARRAY') {
		$self->error("requires array ref($a_ids) of ids!");
	} else {
		my $ids = join("', '", @{$a_ids});
		my $pri = $self->identifier;
		my $sql = "SELECT DISTINCT $pri FROM ".$self->attr('table'). 
				  # " WHERE $pri Like '_%' AND $pri IN ('$ids')"
				  " WHERE $pri IN ('$ids')"
		;
		@IDS = $self->base->get_list($sql);
	}

	return @IDS;
}

sub data_fields { my $self = shift; return $self->fields(@_); }

sub fields {
	my $self = shift;

	my @fields = keys %{$self->{'_data'}};

	return @fields;
}

sub str2ids {
	my $self = shift;
	my $str  = shift || '';
	my @ids  = ();

	if ($str !~	/\w+/) {
		$self->error("no string($str) given to inspect for ids!");
	} else {
		my $match = $self->attr('match_oid');
		# my %x = ($dmc =~ /\<(\w+)\>(\w+)?(?:)\<\/\1\>/gi)
		@ids = ($str =~ /$match/cgs);
		$self->debug(2, "str($str) match($match) -> ids(@ids)") if $Perlbug::DEBUG;
	}

	return @ids;
}

sub ok_ids {
	my $self = shift;
	my $a_ids = shift || '';
	my @ok = ();

	if (!ref($a_ids) eq 'ARRAY') {
		$self->error("expecting array_ref($a_ids) of object ids!");
	} else {
		my $ids = join('|', my @ids = @{$a_ids});
		if (!(scalar(@ids) >= 1)) {
			$self->debug(2, "no ids(@ids) given") if $Perlbug::DEBUG;
		} else {
			my @wids = map { ($_ =~ /\w+/o ? $_ : ()) } @ids;  
			if (!(scalar(@wids))) {
				$self->debug(2, "no word-like ids(@wids) given(@ids)") if $Perlbug::DEBUG;
			} else {
				my $i_ids = @wids;
				my $match = $self->attr('match_oid');
				my $i_oks = @ok = map { ($_ =~ /^$match$/ ? $_ : ()) } @wids;  
				if ($i_ids != $i_oks) {
					$self->debug(2, $self->key()." failed to match($match) object ids! given: $i_ids(@wids) => ok_ids: $i_oks(@ok)") if $Perlbug::DEBUG; 
				}
			}
		}
	}

	return @ok;
}

sub primary_key {
	my $self = shift;
	
	my $pri = $self->attr('primary_key');

	return $pri;
}

sub key {
	my $self = shift;
	my $in = shift || '';
	
	if ($in =~ /\w+/o) {
		$self->attr({'key', $in}); # explicit	
	}

	my $key = $self->attr('key');

	return $key;
}

sub objectid { my $self = shift; return $self->oid(@_); } # shortcut

sub oid {
	my $self = shift;
	my $in = shift || '';

	if ($self->ok_ids([$in])) { # are appropriate
		$self->attr( { 'objectid', $in } ); # explicit	
		$self->data( { $self->attr('primary_key'), $in } );
	}
	my $oid = $self->attr('objectid');

	return $oid;
}

sub id {
	my $self  = shift;
	my $h_ref = shift; #$o_cgi
	my @ids   = ();

	if (!(ref($h_ref))) {
		$self->error("requires some sort of data ref($h_ref)!");
	} else {
		my $obj = $self->key;
		my @vars = ('id', $obj.'id', $obj.'_id', $obj.'_ids');
		foreach my $var (@vars) {
			if (ref($h_ref) eq 'CGI') {
				push(@ids, $h_ref->param($var)) if $h_ref->param($var);
			} else {
				if (ref($h_ref) eq 'ARRAY') {
					push(@ids, @{$h_ref}) if scalar(@{$h_ref}) >= 1;
				} else {
					if (ref($$h_ref{$var}) eq 'ARRAY') {
						push(@ids, @{$$h_ref{$var}}) if scalar(@{$$h_ref{$var}}) >= 1;
					} else {
						push(@ids, $$h_ref{$var}) if $$h_ref{$var} =~ /.+/o;
					}
				}
			}
		}
		$self->debug(3, "ok id(@ids)from: ".Dumper($h_ref)) if $Perlbug::DEBUG;
	} 

	return @ids;
}

sub ids {
	my $self  = shift;
	my $input = shift || '';
	my $extra = shift || '';
	my $refresh = shift || '';
	my @ids   = ();
	
	my $prime = $self->attr('primary_key');
	my $table = $self->attr('table');
	my $sql   = "SELECT DISTINCT $prime FROM $table ";

	if (ref($input)) {				# OBJECT with ids, etc.
		$sql .= " WHERE $prime = '".$input->oid()."'";		
		$sql .= " AND $extra" if $extra;
	} elsif ($input =~ /\w+/o) { 	# SQL where clause
		$input =~ s/^\s*WHERE\s*//io;	
		$sql  .= " WHERE $input";	
	} 								# ALL
	$sql .= " ORDER BY name " if $self->identifier eq 'name';
	
	@ids = $self->base->get_list($sql, $refresh);
	$self->debug(3, "input($input) extra($extra) -> ids(@ids)") if $Perlbug::DEBUG;

	return @ids;
}

sub names {
	my $self = shift;
	my $input = shift || '';
	my $extra = shift || '';
	my @names = ();
	
	my $ident = $self->identifier;
	if ($self->identifier eq 'name') {
		my $sql = "SELECT DISTINCT name FROM ".$self->attr('table');
		if (ref($input)) {				# OBJECT with ids, etc.
			$sql .= ' WHERE '.$input->attr('primary_key')." = '".$input->oid()."'";		
			$sql .= " AND $extra" if $extra;
		} elsif ($input =~ /\w+/o) { 	# SQL where clause
			$input =~ s/^\s*WHERE\s*//io;	
			$sql  .= " WHERE $input";	
		}
		@names = $self->base->get_list($sql);
	}								# ALL
	$self->debug(3, "input($input) extra($extra) -> names(@names)") if $Perlbug::DEBUG;

	return @names;
}

sub col { 
	my $self = shift;
	my $col  = shift;
	my $input = shift || '';
	my @cols = ();
	
	if ($col !~ /\w+/) {
		$self->error("No column($col) given to retrieve!");
	} else {	
		my $sql = "SELECT DISTINCT $col FROM ".$self->attr('table');
		if (ref($input)) {				# OBJECT with ids, etc.
			$sql .= ' WHERE '.$input->attr('primary_key')." = '".$input->oid()."'";		
		} elsif ($input =~ /\w+/o) { 	# SQL where clause
			$input =~ s/^\s*WHERE\s*//io;	
			$sql  .= " WHERE $input";	
		} 		 						# ALL 
		@cols = $self->base->get_list($sql);
	}	

	$self->debug(3, "col($col), input($input) -> cols(@cols)") if $Perlbug::DEBUG;
	return @cols;
}

sub identifier {
	my $self = shift;
	
	my $ident = (grep(/^name$/i, $self->data_fields)) 
	# my $ident = (map { ($_ =~ /^name$/ ? 1 : 0) } $self->data_fields) 
		? 'name' 
		: $self->attr('primary_key');

	$self->debug(3, "ident($ident)") if $Perlbug::DEBUG;
	return $ident;
}

sub id2name {
	my $self    = shift;
	my $a_input = shift;
	my @output  = ();

	if (!ref($a_input)) {
		$self->debug(0, "no input ids given to convert($a_input)") if $Perlbug::DEBUG;
	} else {
		if ($self->identifier ne 'name') {
			$self->debug(3, "identifier ne 'name'!") if $Perlbug::DEBUG;
		} else {
			my @input = @{$a_input};
			if (scalar(@input) >= 1) {
				my $input = join("', '", @input);
				my $sql = 
					"SELECT DISTINCT name FROM ".$self->attr('table').
					" WHERE ".$self->attr('primary_key')." IN ('$input')";
				@output = $self->base->get_list($sql);
				$self->debug(3, "given(@input) -> sql($sql) -> output(@output)") if $Perlbug::DEBUG;
			}
		}
	}
	$self->debug(2, "given(@{$a_input}) output(@output)") if $Perlbug::DEBUG;

	return @output;
}

sub name2id {
	my $self = shift;
	my $a_input   = shift;
	my @output = ();
	if (!ref($a_input)) {
		$self->error("no input names given to convert($a_input)");
	} else {
		my @input = @{$a_input};
		if (scalar(@input) >= 1) {
			my $input = join("', '", @input);
			my $sql = 
				 "SELECT DISTINCT ".$self->attr('primary_key').
				  " FROM ".$self->attr('table')." WHERE ".$self->identifier." IN ('$input')";
			@output = $self->base->get_list($sql);
			$self->debug(3, "input(@input) -> sql($sql) -> output(@output)") if $Perlbug::DEBUG;
		}
	}
	return @output;
}

sub count {
	my $self 	= shift;
	my $input = shift || '';
	my $extra = shift || '';

	my $i_cnt 	= 0;
	
	my $sql = "SELECT COUNT(".$self->attr('primary_key').") FROM ".$self->attr('table');
	if (ref($input)) {				# OBJECT with ids, etc.
		$sql .= ' WHERE '.$input->attr('primary_key')." = '".$input->oid()."'";		
		$sql .= " AND $extra" if $extra;
	} elsif ($input =~ /\w+/o) { 	# SQL where clause
		$input =~ s/^\s*WHERE\s*//io;	
		$sql  .= " WHERE $input";	
	} 								# ALL

	($i_cnt) = $self->base->get_list($sql);
	$self->debug(3, "input($input) extra($extra) -> i_cnt($i_cnt)") if $Perlbug::DEBUG;

	return $i_cnt;
}

sub trim {
	my $self = shift;
	my $a_in = shift;
	my @trimmed = ();

	if (!ref($a_in) eq 'ARRAY') {
		$self->error("expecting array_ref($a_in) to trim!");
	} else {
		foreach my $arg (@{$a_in}) {
			$arg =~ s/^\s+//o;
			$arg =~ s/\s+$//o;
			push(@trimmed, $arg);
		}
	}

	return @trimmed;
}


sub keys_sorted_by_value {
	my $self = shift;
	my $h_in = shift;

	my @sort = ();

	if (ref($h_in) ne 'HASH') {
		$self->error("expecting hash_ref($h_in) to sort!");
	} else {
		my %in = %{$h_in};
		foreach my $arg (sort { $in{$a} cmp $in{$b} } keys %in) {
			push(@sort, $arg);
		}
	}

	return @sort;
}

# HTTP specific methods
# -----------------------------------------------------------------------------

sub link {
	my $self = shift;
	my $fmt  = shift || $self->base->current('format');
	my $aoid = shift || [];
	my $js   = shift || '';
	my $stat = (ref($aoid) eq 'ARRAY') ? join(', ', @{$aoid}) : '';
	
	my $targ = $self->key;

    my @link = $self->href($targ.'_id', $aoid, ucfirst($targ), $stat, $aoid, $js, $fmt);

	$self->debug(3, "targ($targ) oid($aoid) => link(@link)") if $Perlbug::DEBUG;

	return @link;
}

sub choice {
	my $self = shift;

	my $choice = ($self->attr('prejudicial') == 1) ? 'popup' : 'selector';

	$self->debug(3, "choice($choice)...") if $Perlbug::DEBUG;

	return $self->$choice(@_);
}


sub popup {
	my $self = shift;
	my $name = shift || $self->attr('name');
	my $sel  = shift || ''; # any?
	my $where= shift || ''; # sql
	($sel)   = @{$sel} if ref($sel) eq 'ARRAY';

	my $cgi  = $self->base->cgi;

	my %map = ('' => '',);
	%map = (%map, 'any' => 'any') if $sel eq 'any';
	my $pri = $self->attr('primary_key');
	# my $col = (grep(/^name$/i, $self->data_fields)) ? 'name' : $pri;
	my ($col) = map { ($_ =~ /^name$/o ? $_ : $pri ) } $self->data_fields ? 'name' : $pri;

	my @ids = $self->col("CONCAT($pri, ':', $col)", $where); 
	foreach my $id (@ids) {
		my ($pre, $post) = split(':', $id);
		$map{$pre} = $post;
	}

	my @sorted = $self->keys_sorted_by_value(\%map);

	# my $pointer = 'parent.perlbug.document.forms[0].';
	my $pop = $cgi->popup_menu(
		-'name' 	=> $name, 			# xxx_groupids
		-'values' 	=> \@sorted, # keys %map
		-'onChange'	=> 'pick(this);',
		-'default' 	=> $sel,	
		-'labels' 	=> \%map, 
		-'override' => 1,
		@_,
	);
	
	$self->debug(3, "name($name) selected($sel) pop($pop)") if $Perlbug::DEBUG;

	return $pop;
}

sub selector {
	my $self = shift;
	my $name = shift || $self->attr('name');
	my @selected = @_;

	my $cgi  = $self->base->cgi;
	@selected = @{$selected[0]} if ref($selected[0]) eq 'ARRAY';

	my %map = ();
	my $pri = $self->attr('primary_key');
	# my $col = (grep(/^name$/i, $self->data_fields)) ? 'name' : $pri;
	my ($col) = map { ($_ =~ /^name$/o ? $_ : $pri ) } $self->data_fields ? 'name' : $pri;

	my @ids = $self->col("CONCAT($pri, ':', $col)"); 
	foreach my $id (@ids) {
		my ($pre, $post) = split(':', $id);
		$map{$pre} = $post;
	}

	my @sorted = $self->keys_sorted_by_value(\%map);

	my $sel  = $cgi->scrolling_list(
		-'name' 	=> $name, 			# xxx_groupids
		-'values' 	=> \@sorted, # keys %map
		-'default' 	=> \@selected,	
		-'labels' 	=> \%map, 
		-'multiple' => 'true', 
		-'size' 	=> 3, 
		-'override' => 1,
		-'onChange'	=> 'pick(this);',
		@_,
	).'<br>'.join(', ', map { $map{$_} } @selected);
	
	$self->debug(3, "name($name) selected(@selected) => sel($sel)") if $Perlbug::DEBUG;

	return $sel;
}


sub text_area {
	my $self = shift;
	my $name = shift || $self->attr('name');
	my $val  = shift || '';

	($val)  = @{$val} if ref($val) eq 'ARRAY';
	my $cgi  = $self->base->cgi;

	my $txt  = $cgi->textarea(
		-'name' 	=> $name, 			# xxx_groupids
		-'value' 	=> $val,
		-'override' => 1,
		-'onChange'	=> 'pick(this);',
		-'rows'		=> 3,
		-'cols'		=> 25,
		@_, 							# etc. 
	);

	$self->debug(3, "name($name) val($val) => txta($txt)") if $Perlbug::DEBUG;

	return $txt;
}


sub text_field {
	my $self = shift;
	my $name = shift || $self->attr('name');
	my $val  = shift || '';

	($val)  = @{$val} if ref($val) eq 'ARRAY';
	my $cgi  = $self->base->cgi;

	my $txt  = $cgi->textfield(
		-'name' 	=> $name, 			# xxx_groupids
		-'value' 	=> $val,
		-'override' => 1,
		-'onChange'	=> 'pick(this);',
		-'size'		=> 12,
		-'maxlength'=> 12,
		@_, 							# etc. 
	);

	$self->debug(3, "name($name) val($val) => txtf($txt)") if $Perlbug::DEBUG;

	return $txt;
}

sub htmlify {
	my $self   = shift;
	my $h_data = $self->SUPER::htmlify(@_);

	if (ref($h_data) ne 'HASH') {
		$self->error("requires hashed data ref($h_data)!");
	} else {
		my $obj = $self->key;
		my $oid = $self->oid;
		$$h_data{'select'} = $self->base->cgi->checkbox(
			-'name'		=>"${obj}id", 
			-'checked' 	=> '', 
			-'value'	=> $oid, 
			-'label' 	=> '', 
			-'override' => 1
		);

		my $OPTIONAL = $self->base->help_ref('optional', 'Optional');
		my $TRANSFER = $self->base->help_ref('transfer', 'Transfer');
		my $transfer = '<td>&nbsp;</td><td>&nbsp;</td>';
		if (grep(/^$obj$/, $self->base->objects('mail'))) {
			$transfer = qq|<td><b>$TRANSFER type:</b>&nbsp;</td>|.
				'<td>'.
				$self->object('object')->popup("${oid}_transfer", $obj, 
				"UPPER(type) = 'MAIL' 
								AND name IN('message', 'note', 'patch', 'test') AND NAME != '$obj'", 
				# -'onChange'	=> "pick(this); return newcoms('read');",
			) . '</td>';
		}
		$$h_data{'options'} = qq|
				<table border=0>
						<tr>$transfer</tr>
						<tr>
								<td><b>$OPTIONAL information here:</b>&nbsp;</td>
								<td><input type="text" name="${oid}_opts" value="" size="30" 
								onChange="return pick(this);"></td>
						</tr>
				</table>
				|;
	}

	$self->debug(3, "$h_data => <pre>\n".Dumper($h_data)."</pre>\n") if $Perlbug::DEBUG;

	return $h_data;
}

sub form {
	my $self  = shift;
	my $o_cgi = shift; # ignored
	my $title = shift || 'form';
	my $obj   = $self->key;
	my $cgi   = $self->base->cgi;

	my $form = qq|<table border=1>\n|.
		q|<tr><th>&nbsp;</th><th><h3>|.ucfirst($obj).qq| $title</h3></th></tr>\n|;

	$self->reinit() unless $title =~ /^initial/io;

	my $h_data = $self->_oref('data');

	KEY:
	foreach my $key (sort keys %{$h_data}) {
		my $val = $$h_data{$key} || '';
		unless ($key eq 'userid') {
			if ($key =~ /^(created|modified|${obj}id)$/io) { 
				next KEY if $cgi->param('req') =~ /_initform$/io;
			}
		}
		$form .= qq|<tr><td><b>$key</b></td>|;
		if ($key =~ /body|header/io) {
			$form .= qq|<td><textarea name="$key" rows="3" cols="40">$val</textarea></td>|;
		} else {
			my $size = ($key =~ /(description|email_msgid|subject|(to|source)addr)/io) ? 'size="35"' : '';
			$form .= qq|<td><input type="text" name="$key" value="$val" $size></td>|;
		}
		$form .= qq|</tr>\n|;
	}

	$form .= qq|</table>\n|;

	$self->debug(3, "obj($obj) form($form)") if $Perlbug::DEBUG;

	return $form; 
}


sub search {
	my $self = shift;
	my $o_cgi = shift; # ignored
	my $obj  = $self->key;
	my $cgi  = $self->base->cgi;

	my $form = $self->form($cgi, 'search');

	my $FMT		 = $self->base->help_ref('format', 'Formatter');
	my $SHOWSQL  = $self->base->help_ref('show_sql', 'Show SQL');
	my $RESTRICT = $self->base->help_ref('restrict', 'Restrict returns to');
    my %format   = ( 'h' => 'Html list', 'H' => 'Html block', 'L' => 'Html lean', 'a' => 'Ascii list', 'A' => 'Ascii block', 'l' => 'Ascii lean',); 
	my $format   = $cgi->radio_group(-'name' => 'format',  -values => \%format, -'default' => 'h', -'override' => 1);
    my $sqlshow  = $cgi->radio_group(-'name' => 'sqlshow',	-'values' => ['Yes', 'No'], -'default' => 'No', -'override' => 1);
    my $restrict = $cgi->popup_menu(-'name' => 'trim',      -'values' => ['All', '5', '10', '25', '50', '100'],  -'default' => 10, -'override' => 1);

	$form .= qq|
				<table border=0>
				<tr><td>$FMT:     	</td><td>$format</td></tr> 
				<tr><td>$SHOWSQL: 	</td><td>$sqlshow</td></tr> 
				<tr><td>$RESTRICT:	</td><td>$restrict</td></tr> 
				</table>\n
				<input type=hidden name=req value="${obj}_query">\n
		|;

	$self->debug(3, "obj($obj) form($form)") if $Perlbug::DEBUG;

	return $form;
}

sub initform {
	my $self  = shift;
	my $o_cgi = shift; # ignored
	my $obj   = $self->key;
	my $cgi   = $self->base->cgi;

	$self->reinit;
	$self->data(
		$self->minimal_create_info({})	
	);

	my $form = $self->form($cgi, 'initialisation');

	my $optional = $self->base->help_ref('optional', 'Optional');
	$form .= qq|<hr>
				<b>$optional information here:</b>&nbsp;<input type="text" name="opts" value="" size="30">
		<hr>| if $self->isarel('bug'); # group (users)
	$form .= qq|
				<input type=hidden name=req value="${obj}_create">\n
				<input type=hidden name=format value="H">\n
				|;
	$self->debug(3, "obj($obj) form($form)") if $Perlbug::DEBUG;

	print $form; # <- !!!

	return (); 
}

# =============================================================================

sub x_gen_field_handler { # AUTOLOAD'd
    my $self  = shift;
    my $field = shift;
    # if (!(grep(/^$field$/, $self->data_fields))) { 
	if (map { ($_ =~ /^name$/o ? 1 : 0) } $self->data_fields) { 
		$self->error("can't gen_field_handler($field)!");
    } else {
		my $ref = ref($self);
		$self->debug(3, "setting($ref) field($field) handler...") if $Perlbug::DEBUG;
		my $code = qq|
						package $ref;
						sub $field {
								my \$self = shift;
								my \$val  = shift;
								if (defined(\$val)) {
										\$self->{'_data'}{'$field'} = \$val;
								}
								
								my \$ret = \$self->{'_data'}{'$field'};
								# print "returning $field data(\$ret) setting? val(\$val)\n";

								return \$ret;
						}		
				|;
		my $x = eval { $code }; 
		$self->error("Couldn't eval the($ref) field($field) handler: $@") if ($@);
    }
    $self;
}

{ $^W=0; eval ' 
sub base {
		my $self = shift;

		$o_Perlbug_Base = ref($o_Perlbug_Base) ? $o_Perlbug_Base : Perlbug::Base->new(@_);

		return $o_Perlbug_Base; 
} 
'; }

{ $^W=0; eval ' 
sub db {
		my $self = shift;

		return $self->base->db;
} 
'; }

sub _oref { # unsupported 
	my $self  = shift;
	my $key   = shift;
	my $h_ref = ''; # !

	my @refs = keys %{$self};
	# if (!grep(/^_$key$/, @refs)) { # sneaky :-]
	if (!(map { ($_ =~ /^_$key$/ ? 1 : 0) } @refs)) {
		$self->error("unknown key($key) requested! valid keys(@refs)");	
	} else {
		$h_ref = { %{$self->{"_$key"}} }; # copy
	}

	$self->debug(3, "key($key) => href: ".Dumper($h_ref)) if $Perlbug::DEBUG;
	
	return $h_ref;
}

sub relation_types { my $self = shift; return $self->rel_types(@_); } # wrapper for rel_types()

sub rel_types {
	my $self = shift;
	return $self->attr('types');
}


sub isarel {
	my $self = shift;
	my $rel  = shift;
	my $type = lc(shift) || '';

	# my $isa  = (grep(/^$rel$/, $self->rels($type))) ? 1 : 0;
	my $isa = map { ($_ =~ /^$rel$/ ? 1 : 0) } $self->rels($type);

	return $isa;
}


sub relations { my $self = shift; return $self->rels(@_); } # wrapper for rels()

sub rels { 
	my $self = shift;
	my $type = shift || ''; # float|from|to
	my @rels = ();
	if (defined($type) && $type =~ /\w+/o) {
		@rels = $self->attr($type); 
	} else {	
		@rels = map { $self->attr($_) } $self->rel_types;
	}
	$self->debug(3, "type($type) => rels(@rels)") if $Perlbug::DEBUG;

	return @rels;
}


sub relation { my $self = shift; return $self->rel(@_); } # wrapper for rel()

sub rel {
	my $self = shift;
	my $rel  = shift;
	my $o_rel = undef;
	if (!(defined($rel))) { 
		$self->error("No relation($rel) given to handle");
	} else {
		# if (!(grep(/^$rel$/, $self->relations))) {
		$rel =~ s/^.+?\W(\w+)$/$1/;
		if (!(map { ($_ =~ /^$rel$/ ? 1 : 0) } $self->rels)) { 
			$self->error("inappropriate relation($rel) requested from ".ref($self));
		} else {
			my $type = $self->{'_relation'}{$rel}{'type'};
			$o_rel  = $self->base->object($self->key.'->'.$rel, '', $type);
			my $oid = $self->oid;
			$o_rel->set_source($self->key, $self->oid);
			my $rid = $o_rel->oid;
		}
	}	
	$self->debug(3, "rjsf: Object::relation($rel): ".ref($self)." own_key(".$self->attr('key').") rel($rel) rel_key(".$o_rel->attr('key')." o_rel($o_rel)") if $Perlbug::DEBUG;
	return $o_rel;
}


sub relation_ids { my $self = shift; return $self->rel_ids(@_); } # wrapper for rel_ids()

sub rel_ids { # object 
	my $self = shift;
	my $rel  = shift;
	my $args = shift || '';
	my $refresh = shift || '';
	
	my @ids  = ();
	if (!defined($rel)) { 
		$self->error("Unable to get ids for non-existent ".ref($self)." relation($rel)");
	} else {
		my @rellies = $self->rels;
		# if (!(grep(/^$rel$/, @rellies))) {
		if (!(map { ($_ =~ /^$rel$/ ? 1 : 0) } $self->rels)) { 
			$self->error("inappropriate relation($rel) given for rel_ids from ".ref($self)." object ok(@rellies)");
		} else {
			my $o_rel = $self->relation($rel);
			@ids = $o_rel->ids($self, $args, $refresh);
			$self->debug(3, "rel($rel) args($args) -> ids(".@ids.')') if $Perlbug::DEBUG;
		}
	}	
	return @ids;
}


sub _rel_ids { my $self = shift; return $self->rel(@_, 'refresh'); } # wrapper for rel_ids()


sub relation_names { my $self = shift; return $self->rel_names(@_); } # wrapper for rel_names()

sub rel_names { # object 
	my $self = shift;
	my $rel  = shift;
	my $args = shift || '';

	my @names = ();

	my @ids = $self->rel_ids($rel, $args);
	if (scalar(@ids) >= 1) {
		@names = $self->object($rel)->id2name(\@ids) if @ids;
	}
	
	# $self->debug(3, "rel($rel), args($args) -> ids(@ids), names(@names)") if $Perlbug::DEBUG;
	return @names;
}

sub relate {
	my $self    = shift;
	my $h_ships = shift;
	my @rels    = ();

	if (ref($h_ships) ne 'HASH') {
		$self->debug(0, "requires relationships: ".Dumper($h_ships)) if $Perlbug::DEBUG;
	} else {
		my $oid = $self->oid;
		if ($oid !~ /\w+/) {
			$self->error("$self has no object id($oid) to relate with!");
		} else {
			my $obj = $self->key;
			if ($obj eq 'bug') {
				unless (ref($$h_ships{'status'}{'names'}) eq 'ARRAY') {
					my $i_has_status = $self->rel_ids('status');
					$$h_ships{'status'}{'names'} = ['open'] unless $i_has_status;
				}
			}
			my %track = ();
			$self->debug(1, 'oid: '.$self->oid.' relatable: '.Dumper($h_ships)) if $Perlbug::DEBUG;
			RELATE:
			foreach my $rel ($self->rels) {
				next RELATE unless $rel =~ /\w+/o;
				my $prej  = ($self->object($rel)->attr('prejudicial') == 1) ? 1 : 0;
				my $o_rel = $self->rel($rel);
				my $a_ids = $$h_ships{$rel}{'ids'} || [];
				my $call  = $prej ? 'store' : 'assign';
				if (ref($$h_ships{$rel}{'ids'})) {
					$o_rel->$call($a_ids);
					$track{$rel}{'ids'} = $o_rel->ASSIGNED.' <= ('.join(', ', @{$a_ids}).')';
				} 
				my $a_names = $$h_ships{$rel}{'names'} || [];
				if (ref($$h_ships{$rel}{'names'})) { 
					my $call  = $prej ? '_store' : '_assign';
					$o_rel->$call($a_names);
					$track{$rel}{'names'} = $o_rel->ASSIGNED.' <= ('.join(', ', @{$a_names}).')';
				}
				if (ref($track{$rel})) {
					push(@rels, $rel) if keys %{$track{$rel}} >= 1;
				}	
			}
			$self->debug(1, 'oid('.$self->oid.') related: '.Dumper(\%track)) if $Perlbug::DEBUG;
			$self->rtrack(\%track);
		}
	}

	return @rels;
}

sub appropriate {
	my $self    = shift;
	my $h_ships = shift;
	my @bugids  = ();

	if (ref($h_ships) ne 'HASH') {
		$self->debug(0, "requires relationships: ".Dumper($h_ships)) if $Perlbug::DEBUG;
	} else {
		@bugids   = (ref($$h_ships{'bug'}{'ids'}) eq 'ARRAY') 
			? @{$$h_ships{'bug'}{'ids'}}
			: ();
		if (scalar(@bugids) >= 1) {
			my $o_bug = $self->object('bug');
			foreach my $bugid (@bugids) {
				$o_bug->read($bugid)->relate($h_ships);
			}
		}
	}

	return @bugids;
}

sub read {
	my $self = shift;
	my $oid = shift || $self->oid;

	$self->reinit(''); # always want a fresh one
	if ($self->ok_ids([$oid]) != 1) {
		$self->debug(0, "$self requires a valid id($oid) to read against!") if $Perlbug::DEBUG;
	} else {
		my $pri	  = $self->attr('primary_key');
		my $table = $self->attr('table');
		my $sql = "SELECT * FROM $table WHERE $pri = '$oid'"; # SQL
		my ($h_data) = $self->base->get_data($sql);
		$h_data = '' unless $h_data;
		if (ref($h_data) ne 'HASH') {
			$self->debug(0, "failed to retrieve data($h_data) with $pri = '$oid' in table($table)") if $Perlbug::DEBUG;
		} else {
			$self->debug(2, $self->key." oid($oid)") if $Perlbug::DEBUG;
			# $DB::single=2;
			my $res = $self->data($h_data); 			# set
			my $xoid = $self->oid($oid);				# set
			# $self = $self->object($self->attr('key'), $self); # cache
			$self->READ(1) if $self->exists([$oid]); 	# catchy :)
		}
	}
	# print "Object::read($oid)...<pre>".Dumper($self)."</pre>\n";

	$self;
} 

sub READ {
	my $self = shift;
	my $i_flag = shift || ''; 

	$self->flag({'read', $1}) if $i_flag =~ /^(1|0)$/o;

	$i_flag = $self->flag('read');

	return $i_flag;	
}

sub _read {
	my $self = shift;
	my $name = shift || $self->data('name');

	my ($oid) = $self->name2id([$name]);

	return $self->read($oid);
}

sub column_type {
	my $self = shift;
	my $col  = lc(shift || '');
	my $type = 'VARCHAR';

	if (!($col =~ /^\w+$/o && grep(/^$col$/, keys %{$self->{'_type'}}))) {
		$self->error("can't define type for unrecognised column($col)");
	} else {
		$type = $self->{'_type'}{$col};
	}

	return $type;	
}

sub to_date {
	my $self = shift || '';
	return "'@_'";
}

sub prep {
	my $self = shift;
	my $control = uc(shift || '');
	my $h_data	= shift; 
	my $table 	= $self->attr('table');
	my $sql = '';
	
	if (ref($h_data) ne 'HASH') {
		$self->error("can't prep non-existing data hash ref($h_data)");
	} else {
		my $do = (($control eq 'INSERT') ? 'INSERT INTO' : 'UPDATE');
		my @args = (); 
		foreach my $key (keys %{$h_data}) {
			my $type = $self->column_type($key); # def = (VARCHAR|BLOB)
			my $val = $$h_data{$key};
			$self->debug(3, "key($key) type($type) val(".length($val).")") if $Perlbug::DEBUG;
			# $val =~ s/^\s+//o;		# front
			# $val =~ s/\s+$//o;		# back
			my $data = '';
			if ($type eq 'DATETIME') {
				$data = "$key = SYSDATE()";
				if (!($key =~ /modified/io || ($key =~ /created/io && $control eq 'INSERT'))) {
					$data = "$key = ".$self->to_date($val);
				}
			} elsif ($type eq 'INTEGER')  {
				$data = "$key = '$val'";
			} else { # default and handles all strings, requoting!
				$data = "$key = '".$self->base->db->quote($val)."'";
			}
			unless ($key =~ /^(header|body)$/i) {
				$self->debug(3, "Type($type) key($key) val($val) => data($data)") if $Perlbug::DEBUG;
			}
			push(@args, $data) if $data;
		}
		$sql = "$do $table SET ".join(', ', @args).(' ' x rand(10));
		$self->debug(2, "sql($sql)") if $Perlbug::DEBUG;
	}

	return $sql;
}

sub massage {
	my $self  = shift;
	my $o_cgi = shift;
	my $oid   = shift || '';
	my $obj   = $self->key;
	my %ret   = ();

	if (!(ref($o_cgi))) {
		$self->error("$obj requires cgi obj($o_cgi) to massage!");
	} else {
		$self->debug(2, "given: ".Dumper($o_cgi)) if $Perlbug::DEBUG;
		my $objid = $obj.'id';
		$ret{$objid} = [$self->id($o_cgi)];
		
		foreach my $key ($self->fields, '_opts') {
			my $oid_key = $oid.'_'.$key;
			my $val = $o_cgi->param($key) || $o_cgi->param($oid_key) || '';
			if ($val =~ /(.+)/) {
				if ($key =~ /^(created|modified)$/) {	
					$val = "TO_DATE($val)";
				}
				$ret{$key} = $val unless $ret{$val};
			}
		}
		$self->debug(2, "massaged: ".Dumper(\%ret)) if $Perlbug::DEBUG;

		if ($o_cgi->param('req') =~ /_create$/) {
			%ret = %{$self->minimal_create_info(\%ret)};
		}
	} 

	return \%ret;
}

sub minimal_create_info {
	my $self   = shift;
	my $h_data = shift;
	my %ret    = ();

	if (!(ref($h_data) eq 'HASH')) {
		$self->error("requires hash ref($h_data) minimally!");
	} else {
		$self->debug(2, "mci given: ".Dumper($h_data)) if $Perlbug::DEBUG;
		my $admin = $self->base->isadmin;
		if ($admin !~ /\w+/) {
			$self->error("shouldn't get here?"); # web create only for admins
		} else {
			%ret = %{$h_data};
			my $o_usr = $self->base->object('user')->read($admin);

			my $obj = $self->key;
			my $oid = $obj.'id';
			$ret{$oid} = $self->new_id($self->id($h_data)); # unless $ret{$oid} =~ /\w+/o;

			my $msgid	= $ret{'email_msgid'} 	|| $self->base->get_rand_msgid;
			my $from 	= $ret{'sourceaddr'} 	|| $o_usr->data('address');
			my ($to) 	= $ret{'toaddr'} 		|| $self->base->target('generic'); 
			my $subject	= $ret{'subject'}		|| 'no subject given'; 

			$ret{'header'} = qq|
								From: $from
								To: $to
								Message-ID: $msgid
								Subject: $subject

						|; $ret{'header'} =~ s/^\s+//gmos;
			$ret{'subject'} 	= $subject;
			$ret{'email_msgid'}	= $msgid;
			$ret{'sourceaddr'} 	= $from;
			$ret{'toaddr'}	= $to;
		}
		$self->debug(2, "mci return: ".Dumper(\%ret)) if $Perlbug::DEBUG;
	}

	return \%ret;
}


sub query {
	my $self   = shift;
	my $h_data = shift;
	my $cgi    = $self->base->cgi;
	my @ids    = ();

	if (ref($h_data) ne 'HASH') {
		$self->error("requires data hash ref($h_data) to query!");
	} else {
		my @sql = ();
		foreach my $key (sort $self->fields) {
			my $param = $$h_data{$key} || '';
			if (ref($param) eq 'ARRAY') {
				if (scalar(@{$param}) >= 1) {
					my $params = join("', '", @{$param});
					push(@sql, "$key IN ('$params')")
				}
			} elsif ($param =~ /(.+)/) {
				$param = "'$1'";
				my $cmp = $self->db->comp($param);
				if ($key =~ /^(created|modified)$/) {	
					$key = "TO_DAYS($key)";
					$cmp = '>='; 
					$param = "TO_DAYS($param)"; # nice and open via mysql
					# TO_DAYS(modified) >= TO_DAYS('TO_DATE(2001-01-10)')
				}
				push(@sql, "$key $cmp $param");
			}
		}
		my $sql = join(' AND ', @sql);
		$sql =~ s/\*/\%/g;
		my $sqls = $cgi->param('sqlshow') || '0';
		my $trim = $cgi->param('trim') || '25';
		if ($sqls eq 'Yes') {
			print "SQL(".$self->key."): $sql<hr>";
		}
		@ids = $self->ids($sql);
		$self->debug(1, "sql($sql) trim($trim) ids: ".@ids) if $Perlbug::DEBUG;
		if (!(scalar(@ids) >= 1)) {
			print 'no '.$self->key.' ids found<hr>';
		} else {
			print 'found '.@ids.' '.$self->key." ids with trim factor($trim)<hr>";
			my $o_rng = $self->object('range');
			$o_rng->create({
				'name'		=> $self->key,
				'rangeid'	=> $o_rng->new_id,
				'processid'	=> $$,
				'range'		=> $o_rng->rangeify(\@ids),
				# $o_rng->relation('bug')->assign(\@bids); # ouch!
			});
			$#ids = ($trim - 1) if scalar(@ids) > $trim;
			$self->base->{'_range'} = $o_rng->oid if $o_rng->CREATED; 
		}
	} 

	return @ids;
}

sub create {
	my $self = shift;
	my $h_data = shift || $self->_oref('data');
	my $flag   = shift || ''; # anything
	my $sqlclean = shift || '1';
	my ($table, $pri) = ($self->attr('table'), $self->attr('primary_key'));

	if (!(ref($h_data) eq 'HASH')) {
		$self->error("requires data hash ref($h_data) to store object data!");
	} else {
		my $oid = $$h_data{$pri} || '';
		if ($oid !~ /\w+/) {
			$self->error("requires an objectid($oid) to create record: ".Dumper($h_data));
		} else {
			if ($flag eq '' && $oid ne 'NULL' && $self->exists([$oid])) {	# relations (clunky) ...
				$self->error("can't create already existing $pri($oid) in $table");
			} else {					# INSERT
				$self->data($h_data);
				my $sql = $self->prep('insert', $self->_oref('data'));
				my $sth = $self->base->exec($sql);	# DOIT
				if (!$sth) {
					$self->error("Failed($oid) to create sql($sql)!");
				} else {	
					$oid = $self->insertid($sth, $oid); # 
					$self->debug(2, "sql($sql) -> insertid($oid)") if $Perlbug::DEBUG;
					if (!$oid) {
						$self->error("Failed to fetch new oid($oid) from sql($sql)");
					} else {
						$self->CREATED(1);
						$oid = $self->oid($oid); # if $oid =~ /\w+/ && $oid !~ '0';
						# $self->track($sql." -> oid($oid)"); rjsf !
					}
					$self->base->clean_cache('sql', 'force');
				}
			}
		}
	}

	return $self;
}

sub _create {
	my $self = shift;

	$self->attr('clean_sql', 0);
	$self->create(@_);
	$self->attr('clean_sql', 1);

	return $self;
}


sub CREATED {
	my $self = shift;
	my $i_flag = shift || ''; 

	$self->flag({'created', $1}) if $i_flag =~ /^(1|0)$/o;

	$i_flag = $self->flag('created');

	return $i_flag;	
}



sub store { # by id from hashref
	my $self = shift;
	my $h_data = shift || $self->_oref('data');
	my ($table, $pri) = ($self->attr('table'), $self->attr('primary_key'));

	if (!(ref($h_data) eq 'HASH')) {
		$self->error("requires data hash ref($h_data) to store object data!");
	} else {
		my $oid = $$h_data{$pri} || '';
		my $call = ($self->exists([$oid])) ? 'update' : 'create';
		$self = $self->$call($h_data);
		if (ref($self)) {
			$self->STORED(1);
		}
	}

	return $self;
}


sub STORED {
	my $self = shift;
	my $i_flag = shift || ''; 

	$self->flag({'stored', $1}) if $i_flag =~ /^(1|0)$/o;

	$i_flag = $self->flag('stored');

	return $i_flag;	
}

sub transfer { # webtransfer
	my $self   = shift;
	my $h_data = shift;
	my $oid    = shift;
	my $cgi    = $self->base->cgi;
	
	unless ($self->read($oid)->READ) {
		$self->error("can't read oid($oid) for transfer!");
	} else {
		my $transferid = $cgi->param($oid.'_transfer') || '';
		if ($transferid !~ /\w+/) {
			$self->error("require a transferid($transferid) for target object type!");
		} else { 
			my ($targ) = $self->object('object')->col('name', "objectid = '$transferid'"); 
			my $o_tgt = $self->object($targ);
			my $s_data = $self->_oref('data');
			my $pri = $o_tgt->attr('primary_key'); 
			$$s_data{$pri} = $o_tgt->new_id;
			my $i_created = $o_tgt->create($s_data)->CREATED; 
			if ($i_created != 1) {
				$self->error("failed to transfer oid($oid) data from ".ref($self)." -> to($targ)"); 
			} else {
				my $targoid = $o_tgt->oid;
				$self->debug(0, 'transferred '.ref($self)."($oid) -> target($targ) oid($targoid)") if $Perlbug::DEBUG;
				# my $t_ref = $self->href($targ.'_id', [$targoid], $targoid, 'click ', '', "return go('${targ}_id&${targ}_id=$targoid&commands=write');");
				my $t_ref = $self->href($targ.'_id', [$targoid], $targoid, 'click ', '', "return go('${targ}_id&${targ}_id=$targoid');");
				print "<h3>New $targ: $t_ref</h3>\n";
				my $opts = $cgi->param($oid.'_opts') || $cgi->param('_opts') || '';
				my $pars = join(' ', $opts);
				my %update = $self->base->parse_str($pars);
				# scan subject, etc.?

				my @curr = ();
				REL:
				foreach my $rel ($self->rels) {
					my @extant = $self->rel_ids($rel);
					$self->rel($rel)->delete([$oid]);
					next REL unless grep(/^$rel$/, $o_tgt->rels);
					push(@{$update{$rel}{'ids'}}, join(' ', @extant));
				}
				$o_tgt->relate(\%update);
				my $i_deleted = $self->delete([$self->oid]); # !
			}
		}
	}

	return ();
}

sub webupdate {
	my $self   = shift;
	my $h_data = shift;
	my $oid    = shift;
    my $cgi    = shift || $self->base->cgi();

	if (!(ref($h_data) eq 'HASH')) {
		$self->error("requires data hash ref($h_data) to update ".ref($self)." data via the web!");
	} else {
		if ($self->read($oid)->READ) {
			$self->debug(0, "oid: ".$self->oid) if $Perlbug::DEBUG;
			# my $pri = $self->attr('primary_key'); 
			# $$h_data{$pri} = $oid;
			# my $i_updated = $self->update($h_data)->UPDATED; # called separately
			# if ($i_updated == 1) {
				my $opts = $cgi->param($oid.'_opts') || $cgi->param('_opts') || '';
				my @curr = ();
				REL:
				foreach my $rel ($self->rels) {
					my @idents = ($self->object($rel)->identifier eq 'name') 
						? $self->id2name([$self->rel_ids($rel)]) 
						: $self->rel_ids($rel);
					push(@curr, join(' ', @idents));
					$self->debug(0, ref($self)."($oid) rel($rel) -> idents(@idents)") if $Perlbug::DEBUG;
				}
				my $pars = join(' ', $opts, @curr);
				my %cmds = $self->base->parse_str($pars);
				$self->relate(\%cmds);
			# }
		}
	}
	
	return $oid;
}

sub update {
	my $self = shift;
	my $h_data = shift || $self->_oref('data');
	my ($table, $pri) = ($self->attr('table'), $self->attr('primary_key'));

	my ($msg, $sth, $type) = ('', '', '');
	if (!(ref($h_data) eq 'HASH')) {
		$self->error("requires data hash ref($h_data) to update object data!");
	} else {
		my $oid = $$h_data{$pri} || $self->oid || ''; # rjsf: messy :-(
		if (!($self->exists([$oid]))) {	#
			$self->error("can't update non-existent objectid($oid)!");
		} else { 
			$self->read($oid); 	# first we read it...(don't need to exists above)!
			if (!$self->READ) {
				$self->error("can't update object(".$self->key.") with unreadable id($oid)");
			} else { 			# then we can set the new stuff
				$self->data({ %{$h_data}, $pri, $oid }); 	# set
				my $sql = $self->prep('update', $self->_oref('data'));
				$sql = $sql." WHERE $pri = '$oid'";
				my $sth = $self->base->exec($sql);	# DOIT
				if (!$sth) {
					$self->error("Failed($oid) $type sql($sql)!");
				} else {	
					$self->UPDATED(1);
					# $self->track($sql); # rjsf: too much (remove msgheader/body/entry) 
					$self->base->clean_cache('sql');
				}
			}
		}
	}

	return $self;
}

sub UPDATED {
	my $self = shift;
	my $i_flag = shift || ''; 

	$self->flag({'updated', $1}) if $i_flag =~ /^(1|0)$/o;

	$i_flag = $self->flag('updated');

	return $i_flag;	
}

sub delete {
	my $self = shift;
	my $a_oids = shift || [$self->oid()];
	my ($table, $pri) = ($self->attr('table'), $self->attr('primary_key'));

	my ($msg, $sql, $sth, $type) = ('', '', '', '');
	if (!(ref($a_oids) eq 'ARRAY')) {
		$self->error("requires oid/s array ref($a_oids) to delete object data!");
	} else {
		foreach my $oid (@{$a_oids}) {
			if (!($self->exists([$oid]))) {	# DOIT 
				$self->debug(0, "Can't delete non-existing objectid($oid)!") if $Perlbug::DEBUG;	
			} else { # recursion handled by application (foreach rel)
				my $sql = "DELETE FROM ".$self->attr('table')." WHERE ".$self->primary_key." = '$oid'";
				my $sth = $self->base->exec($sql);	# DOIT
				if (!$sth) {
					$self->error("Delete($oid) failed: sql($sql)!");
				} else {	
					$self->DELETED(1); 
					my $obj = $self->key;
					$self->base->track($obj, $oid, $sql) unless $obj =~ /(log|range)/io;
					$self->base->clean_cache('sql');
				}	
			}	
		}
		# $self->reinit;
	}

	return $self;
}

sub DELETED {
	my $self = shift;
	my $i_flag = shift || ''; 

	$self->flag({'deleted', $1}) if $i_flag =~ /^(1|0)$/o;

	$i_flag = $self->flag('deleted');

	return $i_flag;	
}

sub updatable {
    my $self  = shift;
    my $a_ids = shift || ''; # ignored

	$self->error("requires an array_ref or uids($a_ids) to check!") unless ref($a_ids);

	my @ids = $self->base->isadmin ? @{$a_ids} : ();
	
    return @ids;
}

sub insertid {
	my $self = shift;
	my $sth  = shift;
	my $oid  = shift || '';
	my $newid= '';

	if ($sth) {
		if ($oid =~ /^(\s*|NULL)$/io) {
			$newid = $sth->{'mysql_insertid'};
		} else {
			$newid = $oid;
		}
	}

	$self->debug(1, "inserted($sth) oid($oid) => newid($newid)") if $Perlbug::DEBUG;

	return $newid;
}

sub new_id {
	my $self = shift;

	my $newid = 'NULL'; 
	$self->debug(1, 'new '.ref($self)." objectid($newid)") if $Perlbug::DEBUG;
	
	return $newid;
}

sub error {
	my $self = shift;

	my $hint = '<'.($self->attr('key')).'>';

	return $self->base->error("$hint - @_");
}

sub debug {
	my $self = shift;
	$self->base->debug(@_) if $Perlbug::DEBUG;
}

sub object {
	my $self = shift;
	return $self->base->object(@_);
}

sub format { # return $o_fmt->FORMAT(@_)  
	my $self = shift;
	my $fmt  = shift || $self->base->current('format');
	my $func = shift || 'display';

	if (0) { # too late to turn back now :-]
		$self->refresh_relations; # ek
		return $self->FORMAT($fmt, @_); # Perlbug::FORMAT
	} else {
		return $self->template($fmt);
	}
}

sub template { # return $o_template->merge($self, $fmt);
	my $self   = shift;
	my $fmt    = shift || $self->base->current('format');
	my $h_data = shift;
	my $h_rels = shift;
	my $obj    = $self->key;

	my $o_template = $self->object('template');
	my ($hdr, $str, $ftr) = $o_template->merge($self, $fmt, $h_data, $h_rels);

	my $i_printing = $self->attr({'printed', $self->attr('printed') + 1});
	my $i_rep = my $i_reporig = $o_template->data('repeat') || 0; 
	if ($i_rep > 1) {
		my $i_res = $i_printing % $i_rep;
		$i_rep = 0 unless $i_res == 1;
	}
	$self->debug(2, "i_printing($i_printing) % orig($i_reporig) => rep($i_rep)") if $Perlbug::DEBUG;

	$str = $hdr.$str.$ftr if $i_rep;

	$self->debug(3, "!$i_rep!: fmt($fmt) obj($obj) => ".$str) if $Perlbug::DEBUG;

	return $str;
}


sub diff {
	my $self = shift;
	my $xone = shift;
	my $xtwo = shift;
	my $diff = '';

	unless (defined($xone) and defined($xtwo)) {
		$self->debug(0, "requires one($xone) and two($xtwo) to differentiate") if $Perlbug::DEBUG;
	} else { 
		$xone =~ s/^(\s*\n)+/\n/go;
		$xtwo =~ s/^(\s*\n)+/\n/go;
		my $i_one = my @one = split("\s*\n\s*", $xone);
		my $i_two = my @two = split("\s*\n\s*", $xtwo);

		my ($old, $new) = ('', '');
		my $i_max = (($i_one > $i_two) ? $i_one : $i_two) + 1;
		foreach my $i_num (1..$i_max) {
			my $one = (scalar(@one) >= 1) ? shift(@one) : '';
			my $two = (scalar(@two) >= 1) ? shift(@two) : '';
			my $qtwo = quotemeta($two);
			if ($one =~ /^$qtwo$/) {
				$self->debug(3, "$i_max: \n\tone($one) looks like \n\ttwo($two)") if $Perlbug::DEBUG;
			} else {
				$self->debug(3, "$i_max: \n\tone($one) differs from \n\ttwo($two)") if $Perlbug::DEBUG;
				$old .= "$i_num  $one\n";
				$new .= "$i_num  $two\n";
			}
		}
		$diff = "old: \n$old\nnew: \n$new\n" if $old && $new;
	}
	$self->debug(2, "one($xone) two($xtwo) => diff($diff)") if $Perlbug::DEBUG;

	return $diff;
}

sub rtrack {
	my $self   = shift;
	my $h_data = shift || '';
	my $type   = shift || $self->key;
	my $oid    = shift || $self->oid;
	
	my $indent = $Data::Dumper::Indent;
	$Data::Dumper::Indent=0;
	my $i_tracked = $self->base->track($type, $oid, Dumper($h_data)) 
		unless $type =~ /(log|range)/io; #
	$Data::Dumper::Indent=$indent;

	$self->TRACKED(1) if $i_tracked;

	return $self;
}


sub TRACKED {
	my $self = shift;
	my $i_flag = shift || ''; 

	$self->flag({'tracked', $1}) if $i_flag =~ /^(1|0)$/o;

	$i_flag = $self->flag('tracked');

	return $i_flag;	
}

# =============================================================================

sub AUTOLOAD {
    my $self = shift;
    my $get  = shift || '';	# get || { set => 'this' }
	my $meth = $AutoLoader::AUTOLOAD = $AUTOLOAD;
    return if $meth =~ /::DESTROY$/o;

    $meth =~ s/^(.*):://o;
	my $pkg = ref($self);
	my @ret = ();

    if ($meth !~ /^(attr|data|flag)$/) { # not one of ours :-)
        $self->error("$pkg->$meth($get, @_) called with a duff method($AUTOLOAD)!  Try: 'perldoc $pkg'");
    } else { 
		no strict 'refs';
		*{$AUTOLOAD} = sub {
			my $self = shift;
			my $get  = shift;
			my @ret  = ();

			if (!defined($get)) {
				@ret = keys %{$self->{"_$meth"}}; 						# ref
			} else {
				if (ref($get) ne 'HASH') { 								# get
					@ret = ref($self->{"_$meth"}{$get}) eq 'ARRAY' 
						? @{$self->{"_$meth"}{$get}} 
						:  ($self->{"_$meth"}{$get});
				} else {												# set the hashref
					my $keys = join('|', keys %{$self->{"_$meth"}});	# ref
					SET:
					foreach my $key (keys %{$get}) {
						if ($key =~ /^($keys)$/) {
							$self->{"_$meth"}->{$key} = $$get{$key};	# SET
							push(@ret, $$get{$key});
						} else {
							$self->debug(2, "$pkg has no such $meth key($key) valid($keys)") if $Perlbug::DEBUG;
						}
					}
				}
			}
			return wantarray ? @ret : $ret[0];
		}
    }

	return wantarray ? @ret : $ret[0];
}

1;