The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package UR::Object::Type;
use warnings;
use strict;

require UR;
our $VERSION = "0.45"; # UR $VERSION;

use Sys::Hostname;
use Cwd;
use Scalar::Util qw(blessed);
use Sub::Name;

our %meta_classes;
our $bootstrapping = 1;
our @partially_defined_classes;
our $pwd_at_compile_time = cwd();

# each method which caches data on the class for properties stores its hash key here
# when properties mutate this is cleared
our @cache_keys;

sub property_metas {
    my $self = $_[0];
    my @a = map { $self->property_meta_for_name($_) } $self->all_property_names();    
    return @a;
}

# Some accessor methods drawn from properties need to be overridden.
# Some times because they need to operate during bootstrapping.  Sometimes
# because the method needs some special behavior like sorting or filtering.
# Sometimes to optimize performance or cache data

# This needs to remain overridden to enforce the restriction on callers
sub data_source {
    my $self = shift;
    my $ds = $self->data_source_id(@_);
    
    return undef unless $ds;
    local $@;
    my $obj = eval { UR::DataSource->get($ds) || $ds->get() };

    return $obj;
}

sub ancestry_class_metas {
    #my $rule_template = UR::BoolExpr::Template->resolve(__PACKAGE__,'id');

    # Can't use the speed optimization of getting a template here.  Using the Context to get 
    # objects here causes endless recursion during bootstrapping
    map { __PACKAGE__->get($_) } shift->ancestry_class_names;
    #return map { $UR::Context::current->get_objects_for_class_and_rule(__PACKAGE__, $_) }
    #       map { $rule_template->get_rule_for_values($_) }
    #       shift->ancestry_class_names;

}

our $PROPERTY_META_FOR_NAME_TEMPLATE;
push @cache_keys, '_property_meta_for_name';
sub property_meta_for_name {
    my ($self, $property_name) = @_;

    return unless $property_name;

    if (index($property_name,'.') != -1) {
        my @chain = split(/\./,$property_name);
        my $last_class_meta = $self;
        my $last_class_name = $self->id;
        my @pmeta;
        for my $full_link (@chain) {
            my ($link) = ($full_link =~ /^([^\-\?]+)/);
            my $property_meta = $last_class_meta->property_meta_for_name($link);
            push @pmeta, $property_meta;
            last if $link eq $chain[-1];
            my @joins = UR::Object::Join->resolve_chain($last_class_name, $link);
            return unless @joins;

            $last_class_name = $joins[-1]{foreign_class};
            $last_class_meta = $last_class_name->__meta__;
        }
        return unless (@pmeta and $pmeta[-1]);
        return @pmeta if wantarray;
        return $pmeta[-1];
    }

    my $pos = index($property_name,'-'); 
    if ($pos != -1) {
        $property_name = substr($property_name,0,$pos);
    }

    if (exists($self->{'_property_meta_for_name'}) and $self->{'_property_meta_for_name'}->{$property_name}) {
       return $self->{'_property_meta_for_name'}->{$property_name};
    }
    $PROPERTY_META_FOR_NAME_TEMPLATE ||= UR::BoolExpr::Template->resolve('UR::Object::Property', 'class_name', 'property_name');

    my $property;
    for my $class ($self->class_name, $self->ancestry_class_names) {
        my $rule = $PROPERTY_META_FOR_NAME_TEMPLATE->get_rule_for_values($class, $property_name);
        $property = $UR::Context::current->get_objects_for_class_and_rule('UR::Object::Property', $rule);
        if ($property) {
            return $self->{'_property_meta_for_name'}->{$property_name} = $property;
        }
    }
    return;
}

# A front-end for property_meta_for_name, but
# will translate the generic 'id' property into the class' real ID property,
# if it's not called 'id'
sub _concrete_property_meta_for_class_and_name {
    my($self,$property_name) = @_;

    my @property_metas = $self->property_meta_for_name($property_name);

    for (my $i = 0; $i < @property_metas; $i++) {
        if ($property_metas[$i]->id eq "UR::Object\tid"
            and $property_name !~ /\./) #If we're looking at a foreign object's id, can't replace with our own
        {
            # This is the generic id property.  Remap it to the class' real ID property name
            my @id_properties = $self->id_property_names;
            if (@id_properties == 1 and $id_properties[0] eq 'id') {
                next; # this class doesn't have any other ID properties
            }
            #return map { $self->_concrete_property_meta_for_class_and_name($_) } @id_properties;
            my @remapped = map { $self->_concrete_property_meta_for_class_and_name($_) } @id_properties;
            splice(@property_metas, $i, 1, @remapped);
        }
    }
    return @property_metas;
}



sub _flatten_property_name {
    my ($self, $name) = @_;
    
    my $flattened_name = '';
    my @add_keys;
    my @add_values;

    my @meta = $self->property_meta_for_name($name);
    for my $meta (@meta) {
        my @joins = $meta->_resolve_join_chain();
        for my $join (@joins) {
            if ($flattened_name) {
                $flattened_name .= '.'; 
            }
            $flattened_name .= $join->{source_name_for_foreign};
            if (my $where = $join->{where}) {
                $flattened_name .= '-' . $join->sub_group_label; 
                my $join_class = $join->{foreign_class};
                my $bx2 = UR::BoolExpr->resolve($join_class,@$where);
                my $bx2_flat = $bx2->flatten(); # recurses through this
                my ($bx2_flat_template, @values) = $bx2_flat->template_and_values();
                my @keys = @{ $bx2_flat_template->{_keys} };
                for my $key (@keys) {
                    next if substr($key,0,1) eq '-';
                    my $full_key = $flattened_name . '?.' . $key;
                    push @add_keys, $full_key;
                    push @add_values, shift @values;
                }
                if (@values) {
                    Carp:confess("Unexpected mismatch in count of keys and values!");
                }
            }
        }
    }
    return ($flattened_name, \@add_keys, \@add_values);
};

our $DIRECT_ID_PROPERTY_METAS_TEMPLATE;
sub direct_id_property_metas {
    my $self = _object(shift);
    $DIRECT_ID_PROPERTY_METAS_TEMPLATE ||= UR::BoolExpr::Template->resolve('UR::Object::Property', 'class_name', 'property_name', 'is_id >=');
    my $class_name = $self->class_name;
    my @id_property_objects =
        map { $UR::Context::current->get_objects_for_class_and_rule('UR::Object::Property', $_) }
        map { $DIRECT_ID_PROPERTY_METAS_TEMPLATE->get_rule_for_values($class_name, $_, 0) }
        @{$self->{'id_by'}};

    my $sort_sub = sub ($$) { return $_[0]->is_id cmp $_[1]->is_id };
    @id_property_objects = sort $sort_sub @id_property_objects;
    if (@id_property_objects == 0) {
        @id_property_objects = $self->property_meta_for_name("id");
    }
    return @id_property_objects;
}

sub parent_class_names {
    my $self = shift;   
    return @{ $self->{is} };
}


