package UR::Object::Type::ModuleWriter; # to help the installer
package UR::Object::Type; # hold methods for the class which cover Module Read/Write.
use strict;
use warnings;
require UR;
our $VERSION = "0.41"; # UR $VERSION;
our %meta_classes;
our $bootstrapping = 1;
our @partially_defined_classes;
our $pwd_at_compile_time = cwd();
sub resolve_class_description_perl {
my $self = $_[0];
no strict 'refs';
my @isa = @{ $self->class_name . "::ISA" };
use strict;
unless (@isa) {
#Carp::cluck("No isa for $self->{class_name}!?");
my @i = ${ $self->is };
my @parent_class_objects = map { UR::Object::Type->is_loaded(class_name => $_) } @i;
die "Parent class objects not all loaded for " . $self->class_name unless (@i == @parent_class_objects);
@isa = map { $_->class_name } @parent_class_objects;
}
unless (@isa) {
#Carp::confess("FAILED TO SET ISA FOR $self->{class_name}!?");
my @i = ${ $self->is };
my @parent_class_objects = map { UR::Object::Type->is_loaded(class_name => $_) } @i;
unless (@i and @i == @parent_class_objects) {
#$DB::single = 1;
Carp::confess("No inheritance meta-data found for ( @i / @parent_class_objects)" . $self->class_name)
}
@isa = map { $_->class_name } @parent_class_objects;
}
my $class_name = $self->class_name;
my @parent_classes = $self->parent_class_metas;
my $has_table = $self->has_table;
# For getting default values for some of the properties
my $class_meta_meta = UR::Object::Type->get(class_name => 'UR::Object::Type');
my $perl = '';
unless (@isa == 1 and $isa[0] =~ /^UR::Object|UR::Entity$/ ) {
$perl .= " is => " . (@isa == 1 ? "[ '@isa' ],\n" : "[ qw/@isa/ ],\n");
}
$perl .= " table_name => " . ($self->table_name ? "'" . $self->table_name . "'" : 'undef') . ",\n" if $self->data_source_id;
$perl .= " is_abstract => 1,\n" if $self->is_abstract;
$perl .= " er_role => '" . $self->er_role . "',\n" if ($self->er_role and ($self->er_role ne $class_meta_meta->property_meta_for_name('er_role')->default_value));
# Meta-property attributes
my @property_meta_property_names;
my @property_meta_property_strings;
if ($self->{'attributes_have'}) {
@property_meta_property_names = sort { $self->{'attributes_have'}->{$a}->{'position_in_module_header'}
<=>
$self->{'attributes_have'}->{$b}->{'position_in_module_header'} }
keys %{$self->{'attributes_have'}};
foreach my $meta_name ( @property_meta_property_names ) {
my $this_meta_struct = $self->{'attributes_have'}->{$meta_name};
# The attributes_have structure gets propogated to subclasses, but it only needs to appear
# in the class definition of the most-parent class
my $expected_name = $class_name . '::attributes_have';
next unless ( $this_meta_struct->{'is_specified_in_module_header'} eq $expected_name);
# We want these to appear first
my @this_meta_properties;
push @this_meta_properties, sprintf("is => '%s'", $this_meta_struct->{'is'}) if (exists $this_meta_struct->{'is'});
push @this_meta_properties, sprintf("is_optional => %d", $this_meta_struct->{'is_optional'}) if (exists $this_meta_struct->{'is_optional'});
foreach my $key ( sort keys %$this_meta_struct ) {
next if grep { $key eq $_ } qw( is is_optional is_specified_in_module_header position_in_module_header ); # skip the ones we've already done
my $value = $this_meta_struct->{$key};
my $format = $self->_is_number($value) ? "%s => %s" : "%s => '%s'";
push @this_meta_properties, sprintf($format, $key, $value);
}
push @property_meta_property_strings, "$meta_name => { " . join(', ', @this_meta_properties) . " },";
}
}
if (@property_meta_property_strings) {
$perl .= " attributes_have => [\n " .
join("\n ", @property_meta_property_strings) .
"\n ],\n";
}
if (exists $self->{'first_sub_classification_method_name'}) {
# This gets overridden by UR::Object::Type to cache the value it finds from parent
# classes in __first_sub_classification_method_name, so we can't just get the
# property through the normal channels
$perl .= " first_sub_classification_method_name => '" . $self->{'first_sub_classification_method_name'} ."',\n";
}
# These property names are either written in other places in this sub, or shouldn't be written out
my %addl_property_names = map { $_ => 1 } $self->__meta__->all_property_type_names;
my @specified = qw/is class_name table_name id_by er_role is_abstract generated data_source_id schema_name doc namespace id first_sub_classification_method_name property_metas pproperty_names id_property_metas meta_class_name id_generator valid_signals/;
delete @addl_property_names{@specified};
for my $property_name (sort keys %addl_property_names) {
my $property_obj = $class_meta_meta->property_meta_for_name($property_name);
next if ($property_obj->is_calculated or $property_obj->is_delegated);
my $property_value = $self->$property_name;
my $default_value = $property_obj && $property_obj->default_value;
# If the property is set on the class object
# and both the value and default are numeric and numerically different,
# or stringly different than the default
no warnings qw( numeric uninitialized );
if ( defined $property_value and
( ($property_value + 0 eq $property_value and
$default_value + 0 eq $default_value and
$property_value != $default_value)
or
($property_value ne $default_value)
)
) {
# then it should show up in the class definition
$perl .= " $property_name => '" . $self->$property_name . "',\n";
}
}
my %properties_by_section;
my %id_property_names = map { $_ => 1 } $self->direct_id_property_names;
my @properties = $self->direct_property_metas;
foreach my $property_meta ( @properties ) {
my $mentioned_section = $property_meta->is_specified_in_module_header;
next unless $mentioned_section; # skip implied properites
($mentioned_section) = ($mentioned_section =~ m/::(\w+)$/);
if (($mentioned_section and $mentioned_section eq 'id_implied')
or $id_property_names{$property_meta->property_name}) {
push @{$properties_by_section{'id_by'}}, $property_meta;
} elsif ($mentioned_section) {
push @{$properties_by_section{$mentioned_section}}, $property_meta;
} else {
push @{$properties_by_section{'has'}}, $property_meta;
}
}
my %sections_seen;
my $data_source_id = $self->data_source_id;
my ($data_source) = ($data_source_id ? UR::DataSource->get($data_source_id) : undef);
foreach my $section ( ( 'id_by', 'has', 'has_many', 'has_optional', keys(%properties_by_section) ) ) {
next unless ($properties_by_section{$section});
next if ($sections_seen{$section});
$sections_seen{$section} = 1;
# New properites (will have position_in_module_header == undef) should go at the end
my @properties = sort { my $pos_a = defined($a->{'position_in_module_header'})
? $a->{'position_in_module_header'}
: 1000000;
my $pos_b = defined($b->{'position_in_module_header'})
? $b->{'position_in_module_header'}
: 1000000;
$pos_a <=> $pos_b;
}
@{$properties_by_section{$section}};
my $section_src = '';
my $max_name_length = 0;
my $multi_line_indent = '';
foreach my $property_meta ( @properties ) {
my $name = $property_meta->property_name;
$max_name_length = length($name) if (length($name) > $max_name_length);
}
# 14 is the 8 spaces at the start of the $line, plus ' => { '
$multi_line_indent = ' ' x ($max_name_length + 14);
foreach my $property_meta ( @properties ) {
my $name = $property_meta->property_name;
my @fields = $self->_get_display_fields_for_property(
$property_meta,
has_table => $has_table,
section => $section,
data_source => $data_source,
attributes_have => \@property_meta_property_names);
foreach ( @fields ) {
s/\n/\n$multi_line_indent/;
}
my $line = " "
. $name . (" " x ($max_name_length - length($name)))
. " => { "
. join(", ", @fields)
. " },\n";
$section_src .= $line;
}
$perl .= " $section => [\n$section_src ],\n";
}
my $unique_groups = $self->unique_property_set_hashref;
if ($unique_groups and keys %$unique_groups) {
$perl .= " unique_constraints => [\n";
for my $unique_group_name (keys %$unique_groups) {
my $property_names = join(' ', sort { $a cmp $b } @{ $unique_groups->{$unique_group_name}});
$perl .= " { "
. "properties => [qw/$property_names/], "
. "sql => '" . $unique_group_name . "'"
. " },\n";
}
$perl .= " ],\n";
}
$perl .= " schema_name => '" . $self->schema_name . "',\n" if $self->schema_name;
$perl .= " data_source => '" . $self->data_source_id . "',\n" if $self->data_source_id;
my $print_id_generator;
if (my $id_generator = $self->id_generator) {
if ($self->data_source_id and $id_generator eq '-urinternal') {
$print_id_generator = 1;
} elsif (! $self->data_source_id and $id_generator eq '-urinternal') {
$print_id_generator = 0;
} else {
$print_id_generator = 1;
}
$perl .= " id_generator => '$id_generator',\n" if ($print_id_generator);
}
if (my $valid_signals = $self->valid_signals) {
if (ref($valid_signals) ne 'ARRAY') {
Carp::croak("The 'valid_signals' metadata for class $class_name must be an arrayref, got: ".Data::Dumper::Dumper($valid_signals));
} elsif (@$valid_signals) {
$perl .= " valid_signals => ['" . join("', '", @$valid_signals) . "'],\n";
}
}
my $doc = $self->doc;
if (defined($doc)) {
$doc = Dumper($doc);
$doc =~ s/\$VAR1 = //;
$doc =~ s/;\s*$//;
}
$perl .= " doc => $doc,\n" if defined($doc);
return $perl;
}
sub resolve_module_header_source {
my $self = shift;
my $class_name = $self->class_name;
my $perl = "class $class_name {\n";
$perl .= $self->resolve_class_description_perl;
$perl .= "};\n";
return $perl;
}
my $next_line_prefix = "\n";
my $deep_indent_prefix = "\n" . (" " x 55);
sub _get_display_fields_for_property {
my $self = shift;
my $property = shift;
my %params = @_;
if (not $property->is_specified_in_module_header) {
# we omit showing implied properties which have no additional data,
# unless they have their own docs, a specified column, etc.
return();
}
my @fields;
my %seen;
my $property_name = $property->property_name;
my $type = $property->data_type;
if ($type) {
push @fields, "is => '$type'" if $type;
$seen{'is'} = 1;
}
if (defined($property->data_length) and length($property->data_length)) {
push @fields, "len => " . $property->data_length;
$seen{'data_length'} = 1;
}
#$line .= "references => '???', ";
if ($property->is_legacy_eav) {
# temp hack for entity attribute values
#push @fields, "delegate => { via => 'eav_" . $property->property_name . "', to => 'value' }";
push @fields, "is_legacy_eav => 1";
$seen{'is_legacy_eav'} = 1;
}
elsif ($property->is_delegated) {
# do nothing
$seen{'is_delegated'} = 1;
}
elsif ($property->is_calculated) {
my @calc_fields;
if (my $calc_from = $property->calculate_from) {
if ($calc_from and @$calc_from == 1) {
push @calc_fields, "calculate_from => '" . $calc_from->[0] . "'";
} elsif ($calc_from) {
push @calc_fields, "calculate_from => [ '" . join("', '", @$calc_from) . "' ]";
}
}
my $calc_source;
foreach my $calc_type ( qw( calculate calculate_sql calculate_perl calculate_js ) ) {
if ($property->$calc_type) {
$calc_source = 1;
push @calc_fields, "$calc_type => q(" . $property->$calc_type . ")";
}
}
push @calc_fields, 'is_calculated => 1' unless ($calc_source);
push @fields, join(",$next_line_prefix", @calc_fields);
$seen{'is_calculated'} = 1;
}
elsif ($params{has_table} && ! $property->is_transient) {
unless ($property->column_name) {
die("no column for property on class with table: " . $property->property_name .
" class: " . $self->class_name . "?");
}
if ( ( $params{'data_source'}
and $params{'data_source'}->table_and_column_names_are_upper_case
and $property->column_name ne uc($property->property_name)
)
or
( $property->column_name ne $property->property_name)
) {
# If the column name doesn't match the property name, write it out
push @fields, "column_name => '" . $property->column_name . "'";
}
$seen{'column_name'} = 1;
}
if (defined($property->default_value)) {
my $value = $property->default_value;
if (! $self->_is_number($value)) {
$value = "'$value'";
}
push @fields, "default_value => $value";
$seen{'default_value'} = 1;
}
my $implied_property = 0;
if (defined($property->implied_by) and length($property->implied_by)) {
push @fields, "implied_by => '" . $property->implied_by . "'";
$implied_property = 1;
$seen{'implied_by'} = 1;
}
if (my @id_by = eval { $property->get_property_name_pairs_for_join }) {
push @fields, "id_by => "
. (@id_by > 1 ? '[ ' : '')
. join(", ", map { "'" . $_->[0] . "'" } @id_by)
. (@id_by > 1 ? ' ]' : '');
$seen{'get_property_name_pairs_for_join'} = 1;
if (defined $property->id_class_by) {
push @fields, sprintf("id_class_by => '%s'", $property->id_class_by);
}
}
if ($property->via) {
push @fields, "via => '" . $property->via . "'";
$seen{'via'} = 1;
if ($property->to and $property->to ne $property->property_name) {
push @fields, "to => '" . $property->to . "'";
$seen{'to'} = 1;
}
if ($property->is_mutable) {
# via properties are not usually mutable
push @fields, 'is_mutable => 1';
}
}
if ($property->reverse_as) {
push @fields, "reverse_as => '" . $property->reverse_as . "'";
$seen{'reverse_as'} = 1;
}
if ($property->constraint_name) {
push @fields, "constraint_name => '" . $property->constraint_name . "'";
$seen{'constraint_name'} = 1;
}
if ($property->where) {
my @where_parts = ();
my @where = @{ $property->where };
while (@where) {
my $prop_name = shift @where;
my $comparison = shift @where;
if (! ref($comparison)) {
# It's a strictly equals comparison.
# wrap it in quotes...
$comparison = "'$comparison'";
} elsif (ref($comparison) eq 'HASH') {
# It's a more complicated operator
my @operator_parts = ();
foreach my $key ( 'operator', 'value', keys %$comparison ) {
if ($comparison->{$key}) {
if (ref($comparison->{$key})) {
my $class_name = $property->class_name;
Carp::croak("Modulewriter doesn't know how to handle property $property_name of class $class_name. Its 'where' has a non-scalar value for the '$key' key");
}
push @operator_parts, "$key => '" . delete($comparison->{$key}) . "'";
}
}
$comparison = '{ ' . join(', ', @operator_parts) . ' } ';
} else {
my $class_name = $property->class_name;
Carp::croak("Modulewriter doesn't know how to handle property $property_name of class $class_name. Its 'where' is not a simple scalar or hashref");
}
push @where_parts, "$prop_name => $comparison";
}
push @fields, 'where => [ ' . join(', ', @where_parts) . ' ]';
}
if (my $valid_values_arrayref = $property->valid_values) {
$seen{'valid_values'} = 1;
my $value_string = Data::Dumper->new([$valid_values_arrayref])->Terse(1)->Indent(0)->Useqq(1)->Dump;
push @fields, "valid_values => $value_string";
}
if (my $example_values_arrayref = $property->example_values) {
$seen{'example_values'} = 1;
my $value_string = Data::Dumper->new([$example_values_arrayref])->Terse(1)->Indent(0)->Useqq(1)->Dump;
push @fields, "example_values => $value_string";
}
# All the things like is_optional, is_many, etc
# show only true values, false is default
# section can be things like 'has', 'has_optional' or 'has_transient_many_optional'
my $section = $params{'section'};
$section =~ m/^has_(.*)/;
my @sections = split('_',$1 || '');
for my $std_field_name (qw/optional abstract transient constant classwide many deprecated/) {
$seen{$property_name} = 1;
next if (grep { $std_field_name eq $_ } @sections); # Don't print is_optional if we're in the has_optional section
my $property_name = "is_" . $std_field_name;
push @fields, "$property_name => " . $property->$property_name if $property->$property_name;
}
foreach my $meta_property ( @{$params{'attributes_have'}} ) {
my $value = $property->{$meta_property};
if (defined $value) {
my $format = $self->_is_number($value) ? "%s => %s" : "%s => '%s'";
push @fields, sprintf($format, $meta_property, $value);
}
}
my $desc = $property->doc;
if ($desc && length($desc)) {
$desc =~ s/([\$\@\%\\\"])/\\$1/g;
$desc =~ s/\n/\\n/g;
push @fields, $next_line_prefix . "doc => '$desc'";
}
return @fields;
}
sub module_base_name {
my $file_name = shift->class_name;
$file_name =~ s/::/\//g;
$file_name .= ".pm";
return $file_name;
}
sub module_path {
my $self = shift;
my $base_name = $self->module_base_name;
my $path = $INC{$base_name};
return _abs_path_relative_to_pwd_at_compile_time($path) if $path;
#warn "Module $base_name is not in \%INC!\n";
my $namespace;
my $first_slash = index($base_name, '/');
if ($first_slash >= 0) {
# Normal case...
$namespace = substr($base_name, 0, $first_slash);
$namespace .= ".pm";
} else {
# This module must _be_ the namespace
$namespace = $base_name;
}
for my $dir (map { _abs_path_relative_to_pwd_at_compile_time($_) } grep { -d $_ } @INC) {
if (-e $dir . "/" . $namespace) {
#warn "Found $base_name in $dir...\n";
my $try_path = $dir . '/' . $base_name;
return $try_path;
}
}
return;
#Carp::confess("Failed to find a module path for class " . $self->class_name);
}
sub _abs_path_relative_to_pwd_at_compile_time { # not a method
my $path = shift;
if ($path !~ /^[\/\\]/) {
$path = $pwd_at_compile_time . '/' . $path;
}
my $path2 = Cwd::abs_path($path);
# Carp::confess("$path abs is undef?") if not defined $path2;
return $path2;
}
sub module_directory {
my $self = shift;
my $base_name = $self->module_base_name;
my $path = $self->module_path;
return unless defined($path) and length($path);
unless ($path =~ s/$base_name$//) {
Carp::confess("Failed to find base name $base_name at the end of path $path!")
}
return $path;
}
sub module_source_lines {
my $self = shift;
my $file_name = $self->module_path;
my $in = IO::File->new("<$file_name");
unless ($in) {
return (undef,undef,undef);
}
my @module_src = <$in>;
$in->close;
return @module_src
}
sub module_source {
join("",shift->module_source_lines);
}
sub module_header_positions {
my $self = shift;
my @module_src = $self->module_source_lines;
my $namespace = $self->namespace;
my $class_name = $self->class_name;
unless ($self->namespace) {
die "No namespace on $self->{class_name}?"
}
$namespace = 'UR' if $namespace eq $self->class_name;
my $state = 'before';
my ($begin,$end,$use);
for (my $n = 0; $n < @module_src; $n++) {
my $line = $module_src[$n];
if ($state eq 'before') {
if ($line and $line =~ /^use $namespace;/) {
$use = $n;
}
if (
$line and (
$line =~ /^define UR::Object::Type$/
or $line =~ /^(App|UR)::Object::(Type|Class)->(define|create)\($/
or $line =~ /^class\s*$class_name\b/
)
) {
$begin = $n;
$state = 'during';
}
else {
}
}
elsif ($state eq 'during') {
my $old_meta_src .= $line; # FIXME this dosen't appear anywhere else??
if ($line =~ /^(\)|\}|);\s*$/) {
$end = $n;
$state = 'after';
}
}
#elsif ($state eq 'after') {
#
#}
}
# cache
$self->{module_header_positions} = [$begin,$end,$use];
# return
return ($begin,$end,$use);
}
sub module_header_source_lines {
my $self = shift;
my ($begin,$end,$use) = $self->module_header_positions;
my @src = $self->module_source_lines;
return unless $end;
@src[$begin..$end];
}
sub module_header_source {
join("",shift->module_header_source_lines);
}
sub rewrite_module_header {
use strict;
use warnings;
my $self = shift;
my $package = $self->class_name;
# generate new class metadata
my $new_meta_src = $self->resolve_module_header_source;
unless ($new_meta_src) {
die "Failed to generate source code for $package!";
}
my ($begin,$end,$use) = $self->module_header_positions;
my $namespace = $self->namespace;
$namespace = 'UR' if $namespace eq $self->class_name;
unless ($namespace) {
($namespace) = ($package =~ /^(.*?)::/);
}
$new_meta_src = "use $namespace;\n" . $new_meta_src unless $use;
# determine the path to the module
# this may not exist
my $module_file_path = $self->module_path;
# temp safety hack
if ($module_file_path =~ "/gsc/scripts/lib") {
Carp::confess("attempt to write directly to the app server!");
}
# determine the new source for the module
my @module_src;
my $old_file_data;
if (-e $module_file_path) {
# rewrite the existing module
# find the old positions of the module header
@module_src = $self->module_source_lines;
# cleanup legacy cruft
unless ($namespace eq 'UR') {
@module_src = map { ($_ =~ m/^use UR;/?"":$_) } @module_src;
}
if (!grep {$_ =~ m/^use warnings;/} @module_src) {
$new_meta_src = "use warnings;\n" . $new_meta_src;
}
if (!grep {$_ =~ m/^use strict;/} @module_src) {
$new_meta_src = "use strict;\n" . $new_meta_src;
}
# If $begin and $end are undef, then module_header_positions() didn't find any
# old code and we're inserting all brand new stuff. Look for the package declaration
# and insert after that.
my $len;
if (defined $begin || defined $end) {
$len = $end-$begin+1;
} else {
# is there a more fool-proof way to find it?
for ($begin = 0; $begin < $#module_src; ) {
last if ($module_src[$begin++] =~ m/package\s+$package;/);
}
$len = 0;
}
# replace the old lines with the new source
# note that the inserted "row" is multi-line, but joins nicely below...
splice(@module_src,$begin,$len,$new_meta_src);
my $f = IO::File->new($module_file_path);
$old_file_data = join('',$f->getlines);
$f->close();
}
else {
# write new module source
# put =cut marks around it if this is a special metadata class
# the definition at the top is non-functional for bootstrapping reasons
if ($meta_classes{$package}) {
$new_meta_src = "=cut\n\n$new_meta_src\n\n=cut\n\n";
$self->warning_message("Meta package $package");
}
@module_src = join("\n",
"package " . $self->class_name . ";",
"",
"use strict;",
"use warnings;",
"",
$new_meta_src,
"1;",
""
);
}
$ENV{'HOST'} ||= '';
my $temp = "$module_file_path.$$.$ENV{HOST}";
my $temp_dir = $module_file_path;
$temp_dir =~ s/\/[^\/]+$//;
unless (-d $temp_dir) {
print "mkdir -p $temp_dir\n";
system "mkdir -p $temp_dir";
}
my $out = IO::File->new(">$temp");
unless ($out) {
die "Failed to create temp file $temp!";
}
for (@module_src) { $out->print($_) };
$out->close;
my $rv = system qq(perl -e 'eval `cat $temp`' 2>/dev/null 1>/dev/null);
$rv /= 255;
if ($rv) {
die "Module is not compilable with new source!";
}
else {
unless (rename $temp, $module_file_path) {
die "Error renaming $temp to $module_file_path!";
}
}
UR::Context::Transaction->log_change($self, ref($self), $self->id, 'rewrite_module_header', Data::Dumper::Dumper{path => $module_file_path, data => $old_file_data});
return 1;
}
# TODO: move to UR::Util
sub _is_number {
my($self,$value) = @_;
no warnings 'numeric';
my $is_number = ($value + 0) eq $value;
return $is_number;
}
1;
=pod
=head1 NAME
UR::Object::Type::ModuleWriter - Helper module for UR::Object::Type responsible for writing Perl modules
=head1 DESCRIPTION
Subroutines within this module actually live in the UR::Object::Type
namespace; this module is just a convienent place to collect them. The
Module Writer is used by the class updater system (L<(UR::Namespace::Command::Update::Classes>
and 'ur update classes) to add, remove and alter the Perl modules behind
the classes within a Namespace.
=head1 METHODS
=over 4
=item resolve_module_header_source
$classobj->resolve_module_header_source();
Returns a string that represents a fully-formed class definition the the
given class metaobject $classobj.
=item resolve_class_description_perl
$classobj->resolve_class_description_perl()
Used by resolve_module_header_source(). This method inspects all the
applicable properties of the class metaobject and builds up a string that
gets inserted between the {...} of the class definition string.
=item rewrite_module_header
$classobj->rewrite_module_header();
This method rewrites an existing Perl module file in place for the class
metaobject, or creates a new file if one does not already exist.
=item module_base_name
Returns the pathname of the class's module relative to the top level directory
of that class's Namespace.
=item module_path
Returns the fully qualified pathname of the class's module.
=item module_source_lines
Returns the text of the class's Perl module as a list of strings.
=item module_source
Returns the text of the class's Perl module as a single string.
=item module_header_positions
Returns a 3-element list ($begin, $end, $use) where $begin is the line number
where the class header begins. $end is the line number where it ends. $use
is the line number where the module declares that it use's a Namespace.
=item module_header_source_lines
Returns the text of the class's Perl module source where the class definition
is as a list of strings.
=item module_header_source
Returns the text of the class's Perl module source where the class definition
is as a single string.
=back
=head1 SEE ALSO
UR::Object::Type, UR::Object::Type::Initializer
=cut