# If $property_name represents an alias-type property (via => '__self__'),
# then return a string with all the aliases removed
push @cache_keys, '_resolve_property_aliases';
sub resolve_property_aliases {
    my($self,$property_name) = @_;

    return unless $property_name;
    unless ($self->{'_resolve_property_aliases'} && $self->{'_resolve_property_aliases'}->{$property_name}) {
        $self->{'_resolve_property_aliases'} ||= {};

        my @property_metas = $self->property_meta_for_name($property_name);
        my @property_names;
        if (@property_metas) {
            @property_names = map { $_->alias_for } @property_metas;
        } else {
            # there was a problem resolving the chain of properties
            # This happens in the case of an object accessor (is => 'Some::Class') without an id_by
            my @split_names = split(/\./,$property_name);
            my $name_count = @split_names;
            my $prop_meta = $self->property_meta_for_name(shift @split_names);
            return unless $prop_meta;
            my $foreign_class = $prop_meta->data_type && eval { $prop_meta->data_type->__meta__};
            return unless $foreign_class;
            @property_names = ( $prop_meta->alias_for, $foreign_class->resolve_property_aliases(join('.', @split_names)));
            unless (@property_names >= $name_count) {
                Carp::croak("Some parts from property '$property_name' of class ".$self->class_name
                            . " didn't resolve");
            }
        }
        $self->{'_resolve_property_aliases'}->{$property_name} = join('.', @property_names);
    }
    return $self->{'_resolve_property_aliases'}->{$property_name};
}


push @cache_keys, '_id_property_names';
sub id_property_names {
    # FIXME Take a look at id_property_names and all_id_property_names.  
    # They look extremely similar, but tests start dying if you replace one
    # with the other, or remove both and rely on the property's accessor method

    my $self = _object(shift);

    unless ($self->{'_id_property_names'}) {
        my @id_by;
        unless ($self->{id_by} and @id_by = @{ $self->{id_by} }) {
            foreach my $parent ( @{ $self->{'is'} } ) {
                my $parent_class = $parent->class->__meta__;
                next unless $parent_class;
                @id_by = $parent_class->id_property_names;
                last if @id_by;
            }
        }
        $self->{'_id_property_names'} = \@id_by;
    }
    return @{$self->{'_id_property_names'}};
}

push @cache_keys, '_all_id_property_names';
sub all_id_property_names {
    # return shift->id_property_names(@_); This makes URT/t/99_transaction.t fail
    my $self = shift;
    unless ($self->{_all_id_property_names}) {
        my ($tmp,$last) = ('','');
        $self->{_all_id_property_names} = [
            grep { $tmp = $last; $last = $_; $tmp ne $_ }
            sort 
            map { @{ $_->{id_by} } } 
            map { __PACKAGE__->get($_) }
            ($self->class_name, $self->ancestry_class_names)
        ];
    }
    return @{ $self->{_all_id_property_names} };
}

sub direct_id_column_names {
    my $self = _object(shift);
    my @id_column_names =
        map { $_->column_name }
        $self->direct_id_property_metas;
    return @id_column_names;
}


sub ancestry_table_names {
    my $self = _object(shift);
    my @inherited_table_names =
        grep { defined($_) }
        map { $_->table_name }
        $self->ancestry_class_metas;
    return @inherited_table_names;
}

sub all_table_names {
    my $self = _object(shift);
    my @table_names =
        grep { defined($_) }
        ( $self->table_name, $self->ancestry_table_names );
    return @table_names;
}

sub first_table_name {
    my $self = _object(shift);
    if ($self->{_first_table_name}) {
        return $self->{first_table_name};
    }

    my @classes = ($self);
    while(@classes) {
        my $co = shift @classes;
        if (my $table_name = $co->table_name) {
            $self->{first_table_name} = $table_name;
            return $table_name;
        }
        my @parents = map { $_->__meta__ } @{$co->{'is'}};
        push @classes, @parents;
    }
    return;
}
    

sub ancestry_class_names {
    my $self = shift;
    
    if ($self->{_ordered_inherited_class_names}) {
        return @{ $self->{_ordered_inherited_class_names} };
    }
    
    my $ordered_inherited_class_names = $self->{_ordered_inherited_class_names} = [ @{ $self->{is} } ];    
    my @unchecked = @$ordered_inherited_class_names;
    my %seen = ( $self->{class_name} => 1 );
    while (my $ancestor_class_name = shift @unchecked) {
        next if $seen{$ancestor_class_name};
        $seen{$ancestor_class_name} = 1;
        my $class_meta = $ancestor_class_name->__meta__;
        Carp::confess("Can't find meta for $ancestor_class_name!") unless $class_meta;
        next unless $class_meta->{is};
        push @$ordered_inherited_class_names, @{ $class_meta->{is} };
        unshift @unchecked, $_ for reverse @{ $class_meta->{is} };
    }    
    return @$ordered_inherited_class_names;
}

push @cache_keys, '_all_property_names';
sub all_property_names {
    my $self = shift;
    
    if ($self->{_all_property_names}) {
        return @{ $self->{_all_property_names} };
    }
 
    my %seen = ();   
    my $all_property_names = $self->{_all_property_names} = [];
    for my $class_name ($self->class_name, $self->ancestry_class_names) {
        next if $class_name eq 'UR::Object';
        my $class_meta = UR::Object::Type->get($class_name);
        if (my $has = $class_meta->{has}) {
            push @$all_property_names, 
                grep { 
                    not exists $has->{$_}{id_by}
                }
                grep { !exists $seen{$_} } 
                sort keys %$has;
            foreach (@$all_property_names) {
                $seen{$_} = 1;
            }
        }
    }
    return @$all_property_names;
}


########################################################################
# End of overridden property methods
########################################################################

sub _resolve_meta_class_name_for_class_name {
    my $class = shift;
    my $class_name = shift;
    #if ($class_name->isa("UR::Object::Type") or $meta_classes{$class_name} or $class_name =~ '::Type') {
    if ($meta_classes{$class_name} or $class_name =~ '::Type') {
        return "UR::Object::Type"
    }
    else {
        return $class_name . "::Type";
    }    
}

sub _resolve_meta_class_name {
    my $class = shift;
    my ($rule,%extra) = UR::BoolExpr->resolve_normalized($class, @_);
    my %params = $rule->params_list;
    my $class_name = $params{class_name};
    return unless $class_name;
    return $class->_resolve_meta_class_name_for_class_name($class_name);
}


# This method can go away when we have the is_cached meta-property
sub first_sub_classification_method_name {
    my $self = shift;
    
    # This may be one of many things which class meta-data should "inherit" from classes which 
    # its instances inherit from.  This value is set to the value found on the most concrete class
    # in the inheritance tree.

    return $self->{___first_sub_classification_method_name} if exists $self->{___first_sub_classification_method_name};
    
    $self->{___first_sub_classification_method_name} = $self->sub_classification_method_name;
    unless ($self->{___first_sub_classification_method_name}) {
        for my $parent_class ($self->ancestry_class_metas) {
            last if ($self->{___first_sub_classification_method_name} = $parent_class->sub_classification_method_name);
        }
    }
    
    return $self->{___first_sub_classification_method_name};
}


# Another thing that is "inherited" from parent class metas
sub subclassify_by {
    my $self = shift;

    return $self->{'__subclassify_by'} if exists $self->{'__subclassify_by'};

    $self->{'__subclassify_by'} = $self->__subclassify_by;
    unless ($self->{'__subclassify_by'}) {
        for my $parent_class ($self->ancestry_class_metas) {
            last if ($self->{'__subclassify_by'} = $parent_class->__subclassify_by);
        }
    }

    return $self->{'__subclassify_by'};
}

sub resolve_composite_id_from_ordered_values {    
    my $self = shift;
    my $resolver = $self->get_composite_id_resolver;
    return $resolver->(@_);
}

sub resolve_ordered_values_from_composite_id {
    my $self = shift;
    my $decomposer = $self->get_composite_id_decomposer;
    return $decomposer->(@_);
}

sub get_composite_id_decomposer {
    my $self = shift;
    my $decomposer;
    unless ($decomposer = $self->{get_composite_id_decomposer}) {
        my @id_property_names = $self->id_property_names;        
        if (@id_property_names == 1) {
            $decomposer = sub { $_[0] };
        }
        else {
            my $separator = $self->_resolve_composite_id_separator;
            $decomposer = sub { 
                if (ref($_[0])) {
                    # ID is an arrayref, or we'll throw an exception.                    
                    my $id = $_[0];
                    my $underlying_id_count = scalar(@$id);
                    
                    # Handle each underlying ID, turning each into an arrayref divided by property value.
                    my @decomposed_ids;
                    for my $underlying_id (@$id) {
                        push @decomposed_ids, [map { $_ eq '' ? undef : $_ } split($separator,$underlying_id)];
                    }
            
                    # Count the property values.
                    my $underlying_property_count = scalar(@{$decomposed_ids[0]}) if @decomposed_ids;
                    $underlying_property_count ||= 0;
            
                    # Make a list of property values, but each value will be an
                    # arrayref of a set of values instead of a single value.
                    my @property_values;
                    for (my $n = 0; $n < $underlying_property_count; $n++) {
                        $property_values[$n] = [ map { $_->[$n] } @decomposed_ids ];
                    }
                    return @property_values;                
                }
                else {
                    # Regular scalar ID.
                    no warnings 'uninitialized';  # $_[0] can be undef in some cases...
                    return split($separator,$_[0])  
                }
            };
        }
        Sub::Name::subname('UR::Object::Type::InternalAPI::composite_id_decomposer(closure)',$decomposer);
        $self->{get_composite_id_decomposer} = $decomposer;
    }
    return $decomposer;
}

sub _resolve_composite_id_separator {   
    # TODO: make the class pull this from its parent at creation time
    # and only have it dump it if it differs from its parent
    my $self = shift;
    my $separator = "\t";
    for my $class_meta ($self, $self->ancestry_class_metas) {
        if ($class_meta->composite_id_separator) {
            $separator = $class_meta->composite_id_separator;
            last;
        }
    }
    return $separator; 
}

sub get_composite_id_resolver {
    my $self = shift;    
    my $resolver;
    unless($resolver = $self->{get_composite_id_resolver}) {
        my @id_property_names = $self->id_property_names;        
        if (@id_property_names == 1) {
            $resolver = sub { $_[0] };
        }
        else {
            my $separator = $self->_resolve_composite_id_separator;
            $resolver = sub { 
                if (ref($_[0]) eq 'ARRAY') {                
                    # Determine how big the arrayrefs are.
                    my $underlying_id_count = scalar(@{$_[0]});
                    
                    # We presume that, if one value is an arrayref, the others are also,
                    # and are of equal length.
                    my @id;
                    for (my $id_num = 0; $id_num < $underlying_id_count; $id_num++) {
                        # One value per id_property on the class.
                        # Each value is an arrayref in this case.
                        for my $value (@_) {
                            no warnings 'uninitialized';  # Some values in the list might be undef
                            $id[$id_num] .= $separator if $id[$id_num];
                            $id[$id_num] .= $value->[$id_num];
                        }
                    }
                    return \@id;           
                }
                else {
                    no warnings 'uninitialized';  # Some values in the list might be undef
                    return join($separator,@_) 
                }
            };
        }
        Sub::Name::subname('UR::Object::Type::InternalAPI::composite_id_resolver(closure)',$resolver);
        $self->{get_composite_id_resolver} = $resolver;
    }    
    return $resolver;
}

# UNUSED, BUT BETTER FOR MULTI-COLUMN FK
sub composite_id_list_scalar_mix {
    # This is like the above, but handles the case of arrayrefs
    # mixing with scalar values in a multi-property id.

    my ($self, @values) = @_;

    my @id_sets;
    for my $value (@values) {
        if (@id_sets == 0) {
            if (not ref $value) {
                @id_sets = ($value);
            }
            else {
                @id_sets = @$value;
            }
        }
        else {
            if (not ref $value) {
                for my $id_set (@id_sets) {
                    $id_set .= "\t" . $value;
                }
            }
            else {
                for my $new_id (@$value) {
                    for my $id_set (@id_sets) {
                        $id_set .= "\t" . $value;
                    }
                }
            }
        }
    }

    if (@id_sets == 1) {
        return $id_sets[0];
    }
    else {
        return \@id_sets;
    }
}


sub id_property_sorter {
    # Return a closure that sort can use to sort objects by all their ID properties
    # This should be the same order that an SQL query with 'order by ...' would return them
    my $self = shift;
    return $self->{'_id_property_sorter'} ||= $self->sorter(); 
}

sub sorter {
    my ($self,@properties) = @_;
    push @properties, $self->id_property_names;
    my $key = join("__",@properties);
    my $sorter = $self->{_sorter}{$key};
    unless ($sorter) {
        my @is_numeric;
        my @is_descending;
        for my $property (@properties) {
            if ($property =~ m/^(-|\+)(.*)$/) {
                push @is_descending, $1 eq '-';
                $property = $2;  # yes, we're manipulating the original list element
            } else {
                push @is_descending, 0;
            }

            my ($pmeta,@extra) = $self->_concrete_property_meta_for_class_and_name($property);
            if(@extra) {
                # maybe a composite property (typically ID), or a chained property (prop.other_prop)
                $pmeta = $self->property_meta_for_name($property);
            }

            if ($pmeta) {
                my $is_numeric = $pmeta->is_numeric;
                push @is_numeric, $is_numeric;
            }
            elsif ($UR::initialized) {
                Carp::cluck("Failed to find property meta for $property on $self?  Cannot produce a sorter for @properties");
                push @is_numeric, 0;
            }
            else {
                push @is_numeric, 0;
            }
        }

        no warnings 'uninitialized';
        $sorter = $self->{_sorter}{$key} ||= sub($$) {

            for (my $n = 0; $n < @properties; $n++) {
                my $property = $properties[$n];
                my @property_string = split('\.',$property);

                my($first,$second) = $is_descending[$n] ? ($_[1], $_[0]) : ($_[0], $_[1]);
                for my $current (@property_string) {
                    $first = $first->$current;
                    $second = $second->$current;
                    if (!defined($second)) {
                        return -1;
                    } elsif (!defined($first)) {
                        return 1;
                    }
                }

                my $cmp = $is_numeric[$n] ? $first <=> $second : $first cmp $second;
                return $cmp if $cmp;
            }
            return 0;
        };
    }
    Sub::Name::subname("UR::Object::Type::sorter__" . $self->class_name . '__' . $key, $sorter);
    return $sorter;
}

sub is_meta {
    my $self = shift;
    my $class_name = $self->class_name;
    return grep { $_ ne 'UR::Object' and $class_name->isa($_) } keys %meta_classes;
}

sub is_meta_meta {
    my $self = shift;
    my $class_name = $self->class_name;
    return 1 if $meta_classes{$class_name};
    return;
}

# Things that can't safely be removed from the object cache.
our %uncachable_types = ( ( map { $_ => 0 } keys %UR::Object::Type::meta_classes),   # meta-classes are locked in the cache...
                          'UR::Object' => 1,        # .. except for UR::Object
                          'UR::Object::Ghost' => 0,
                          'UR::DataSource' => 0,
                          'UR::Context' => 0,
                          'UR::Object::Index' => 0,
                        );
sub is_uncachable {
    my $self = shift;

    my $class_name = $self->class_name;

    if (@_) {
        # setting the is_uncachable value
        return $uncachable_types{$class_name} = shift;
    }

    unless (exists $uncachable_types{$class_name}) {
        my $is_uncachable = 1;
        foreach my $type ( keys %uncachable_types ) {
            if ($class_name->isa($type) and ! $uncachable_types{$type}) {
                $is_uncachable = 0;
                last;
            }
        }
        $uncachable_types{$class_name} = $is_uncachable;
        unless (exists $uncachable_types{$class_name}) {
            die "Couldn't determine is_uncachable() for $class_name";
        }
    }
    return $uncachable_types{$class_name};
}


# Mechanisms for generating object IDs when none were specified at
# creation time

sub autogenerate_new_object_id_uuid {
    require Data::UUID;
    my $uuid = Data::UUID->new->create_hex();
    $uuid =~ s/^0x//;
    return $uuid;
}

our $autogenerate_id_base_format = join(" ",Sys::Hostname::hostname(), "%s", time); # the %s gets $$ when needed
our $autogenerate_id_iter = 10000;
sub autogenerate_new_object_id_urinternal {
    my($self, $rule) = @_;

    my @id_property_names = $self->id_property_names;
    if (@id_property_names > 1) {
        # we really could, but it seems like if you 
        # asked to do it, it _has_ to be a mistake.  If there's a legitimate
        # reason, this check should be removed
        $self->error_message("Can't autogenerate ID property values for multiple ID property class " . $self->class_name);
        return;
    }
    return sprintf($autogenerate_id_base_format, $$) . " " . (++$autogenerate_id_iter);
}

sub autogenerate_new_object_id_datasource {
    my($self,$rule) = @_;

    my ($data_source) = $UR::Context::current->resolve_data_sources_for_class_meta_and_rule($self);
    if ($data_source) {
        return $data_source->autogenerate_new_object_id_for_class_name_and_rule(
            $self->class_name,
            $rule
        );
    } else {
        Carp::croak("Class ".$self->class." has id_generator '-datasource', but the class has no data source to delegate to");
    }
}


# Support the autogeneration of unique IDs for objects which require them.
sub autogenerate_new_object_id {
    my $self = _object($_[0]);
    #my $rule = shift;

    unless ($self->{'_resolved_id_generator'}) {
        my $id_generator = $self->id_generator;

        if (ref($id_generator) eq 'CODE') {
            $self->{'_resolved_id_generator'} = $id_generator;

        } elsif ($id_generator and $id_generator =~ m/^\-(\S+)/) {
            my $id_method = 'autogenerate_new_object_id_' . $1;
            my $subref = $self->can($id_method);
            unless ($subref) {
                Carp::croak("'$id_generator' is an invalid id_generator for class "
                            . $self->class_name
                            . ": Can't locate object method '$id_method' via package ".ref($self));
            }
            $self->{'_resolved_id_generator'} = $subref;

        } else {
            # delegate to the data source
            my ($data_source) = $UR::Context::current->resolve_data_sources_for_class_meta_and_rule($self);
            if ($data_source) {
                $self->{'_resolved_id_generator'} = sub {
                    $data_source->autogenerate_new_object_id_for_class_name_and_rule(
                        shift->class_name,
                        shift
                    )
                };
            }
        }
    }
    goto $self->{'_resolved_id_generator'};
}

# from ::Object->generate_support_class
our %support_class_suffixes = map { $_ => 1 } qw/Set View Viewer Ghost Iterator Value/;
sub generate_support_class_for_extension {
    my $self = shift;
    my $extension_for_support_class = shift;
    my $subject_class_name = $self->class_name;

    unless ($subject_class_name) {
        Carp::confess("No subject class name for $self?"); 
    }

    return unless defined $extension_for_support_class;

    if ($subject_class_name eq "UR::Object") {
        # Carp::cluck("can't generate $extension_for_support_class for UR::Object!\n");
        # NOTE: we hit this a bunch of times when "getting" meta-data objects during boostrap.
        return;
    }

    unless ($support_class_suffixes{$extension_for_support_class})
    {
        #$self->debug_message("Cannot generate a class with extension $extension_for_support_class.");
        return;
    }

    my $subject_class_obj = UR::Object::Type->get(class_name => $subject_class_name);
    unless ($subject_class_obj)  {
        $self->debug_message("Cannot autogenerate $extension_for_support_class because $subject_class_name does not exist.");
        return;
    }

    my $new_class_name = $subject_class_name . "::" . $extension_for_support_class;
    my $class_obj;
    if ($class_obj = UR::Object::Type->is_loaded($new_class_name)) {
        # getting the subject class autogenerated the support class automatically
        # shortcut out
        return $class_obj;
    }

    no strict 'refs';
    my @subject_parent_class_names = @{ $subject_class_name . "::ISA" };
    my @parent_class_names =
        grep { UR::Object::Type->get(class_name => $_) }
        map { $_ . "::" . $extension_for_support_class }
        grep { $_->isa("UR::Object") }
        grep { $_ !~ /^UR::/  or $extension_for_support_class eq "Ghost" }
        @subject_parent_class_names;
    use strict 'refs';

    unless (@parent_class_names) {
        if (UR::Object::Type->get(class_name => ("UR::Object::" . $extension_for_support_class))) {
            @parent_class_names = "UR::Object::" . $extension_for_support_class;
        }
    }

    unless (@parent_class_names) {
        #print Carp::longmess();
        #$self->error_message("Cannot autogenerate $extension_for_support_class for $subject_class_name because parent classes (@subject_parent_class_names) do not have classes with that extension.");
        return;
    }
    
    my @id_property_names = $subject_class_obj->id_property_names;
    my %id_property_names = map { $_ => 1 } @id_property_names;
    
    if ($extension_for_support_class eq 'Ghost') {
        my $subject_class_metaobj = UR::Object::Type->get($self->meta_class_name);  # Class object for the subject_class
        my %class_params = map { $_ => $subject_class_obj->$_ }
                           grep { my $p = $subject_class_metaobj->property_meta_for_name($_)
                                    || Carp::croak("Can't no metadata for property '$_' of class ".$self->meta_class_name);
                                  ! $p->is_delegated and ! $p->is_calculated }
                           $subject_class_obj->__meta__->all_property_names;
        delete $class_params{generated};
        delete $class_params{meta_class_name};
        delete $class_params{subclassify_by};
        delete $class_params{sub_classification_meta_class_name};
        delete $class_params{id_generator};
        delete $class_params{id};
        delete $class_params{is};
        delete $class_params{roles};

        my $attributes_have = UR::Util::deep_copy($subject_class_obj->{attributes_have});
        my $class_props = UR::Util::deep_copy($subject_class_obj->{has});    
        for (values %$class_props) {
            delete $_->{class_name};
            delete $_->{property_name};
        }
        
        %class_params = (
                %class_params,
                class_name => $new_class_name,
                is => \@parent_class_names, 
                is_abstract => 0,
                has => [%$class_props],
                attributes_have => $attributes_have,
                id_properties => \@id_property_names,
        );
        $class_obj = UR::Object::Type->define(%class_params);
    }
    else {
        $class_obj = UR::Object::Type->define(
            class_name => $subject_class_name . "::" . $extension_for_support_class,
            is => \@parent_class_names,
        );
    }
    return $class_obj;
}

sub has_table {
    my $self = shift;
    if ($bootstrapping) {
        return 0;
    }
    return 1 if $self->table_name;
    # FIXME - shouldn't this call inheritance() instead of parent_classes()?
    my @parent_classes = $self->parent_classes;
    for my $class_name (@parent_classes) {
        next if $class_name eq "UR::Object";
        my $class_obj = UR::Object::Type->get(class_name => $class_name);
        if ($class_obj->has_direct_table) {
            return 1;
        }
    }
    return;
}

sub has_direct_table {
    my $self = shift;
    return 1 if $self->table_name;

    if ($self->data_source_id and $self->data_source_id->isa('UR::DataSource::Default')) {
        my $load_function_name = join('::', $self->class_name, '__load__');
        return 1 if exists &$load_function_name;
    }
    return;
}

sub most_specific_subclass_with_table {
    my $self = shift;

    return $self->class_name if $self->table_name;

    foreach my $class_name ( $self->class_name->inheritance ) {
        my $class_obj = UR::Object::Type->get(class_name => $class_name);
        return $class_name if ($class_obj and $class_obj->has_direct_table);
    }
    return;
}

sub most_general_subclass_with_table {
    my $self = shift;

    my @subclass_list = reverse ( $self->class_name, $self->class_name->inheritance );
    foreach my $class_name ( $self->inheritance ) {
        my $class_obj = UR::Object::Type->get(class_name => $class_name);
        return $class_name if ($class_obj && $class_obj->has_direct_table);
    }
    return;
}

    

sub _load {
    my $class = shift;
    my $rule = shift;

    $rule = $rule->normalize;
    my $params = $rule->legacy_params_hash;

    # While core entity classes are actually loaded,
    # support classes dynamically generate for them as needed.
    # Examples are Acme::Employee::View::emp_id, and Acme::Equipment::Ghost

    # Try to parse the class name.
    my $class_name = $params->{class_name};

    # See if the class autogenerates from another class.
    # i.e.: Acme::Foo::Bar might be generated by Acme::Foo
    unless ($class_name) {
        my $namespace = $params->{namespace};
        if (my $data_source = $params->{data_source_id}) {
            $namespace = $data_source->get_namespace;
        }
        if ($namespace) {
            # FIXME This chunk seems to be getting called each time there's a new table/class
            #Carp::cluck("Getting all classes for namespace $namespace from the filesystem...");
            my @classes = $namespace->get_material_classes;
            return $class->is_loaded($params);
        }
        Carp::confess("Non-class_name used to find a class object: "
                    . join(', ', map { "$_ => " . (defined $params->{$_} ? "'" . $params->{$_} . "'" : 'undef') } keys %$params));
    }

    # Besides the common case of asking for a class by its name, the next most
    # common thing is asking for multiple classes by their names.  Rather than doing the
    # hard work of doing it "right" right here, just recursively call myself with each
    # item in that list
    if (ref $class_name eq 'ARRAY') {
        # FIXME is there a more efficient way to add/remove class_name from the rule?
        my $rule_without_class_name = $rule->remove_filter('class_name');
        $rule_without_class_name = $rule_without_class_name->remove_filter('id');  # id is a synonym for class_name
        my @objs = map { $class->_load($rule_without_class_name->add_filter(class_name => $_)) } @$class_name;
        return $class->context_return(@objs);
    }
        
    # If the class is loaded, we're done.
    # This is an un-documented unique constraint right now.
    my $class_obj = $class->is_loaded(class_name => $class_name);
    return $class_obj if $class_obj;

    # Handle deleted classes.
    # This is written in non-oo notation for bootstrapping.
    no warnings;
    if (
        $class_name ne "UR::Object::Type::Ghost"
        and
        UR::Object::Type::Ghost->can("class")
        and
        $UR::Context::current->get_objects_for_class_and_rule("UR::Object::Type::Ghost",$rule,0)
    ) {
        return;
    }

    # Check the filesystem.  The file may create its metadata object.
    my $exception = do {
        local $@;
        eval "use $class_name";
        $@;
    };
    unless ($exception) {
        # If the above module was loaded, and is an UR::Object,
        # this will find the object.  If not, it will return nothing.
        $class_obj = $UR::Context::current->get_objects_for_class_and_rule($class,$rule,0);
        return $class_obj if $class_obj;
    }
    if ($exception) {
        # We need to handle $@ here otherwise we'll see
        # "Can't locate UR/Object/Type/Ghost.pm in @INC" error.
        # We want to fall through "in the right circumstances".
        (my $module_path = $class_name . '.pm') =~ s/::/\//g;
        unless ($exception =~ /Can't locate $module_path in \@INC/) {
            die "Error while autoloading with 'use $class_name': $exception";
        }
    }

    # Parse the specified class name to check for a suffix.
    my ($prefix, $base, $suffix) = ($class_name =~ /^([^\:]+)::(.*)::([^:]+)/);

    my @parts;
    ($prefix, @parts) = split(/::/,$class_name);

    for (my $suffix_pos = $#parts; $suffix_pos >= 0; $suffix_pos--)
    {
        $class_obj = $UR::Context::current->get_objects_for_class_and_rule($class,$rule,0);
        if ($class_obj) {
            # the class was somehow generated while we were checking other classes for it and failing.
            # this can happen b/c some class with a name which is a subset of the one we're looking
            # for might "use" the one we want.
            return $class_obj if $class_obj;
        } 

        my $base   = join("::", @parts[0 .. $suffix_pos-1]);
        my $suffix = join("::", @parts[$suffix_pos..$#parts]);

        # See if a class exists for the same name w/o the suffix.
        # This may cause this function to be called recursively for
        # classes like Acme::Equipment::Set::View::upc_code,
        # which would fire recursively for three extensions of
        # Acme::Equipment.
        my $full_base_class_name = $prefix . ($base ? "::" . $base : "");
        my $base_class_obj;
        my $exception = do {
            local $@;
            $base_class_obj = eval { $full_base_class_name->__meta__ };
            $@;
        };
        if ($exception && $exception =~ m/^Error while autoloading/) {
            die $exception;
        }

        if ($base_class_obj)
        {
            # If so, that class may be able to generate a support
            # class.
            $class_obj = $full_base_class_name->__extend_namespace__($suffix);
            if ($class_obj)
            {
                # Autogeneration worked.
                # We still defer to is_loaded, since other parameters
                # may prevent the newly "loaded" class from being
                # returned.                
                return $UR::Context::current->get_objects_for_class_and_rule($class,$rule,0)
            }
        }
    }

    # If we fall-through to this point, no class was found and no module.
    return;
}


sub use_module_with_namespace_constraints {
    use strict;
    use warnings;

    my $self = shift;
    my $target_class = shift;

    # If you do "use Acme; $o = Acme::Rocket->new();", and Perl finds Acme.pm
    # at "/foo/bar/Acme.pm", Acme::Rocket must be under /foo/bar/Acme/
    # in order to be dynamically loaded.

    my @words = split("::",$target_class);
    my $path;
    while (@words > 1) {
        my $namespace_name = join("::",@words[0..$#words-1]);
        my $namespace_expected_module = join("/",@words[0..$#words-1]) . ".pm";


        if ($path = $INC{$namespace_expected_module}) {
            #print "got mod $namespace_expected_module at $path for $target_class\n";
            $path =~ s/\/*$namespace_expected_module//g;
        }
        else {
            my $namespace_obj =  UR::Object::Type->is_loaded(class_name => $namespace_name);
            if ($namespace_obj) {
                eval { $path = $namespace_obj->module_directory };
                if ($@) {
                    # non-module class
                    # don't auto-use, but don't make a lot of noise about it either
                }
            }
        }    
        last if $path;
        pop @words;
    }

    unless ($path) {
        #Carp::cluck("No module_directory found for namespace $namespace_name."
        #    . "  Cannot dynamically load $target_class.");
        return;
    }


    $self->_use_safe($target_class,$path);
    my $meta = UR::Object::Type->is_loaded(class_name => $target_class);
    if ($meta) {
        return $meta;
    }
    else {
        return;
    }
}

sub _use_safe {
    use strict;
    use warnings;

    my ($self, $target_class, $expected_directory) = @_;

    # TODO: use some smart module to determine whether the path is
    # relative on the current system.
    if (defined($expected_directory) and $expected_directory !~ /^[\/\\]/) {
        $expected_directory = $pwd_at_compile_time . "/" . $expected_directory;
    }

    my $class_path = $target_class . ".pm";
    $class_path =~ s/\:\:/\//g;

    my @INC_COPY = @INC;
    if ($expected_directory) {
        unshift @INC, $expected_directory;
    }
    my $found = "";
    for my $dir (@INC) {
        if ($dir and (-e $dir . "/" . $class_path)) {
            $found = $dir;
            last;
        }
    }

    if (!$found) {
        # not found
        @INC = @INC_COPY;
        return;
    }

    if ($expected_directory and $expected_directory ne $found) {
        # not found in the specified location
        @INC = @INC_COPY;
        return;
    }

    do {
        local $SIG{__DIE__};
        local $SIG{__WARN__};
        eval "use $target_class";
    };

    # FIXME - if the use above failed because of a compilation error in the module we're trying to
    # load, then the error message below just tells the user that "Compilation failed in require"
    # and isn't propogating the error message about what caused the compile to fail
    if ($@) {
        #local $SIG{__DIE__};

        @INC = @INC_COPY;
        die ("ERROR DYNAMICALLY LOADING CLASS $target_class\n$@");
    }

    for (0..$#INC) {
        if ($INC[$_] eq $expected_directory) {
            splice @INC, $_, 1;
            last;
        }
    }

    return 1;
}


# sub _object
# This is used to make sure that methods are called
# as object methods and not class methods.
# The typical case that's important is when something
# like UR::Object::Type->method(...) is called.
# If an object is expected in a method and it gets
# a class instead, well, unpredictable things can
# happen.
#
# For many methods on UR::Objects, the implementation
# is in UR::Object.  However, some of those methods
# have the same name as methods in here (purposefully),
# and those UR::Object methods often get the
# UR::Object::Type object and call the same method,
# which ends up in this file.  The problem is when
# those methods are called on UR::Object::Type
# itself it come directly here, without getting
# the UR::Object::Type object for UR::Object::Type
# (confused yet?).  So to fix this, we use _object to
# make sure we have an object and not a class.
#
# Basically, we make sure we're working with a class
# object and not a class name.
#

sub _object {
    return ref($_[0]) ? $_[0] : $_[0]->__meta__;
}

# new version gets everything, including "id" itself and object ref properties
push @cache_keys, '_all_property_type_names';
sub all_property_type_names {
    my $self = shift;
    
    if ($self->{_all_property_type_names}) {
        return @{ $self->{_all_property_type_names} };
    }
    
    #my $rule_template = UR::BoolExpr::Template->resolve('UR::Object::Type', 'id');

    my $all_property_type_names = $self->{_all_property_type_names} = [];
    for my $class_name ($self->class_name, $self->ancestry_class_names) {
        my $class_meta = UR::Object::Type->get($class_name);
        #my $rule = $rule_template->get_rule_for_values($class_name);
        #my $class_meta = $UR::Context::current->get_objects_for_class_and_rule('UR::Object::Type',$rule);
        if (my $has = $class_meta->{has}) {            
            push @$all_property_type_names, sort keys %$has;
        }
    }
    return @$all_property_type_names;
}

sub table_for_property {
    my $self = _object(shift);
    Carp::croak('must pass a property_name to table_for_property') unless @_;
    my $property_name = shift;
    for my $class_object ( $self, $self->ancestry_class_metas )
    {
        my $property_object = UR::Object::Property->get( class_name => $class_object->class_name, property_name => $property_name );
        if ( $property_object )
        {
            next unless $property_object->column_name;
            return $class_object->table_name;
        }
    }

    return;
}

sub column_for_property {
    my $self = _object(shift);
    Carp::croak('must pass a property_name to column_for_property') unless @_;
    my $property_name = shift;

    my($properties,$columns) = @{$self->{'_all_properties_columns'}};
    for (my $i = 0; $i < @$properties; $i++) {
        if ($properties->[$i] eq $property_name) {
            return $columns->[$i];
        }
    }

    for my $class_object ( $self->ancestry_class_metas ) {
        my $column_name = $class_object->column_for_property($property_name);
        return $column_name if $column_name;
    }
    return;
}

sub property_for_column {
    my $self = _object(shift);
    Carp::croak('must pass a column_name to property_for_column') unless @_;
    my $column_name = lc(shift);

    my $data_source = $self->data_source || 'UR::DataSource';
    my($table_name,$self_table_name);
    ($table_name, $column_name) = $data_source->_resolve_table_and_column_from_column_name($column_name);
    (undef, $self_table_name) = $data_source->_resolve_owner_and_table_from_table_name($self->table_name);

    if (! $table_name) {
        my($properties,$columns) = @{$self->{'_all_properties_columns'}};
        for (my $i = 0; $i < @$columns; $i++) {
            if (lc($columns->[$i]) eq $column_name) {
                return $properties->[$i];
            }
        }
    } elsif ($table_name
             and
             $self_table_name
             and lc($self_table_name) eq lc($table_name)
    ) {
        # @$properties and @$columns contain items inherited from parent classes
        # make sure the property we find with that name goes to this class
        my $property_name = $self->property_for_column($column_name);
        return undef unless $property_name;
        my $prop_meta = $self->property_meta_for_name($property_name);
        if ($prop_meta->class_name eq $self->class_name
            and
            lc($prop_meta->column_name) eq $column_name
        ) {
            return $property_name;
        }

    } elsif ($table_name) {

        for my $class_object ( $self, $self->ancestry_class_metas ) {
            next unless $class_object->data_source;
            my $class_object_table_name;
            (undef, $class_object_table_name)
                = $class_object->data_source->_resolve_owner_and_table_from_table_name($class_object->table_name);

            if (! $class_object_table_name
                or
                $table_name ne lc($class_object_table_name)
            ) {
                (undef, $class_object_table_name) = $class_object->data_source->parse_view_and_alias_from_inline_view($class_object->table_name);
            }
            next if (! $class_object_table_name
                or
                $table_name ne lc($class_object_table_name));

            my $property_name = $class_object->property_for_column($column_name);
            return $property_name if $property_name;
        }
    }

    return;
}

# Methods for maintaining unique constraints
# This is primarily used by the class re-writer (ur update classes-from-db), but
# BoolExprs use them,too

# Adds a constraint by name and property list to the class metadata.  The class initializer
# fills this data in via the 'constraints' key, so it shouldn't call add_unique_constraint()
# directly
sub add_unique_constraint {
    my $self = shift;

    unless (@_) {
        Carp::croak('method add_unique_constraint requires a constraint name as a parameter');
    }
    my $constraint_name = shift;

    my $constraints = $self->unique_property_set_hashref();
    if (exists $constraints->{$constraint_name}) {
        Carp::croak("A constraint named '$constraint_name' already exists for class ".$self->class_name);
    }

    unless (@_) {
        Carp::croak('method add_unique_constraint requires one or more property names as parameters');
    }
    my @property_names = @_;

    # Add a new constraint record
    push @{ $self->{'constraints'} } , { sql => $constraint_name, properties => \@property_names };
    # invalidate the other cached data
    $self->_invalidate_cached_data_for_subclasses('_unique_property_sets', '_unique_property_set_hashref');
}

sub remove_unique_constraint {
    my $self = shift;

    unless (@_) {
        Carp::croak("method remove_unique_constraint requires a constraint name as a parameter");
    }

    my $constraint_name = shift;
    my $constraints = $self->unique_property_set_hashref();
    unless (exists $constraints->{$constraint_name}) {
        Carp::croak("There is no constraint named '$constraint_name' for class ".$self->class_name);
    }

    # Remove the constraint record
    for (my $i = 0; $i < @{$self->{'constraints'}}; $i++) {
        if ($self->{'constraints'}->[$i]->{'sql'} = $constraint_name) {
            splice(@{$self->{'constraints'}}, $i, 1);
        }
    }
    $self->_invalidate_cached_data_for_subclasses('_unique_property_sets', '_unique_property_set_hashref');
}


# This returns a list of lists.  Each inner list is the properties/columns
# involved in the constraint
sub unique_property_sets {
    my $self = shift; 
    if ($self->{_unique_property_sets}) {
        return @{ $self->{_unique_property_sets} };
    }

    my $unique_property_sets = $self->{_unique_property_sets} = [];

    for my $class_name ($self->class_name, $self->ancestry_class_names) {
        my $class_meta = UR::Object::Type->get($class_name);
        if ($class_meta->{constraints}) {            
            for my $spec (@{ $class_meta->{constraints} }) {
                push @$unique_property_sets, [ @{ $spec->{properties} } ] 
            }
        }
    }
    return @$unique_property_sets;
}

# Return the constraint information as a hashref
# keys are the SQL constraint name, values are a listref of property/column names involved
sub unique_property_set_hashref {
    my $self = shift;

    if ($self->{_unique_property_set_hashref}) {
        return $self->{_unique_property_set_hashref};
    }

    my $unique_property_set_hashref = $self->{_unique_property_set_hashref} = {};
   
    for my $class_name ($self->class_name, $self->ancestry_class_names) {
        my $class_meta = UR::Object::Type->get($class_name);
        if ($class_meta->{'constraints'}) {
            for my $spec (@{ $class_meta->{'constraints'} }) {
                my $unique_group = $spec->{'sql'};
                next if ($unique_property_set_hashref->{$unique_group});  # child classes override parents
                $unique_property_set_hashref->{$unique_group} = [ @{$spec->{properties}} ];
            }
        }
    }

    return $unique_property_set_hashref;
}


# Used by the class meta meta data constructors to make changes in the 
# raw data stored in the class object's hash.  These should really
# only matter while running ur update

# Args are:
# 1) An UR::Object::Property object with attribute_name, class_name, id, property_name, type_name
# 2) The method called: _construct_object, load, 
# 3) An id?
sub _property_change_callback {
    my($property_obj,$method, $old_val, $new_val) = @_;

    return if ($method eq 'load' || $method eq 'unload');
    return unless ref($property_obj);  # happens when, say, error_message is called on the UR::Object::Property class

    my $class_obj = UR::Object::Type->get(class_name => $property_obj->class_name);
    my $property_name = $property_obj->property_name;

    $old_val = '' unless(defined $old_val);
    $new_val = '' unless(defined $new_val);

    if ($method eq 'create') {
        unless ($class_obj->{'has'}->{$property_name}) {
            my @attr = qw( class_name data_length data_type is_delegated is_optional property_name );

            my %new_property;
            foreach my $attr_name (@attr ) {
                $new_property{$attr_name} = $property_obj->$attr_name();
            }
            $class_obj->{'has'}->{$property_name} = \%new_property;
        }
        if (defined $property_obj->is_id) {
            &_id_property_change_callback($property_obj, 'create');
        }

    } elsif ($method eq 'delete') {
        if (defined $property_obj->is_id) {
            &_id_property_change_callback($property_obj, 'delete');
        }
        delete $class_obj->{'has'}->{$property_name};

    } elsif ($method eq 'is_id' and $new_val ne $old_val) {
        my $change = $new_val ? 'create' : 'delete';
        &_id_property_change_callback($property_obj, $change);
    }

    if (exists $class_obj->{'has'}->{$property_name}
        && exists $class_obj->{'has'}->{$property_name}->{$method}) {
        $class_obj->{'has'}->{$property_name}->{$method} = $new_val;

    } 

    # Invalidate the cache used by all_property_names()
    for my $key (@cache_keys) {
        $class_obj->_invalidate_cached_data_for_subclasses($key);
    }
}


# Some expensive-to-calculate data gets stored in the class meta hashref
# and needs to be removed for all the existing subclasses
sub _invalidate_cached_data_for_subclasses {
    my($class_meta, @cache_keys) = @_;

    delete @$class_meta{@cache_keys};

    my @subclasses = @{$UR::Object::Type::_init_subclasses_loaded{$class_meta->class_name}};
    my %seen;
    while (my $subclass = shift @subclasses) {
        next if ($seen{$subclass}++);
        my $sub_meta = UR::Object::Type->get(class_name => $subclass);
        delete @$sub_meta{@cache_keys};
        push @subclasses, @{$UR::Object::Type::_init_subclasses_loaded{$sub_meta->class_name}};
    }
}


# A streamlined version of the method just below that dosen't check that the
# data in both places is the same before a delete operation.  What was happening
# was that an ID property got deleted and the position checks out ok, but then
# a second ID property gets deleted and now the position dosen't match because we
# aren't able to update the object's position property 'cause it's an ID property
# and can't be changed.  
#
# The short story is that we've lowered the bar for making sure it's safe to delete info
sub _id_property_change_callback {
    my $property_obj = shift;
    my $method = shift;

    return if ($method eq 'load' || $method eq 'unload');

    my $class = UR::Object::Type->get(class_name => $property_obj->class_name);
    
    if ($method eq 'create') {
        my $pos = $property_obj->id_by;
        $pos += 0;  # make sure it's a number
        if ($pos <= @{$class->{'id_by'}}) {
            splice(@{$class->{'id_by'}}, $pos, 0, $property_obj->property_name);
        } else {
            # $pos is past the end... probably an id property was deleted and another added
            push @{$class->{'id_by'}}, $property_obj->property_name;
        }
    } elsif ($method eq 'delete') {
        my $property_name = $property_obj->property_name;
        for (my $i = 0; $i < @{$class->{'id_by'}}; $i++) {
            if ($class->{'id_by'}->[$i] eq $property_name) {
                splice(@{$class->{'id_by'}}, $i, 1);
                return;
            }
        }
        #$DB::single = 1;
        Carp::confess("Internal data consistancy problem: could not find property named $property_name in id_by list for class meta " . $class->class_name);

    } else {
        # Shouldn't get here since ID properties can't be changed, right?
        #$DB::single = 1;
        Carp::confess("Shouldn't be here as ID properties can't change");
        1;
    }

    $class->{'_all_id_property_names'} = undef;  #  Invalidate the cache used by all_id_property_names
}


#
# BOOTSTRAP CODE
#

sub get_with_special_parameters {
    my $class = shift;
    my $rule = shift;
    my %extra = @_;
    if (my $namespace = delete $extra{'namespace'}) {
        unless (keys %extra) {
            my @c = $namespace->get_material_classes();
            @c = grep { $_->namespace eq $namespace } $class->is_loaded($rule->params_list);
            return $class->context_return(@c);
        }
    }
    return $class->SUPER::get_with_special_parameters($rule,@_);
}

sub __signal_change__ {
    my $self = shift;
    my @rv = $self->SUPER::__signal_change__(@_);
    if ($_[0] eq "delete") {
        my $class_name = $self->{class_name};
        $self->ungenerate();
    }
    return @rv;
}

my @default_valid_signals = qw(create delete commit rollback load unload load_external subclass_loaded);
our %STANDARD_VALID_SIGNALS;
@STANDARD_VALID_SIGNALS{@default_valid_signals} = (1) x @default_valid_signals;
sub _is_valid_signal {
    my $self = shift;
    my $aspect = shift;

    # An aspect of empty string (or undef) means all aspects are being observed.
    return 1 unless (defined($aspect) and length($aspect));

    # All standard creation and destruction methods emit a signal.
    return 1 if ($STANDARD_VALID_SIGNALS{$aspect});

    for my $property ($self->all_property_names)
    {
        return 1 if $property eq $aspect;
    }

    if (!exists $self->{'_is_valid_signal'}) {
        $self->{'_is_valid_signal'} = { map { $_ => 1 } @{$self->{'valid_signals'}} };
    }

    return 1 if ($self->{'_is_valid_signal'}->{$aspect});

    foreach my $parent_meta ( $self->parent_class_metas ) {
        if ($parent_meta->_is_valid_signal($aspect)) {
            $self->{'_is_valid_signal'}->{$aspect} = 1;
            return 1;
        }
    }

    return 0;
}


sub generated {
    my $self = shift;
    if (@_) {
        $self->{'generated'} = shift;
    }
    return $self->{'generated'};
}

sub ungenerate {
    my $self = shift;
    my $class_name = $self->class_name;
    delete $UR::Object::_init_subclass->{$class_name};
    delete $UR::Object::Type::_inform_all_parent_classes_of_newly_loaded_subclass{$class_name};    
    do {
        no strict;
        no warnings;
        my @symbols_which_are_not_subordinate_namespaces =
            grep { substr($_,-2) ne '::' }
            keys %{ $class_name . "::" };
        my $hr = \%{ $class_name . "::" };
        delete @$hr{@symbols_which_are_not_subordinate_namespaces};        
    };
    my $module_name = $class_name;
    $module_name =~ s/::/\//g;
    $module_name .= ".pm";
    delete $INC{$module_name};    
    $self->{'generated'} = 0;
}

sub singular_accessor_name_for_is_many_accessor {
    my($self, $property_name) = @_;
    unless (exists $self->{_accessor_singular_names}->{$property_name}) {
        my $property_meta = $self->property_meta_for_name($property_name) if ($self->generated);
        if ($bootstrapping  # trust the caller when bootstrapping
            or
            ! $self->generated # when called from UR::Object::Type::AccessorWriter and the property isn't created yet
            or
            ($property_meta && $property_meta->is_many)
        ) {
            require Lingua::EN::Inflect;
            $self->{_accessor_singular_names}->{$property_name} = Lingua::EN::Inflect::PL_V($property_name);
        } else {
            $self->{_accessor_singular_names}->{$property_name} = undef;
        }
    }
    return $self->{_accessor_singular_names}->{$property_name};
}

sub iterator_accessor_name_for_is_many_accessor {
    my($self, $property_name) = @_;

    my $singular = $self->singular_accessor_name_for_is_many_accessor($property_name);
    return $singular && "${singular}_iterator";
}

sub set_accessor_name_for_is_many_accessor {
    my($self, $property_name) = @_;

    my $singular = $self->singular_accessor_name_for_is_many_accessor($property_name);
    return $singular && "${singular}_set";
}

sub rule_accessor_name_for_is_many_accessor {
    my($self, $property_name) = @_;

    my $singular = $self->singular_accessor_name_for_is_many_accessor($property_name);
    return $singular && "__${singular}_rule";
}

sub arrayref_accessor_name_for_is_many_accessor {
    my($self, $property_name) = @_;

    my $singular = $self->singular_accessor_name_for_is_many_accessor($property_name);
    return $singular && "${singular}_arrayref";
}

sub adder_name_for_is_many_accessor {
    my($self, $property_name) = @_;

    my $singular = $self->singular_accessor_name_for_is_many_accessor($property_name);
    return $singular && "add_${singular}";
}

sub remover_name_for_is_many_accessor {
    my($self, $property_name) = @_;

    my $singular = $self->singular_accessor_name_for_is_many_accessor($property_name);
    return $singular && "remove_${singular}";
}

1;