putting the cache experiment in a branch
[gitmo/Class-MOP.git] / lib / Class / MOP.pm
index 8ca431d..ca07a0a 100644 (file)
@@ -4,8 +4,10 @@ package Class::MOP;
 use strict;
 use warnings;
 
-use Carp         'confess';
-use Scalar::Util 'weaken';
+use MRO::Compat;
+
+use Carp          'confess';
+use Scalar::Util  'weaken';
 
 use Class::MOP::Class;
 use Class::MOP::Attribute;
@@ -14,20 +16,89 @@ use Class::MOP::Method;
 use Class::MOP::Immutable;
 
 BEGIN {
-    our $VERSION   = '0.51';
+    our $VERSION   = '0.56';
     our $AUTHORITY = 'cpan:STEVAN';    
     
-    use XSLoader;
-    XSLoader::load( 'Class::MOP', $VERSION );    
+    *IS_RUNNING_ON_5_10 = ($] < 5.009_005) 
+        ? sub () { 0 }
+        : sub () { 1 };    
+
+    # NOTE:
+    # we may not use this yet, but once 
+    # the get_code_info XS gets merged 
+    # upstream to it, we will always use 
+    # it. But for now it is just kinda 
+    # extra overhead.
+    # - SL
+    require Sub::Identify;
+        
+    # stash these for a sec, and see how things go
+    my $_PP_subname       = sub { $_[1] };
+    my $_PP_get_code_info = sub ($) { 
+        return (            
+            Sub::Identify::stash_name($_[0]), 
+            Sub::Identify::sub_name($_[0])
+        ) 
+    };    
     
-    unless ($] < 5.009_005) {
-        require mro;
-        no warnings 'redefine', 'prototype';
-        *check_package_cache_flag = \&mro::get_pkg_gen;
-        *IS_RUNNING_ON_5_10 = sub () { 1 };
+    if ($ENV{CLASS_MOP_NO_XS}) {
+        # NOTE:
+        # this is if you really want things
+        # to be slow, then you can force the
+        # no-XS rule this way, otherwise we 
+        # make an effort to load as much of 
+        # the XS as possible.
+        # - SL
+        no warnings 'prototype', 'redefine';
+        
+        unless (IS_RUNNING_ON_5_10()) {
+            # get this from MRO::Compat ...
+            *check_package_cache_flag = \&MRO::Compat::__get_pkg_gen_pp;
+        }
+        else {
+            # NOTE:
+            # but if we are running 5.10 
+            # there is no need to use the 
+            # Pure Perl version since we 
+            # can use the built in mro 
+            # version instead.
+            # - SL
+            *check_package_cache_flag = \&mro::get_pkg_gen; 
+        }
+        # our own version of Sub::Name
+        *subname       = $_PP_subname;
+        # and the Sub::Identify version of the get_code_info
+        *get_code_info = $_PP_get_code_info;        
     }
     else {
-        *IS_RUNNING_ON_5_10 = sub () { 0 };        
+        # now try our best to get as much 
+        # of the XS loaded as possible
+        {
+            local $@;
+            eval {
+                require XSLoader;
+                XSLoader::load( 'Class::MOP', $VERSION );            
+            };
+            die $@ if $@ && $@ !~ /object version|loadable object/;
+            
+            # okay, so the XS failed to load, so 
+            # use the pure perl one instead.
+            *get_code_info = $_PP_get_code_info if $@; 
+        }        
+        
+        # get it from MRO::Compat
+        *check_package_cache_flag = \&mro::get_pkg_gen;        
+        
+        # now try and load the Sub::Name 
+        # module and use that as a means
+        # for naming our CVs, if not, we 
+        # use the workaround instead.
+        if ( eval { require Sub::Name } ) {
+            *subname = \&Sub::Name::subname;
+        } 
+        else {
+            *subname = $_PP_subname;
+        }     
     }
 }
 
@@ -77,9 +148,9 @@ sub is_class_loaded {
     my $class = shift;
     no strict 'refs';
     return 1 if defined ${"${class}::VERSION"} || defined @{"${class}::ISA"};
-    foreach (keys %{"${class}::"}) {
-            next if substr($_, -2, 2) eq '::';
-            return 1 if defined &{"${class}::$_"};
+    foreach my $symbol (keys %{"${class}::"}) {
+            next if substr($symbol, -2, 2) eq '::';
+            return 1 if defined &{"${class}::${symbol}"};
     }
     return 0;
 }
@@ -94,6 +165,68 @@ sub is_class_loaded {
 
 # ... nothing yet actually ;)
 
+use Storable;
+
+my $MOP_CACHE_FILE = 'Class_MOP.cache';
+
+#warn ((stat $INC{'Class/MOP.pm'})[9]);
+#warn ((stat $MOP_CACHE_FILE)[9]);
+
+if (-e $MOP_CACHE_FILE && (stat $INC{'Class/MOP.pm'})[9] < (stat $MOP_CACHE_FILE)[9]) {
+    $Storable::Eval = 1;    
+    my $cache = Storable::retrieve($MOP_CACHE_FILE);
+    
+    # now we do 2 things, first is to grab 
+    # the cached metaclass, and second is 
+    # to make sure that we reinstall any 
+    # methods we installed in the bootstrap
+    # process, this is typically constructors
+    # and clone methods 
+    
+    my %methods;    
+    
+    foreach my $meta_name (keys %{$cache->{metas}}) {
+        my $metaclass = $cache->{metas}->{$meta_name};        
+        
+        # before we do anything to the 
+        # metaclasses, we need to grab the 
+        # methods we added in the bootstrap
+        # because any calls to get_method_map
+        # will cause it to grab the ones 
+        # that are on disk, and not in the 
+        # bootstrap.
+        $methods{$meta_name} = [];
+        
+        foreach my $method_to_reinstall (@{$cache->{methods_to_reinstall}->{$meta_name}}) {
+            #use Data::Dumper;
+            #$Data::Dumper::Deparse = 1;
+            #warn Dumper $metaclass->{'%!methods'}->{$method_to_reinstall};
+            push @{ $methods{$meta_name} } => {
+                name   => $method_to_reinstall,
+                method => $metaclass->{'%!methods'}->{$method_to_reinstall},
+            };
+        } 
+               
+        store_metaclass_by_name($meta_name, $metaclass);        
+    }
+    
+    # now we can start adding methods
+    # so that we get the properly 
+    # bootstrapped versions of them
+    foreach my $meta_name (keys %methods) {
+        my $metaclass = $cache->{metas}->{$meta_name};
+        foreach my $method_to_install (@{ $methods{$meta_name} }) {
+            $metaclass->add_method(
+                $method_to_install->{name},
+                $method_to_install->{method}
+            );        
+        }
+    }
+}
+else {
+    
+my %methods_to_reinstall;
+
 ## ----------------------------------------------------------------------------
 ## Bootstrapping
 ## ----------------------------------------------------------------------------
@@ -135,9 +268,7 @@ Class::MOP::Package->meta->add_attribute(
             # rather than re-produce it here
             'namespace' => \&Class::MOP::Package::namespace
         },
-        # NOTE:
-        # protect this from silliness
-        init_arg => '!............( DO NOT DO THIS )............!',
+        init_arg => undef,
         default  => sub { \undef }
     ))
 );
@@ -151,6 +282,8 @@ Class::MOP::Package->meta->add_method('initialize' => sub {
     $class->meta->new_object('package' => $package_name, @_);
 });
 
+$methods_to_reinstall{'Class::MOP::Package'} = [qw[initialize]];
+
 ## --------------------------------------------------------
 ## Class::MOP::Module
 
@@ -172,9 +305,7 @@ Class::MOP::Module->meta->add_attribute(
             # rather than re-produce it here
             'version' => \&Class::MOP::Module::version
         },
-        # NOTE:
-        # protect this from silliness
-        init_arg => '!............( DO NOT DO THIS )............!',
+        init_arg => undef,
         default  => sub { \undef }
     ))
 );
@@ -193,9 +324,7 @@ Class::MOP::Module->meta->add_attribute(
             # rather than re-produce it here
             'authority' => \&Class::MOP::Module::authority
         },
-        # NOTE:
-        # protect this from silliness
-        init_arg => '!............( DO NOT DO THIS )............!',
+        init_arg => undef,
         default  => sub { \undef }
     ))
 );
@@ -240,9 +369,7 @@ Class::MOP::Class->meta->add_attribute(
             # rather than re-produce it here
             'superclasses' => \&Class::MOP::Class::superclasses
         },
-        # NOTE:
-        # protect this from silliness
-        init_arg => '!............( DO NOT DO THIS )............!',
+        init_arg => undef,
         default  => sub { \undef }
     ))
 );
@@ -345,6 +472,14 @@ Class::MOP::Attribute->meta->add_attribute(
 );
 
 Class::MOP::Attribute->meta->add_attribute(
+    Class::MOP::Attribute->new('$!initializer' => (
+        init_arg  => 'initializer',
+        reader    => { 'initializer'     => \&Class::MOP::Attribute::initializer     },
+        predicate => { 'has_initializer' => \&Class::MOP::Attribute::has_initializer },
+    ))
+);
+
+Class::MOP::Attribute->meta->add_attribute(
     Class::MOP::Attribute->new('$!writer' => (
         init_arg  => 'writer',
         reader    => { 'writer'     => \&Class::MOP::Attribute::writer     },
@@ -423,7 +558,7 @@ Class::MOP::Attribute->meta->add_method('new' => sub {
     } else {
         (Class::MOP::Attribute::is_default_a_coderef(\%options))
             || confess("References are not allowed as default values, you must ".
-                       "wrap then in a CODE reference (ex: sub { [] } and not [])")
+                       "wrap the default of '$name' in a CODE reference (ex: sub { [] } and not [])")
                 if exists $options{default} && ref $options{default};
     }
     # return the new object
@@ -435,6 +570,8 @@ Class::MOP::Attribute->meta->add_method('clone' => sub {
     $self->meta->clone_object($self, @_);
 });
 
+$methods_to_reinstall{'Class::MOP::Attribute'} = [qw[new clone]];
+
 ## --------------------------------------------------------
 ## Class::MOP::Method
 
@@ -445,6 +582,42 @@ Class::MOP::Method->meta->add_attribute(
     ))
 );
 
+Class::MOP::Method->meta->add_attribute(
+    Class::MOP::Attribute->new('$!package_name' => (
+        init_arg => 'package_name',
+        reader   => { 'package_name' => \&Class::MOP::Method::package_name },
+    ))
+);
+
+Class::MOP::Method->meta->add_attribute(
+    Class::MOP::Attribute->new('$!name' => (
+        init_arg => 'name',
+        reader   => { 'name' => \&Class::MOP::Method::name },
+    ))
+);
+
+Class::MOP::Method->meta->add_method('wrap' => sub {
+    my $class   = shift;
+    my $code    = shift;
+    my %options = @_;
+
+    ('CODE' eq (Scalar::Util::reftype($code) || ''))
+        || confess "You must supply a CODE reference to bless, not (" . ($code || 'undef') . ")";
+
+    ($options{package_name} && $options{name})
+        || confess "You must supply the package_name and name parameters";
+
+    # return the new object
+    $class->meta->new_object(body => $code, %options);
+});
+
+Class::MOP::Method->meta->add_method('clone' => sub {
+    my $self  = shift;
+    $self->meta->clone_object($self, @_);
+});
+
+$methods_to_reinstall{'Class::MOP::Method'} = [qw[wrap clone]];
+
 ## --------------------------------------------------------
 ## Class::MOP::Method::Wrapped
 
@@ -464,9 +637,21 @@ Class::MOP::Method::Generated->meta->add_attribute(
     Class::MOP::Attribute->new('$!is_inline' => (
         init_arg => 'is_inline',
         reader   => { 'is_inline' => \&Class::MOP::Method::Generated::is_inline },
+        default  => 0, 
     ))
 );
 
+Class::MOP::Method::Generated->meta->add_method('new' => sub {
+    my ($class, %options) = @_;
+    ($options{package_name} && $options{name})
+        || confess "You must supply the package_name and name parameters";    
+    my $self = $class->meta->new_object(%options);
+    $self->initialize_body;  
+    $self;
+});
+
+$methods_to_reinstall{'Class::MOP::Method::Generated'} = [qw[new]];
+
 ## --------------------------------------------------------
 ## Class::MOP::Method::Accessor
 
@@ -486,6 +671,36 @@ Class::MOP::Method::Accessor->meta->add_attribute(
     ))
 );
 
+Class::MOP::Method::Accessor->meta->add_method('new' => sub {
+    my $class   = shift;
+    my %options = @_;
+
+    (exists $options{attribute})
+        || confess "You must supply an attribute to construct with";
+
+    (exists $options{accessor_type})
+        || confess "You must supply an accessor_type to construct with";
+
+    (Scalar::Util::blessed($options{attribute}) && $options{attribute}->isa('Class::MOP::Attribute'))
+        || confess "You must supply an attribute which is a 'Class::MOP::Attribute' instance";
+
+    ($options{package_name} && $options{name})
+        || confess "You must supply the package_name and name parameters";
+
+    # return the new object
+    my $self = $class->meta->new_object(%options);
+    
+    # we don't want this creating
+    # a cycle in the code, if not
+    # needed
+    Scalar::Util::weaken($self->{'$!attribute'});
+
+    $self->initialize_body;  
+    
+    $self;
+});
+
+$methods_to_reinstall{'Class::MOP::Method::Accessor'} = [qw[new]];
 
 ## --------------------------------------------------------
 ## Class::MOP::Method::Constructor
@@ -496,6 +711,7 @@ Class::MOP::Method::Constructor->meta->add_attribute(
         reader   => {
             'options' => \&Class::MOP::Method::Constructor::options
         },
+        default  => sub { +{} }
     ))
 );
 
@@ -508,6 +724,32 @@ Class::MOP::Method::Constructor->meta->add_attribute(
     ))
 );
 
+Class::MOP::Method::Constructor->meta->add_method('new' => sub {
+    my $class   = shift;
+    my %options = @_;
+
+    (Scalar::Util::blessed $options{metaclass} && $options{metaclass}->isa('Class::MOP::Class'))
+        || confess "You must pass a metaclass instance if you want to inline"
+            if $options{is_inline};
+
+    ($options{package_name} && $options{name})
+        || confess "You must supply the package_name and name parameters";
+
+    # return the new object
+    my $self = $class->meta->new_object(%options);
+    
+    # we don't want this creating
+    # a cycle in the code, if not
+    # needed
+    Scalar::Util::weaken($self->{'$!associated_metaclass'});
+
+    $self->initialize_body;  
+    
+    $self;
+});
+
+$methods_to_reinstall{'Class::MOP::Method::Constructor'} = [qw[new]];
+
 ## --------------------------------------------------------
 ## Class::MOP::Instance
 
@@ -533,6 +775,28 @@ Class::MOP::Instance->meta->add_attribute(
 # time of the MOP, and gives us
 # no actual benefits.
 
+unless ($ENV{CLASS_MOP_NO_CACHE}) {
+    my %metaclasses_to_store = get_all_metaclasses();
+    $Storable::Deparse = 1;
+    Storable::nstore({
+        metas                => \%metaclasses_to_store,
+        methods_to_reinstall => \%methods_to_reinstall
+    }, $MOP_CACHE_FILE);
+    
+    #foreach my $meta_name (keys %metaclasses_to_store) {
+    #    my $metaclass = $metaclasses_to_store{$meta_name};
+    #    foreach my $method_to_reinstall (@{$methods_to_reinstall{$meta_name}}) {
+    #        warn "CHECKING $method_to_reinstall";
+    #
+    #        use Data::Dumper;
+    #        $Data::Dumper::Deparse = 1;
+    #        warn Dumper $metaclass->{'%!methods'}->{$method_to_reinstall};
+    #    }    
+    #}
+}
+
+}
+
 $_->meta->make_immutable(
     inline_constructor => 0,
     inline_accessors   => 0,
@@ -566,7 +830,7 @@ Class::MOP - A Meta Object Protocol for Perl 5
 
 =head1 DESCRIPTON
 
-This module is an attempt to create a meta object protocol for the
+This module is a fully functioning meta object protocol for the
 Perl 5 object system. It makes no attempt to change the behavior or
 characteristics of the Perl 5 object system, only to create a
 protocol for its manipulation and introspection.
@@ -694,7 +958,7 @@ programming. So in other words, don't worry about it.
 
 =head1 PROTOCOLS
 
-The protocol is divided into 3 main sub-protocols:
+The protocol is divided into 4 main sub-protocols:
 
 =over 4
 
@@ -710,7 +974,7 @@ See L<Class::MOP::Class> for more details.
 
 This provides a consistent represenation for an attribute of a
 Perl 5 class. Since there are so many ways to create and handle
-atttributes in Perl 5 OO, this attempts to provide as much of a
+attributes in Perl 5 OO, this attempts to provide as much of a
 unified approach as possible, while giving the freedom and
 flexibility to subclass for specialization.
 
@@ -725,6 +989,16 @@ making it possible to extend the system in many ways.
 
 See L<Class::MOP::Method> for more details.
 
+=item The Instance protocol
+
+This provides a layer of abstraction for creating object instances. 
+Since the other layers use this protocol, it is relatively easy to 
+change the type of your instances from the default HASH ref to other
+types of references. Several examples are provided in the F<examples/> 
+directory included in this distribution.
+
+See L<Class::MOP::Instance> for more details.
+
 =back
 
 =head1 FUNCTIONS
@@ -749,6 +1023,8 @@ compat.
 
 This will load a given C<$class_name> and if it does not have an
 already initialized metaclass, then it will intialize one for it.
+This function can be used in place of tricks like 
+C<eval "use $module"> or using C<require>.
 
 =item B<is_class_loaded ($class_name)>
 
@@ -761,8 +1037,27 @@ is probably correct about 99% of the time.
 
 =item B<check_package_cache_flag ($pkg)>
 
+This will return an integer that is managed by C<Class::MOP::Class>
+to determine if a module's symbol table has been altered. 
+
+In Perl 5.10 or greater, this flag is package specific. However in 
+versions prior to 5.10, this will use the C<PL_sub_generation> variable
+which is not package specific. 
+
 =item B<get_code_info ($code)>
 
+This function returns two values, the name of the package the C<$code> 
+is from and the name of the C<$code> itself. This is used by several 
+elements of the MOP to detemine where a given C<$code> reference is from.
+
+=item B<subname ($name, $code)>
+
+B<NOTE: DO NOT USE THIS FUNCTION, IT IS FOR INTERNAL USE ONLY!>
+
+If possible, we will load the L<Sub::Name> module and this will function 
+as C<Sub::Name::subname> does, otherwise it will just return the C<$code>
+argument.
+
 =back
 
 =head2 Metaclass cache functions
@@ -791,14 +1086,28 @@ been cached by B<Class::MOP::Class>.
 
 =item B<get_metaclass_by_name ($name)>
 
+This will return a cached B<Class::MOP::Class> instance of nothing
+if no metaclass exist by that C<$name>.
+
 =item B<store_metaclass_by_name ($name, $meta)>
 
+This will store a metaclass in the cache at the supplied C<$key>.
+
 =item B<weaken_metaclass ($name)>
 
+In rare cases it is desireable to store a weakened reference in 
+the metaclass cache. This function will weaken the reference to 
+the metaclass stored in C<$name>.
+
 =item B<does_metaclass_exist ($name)>
 
+This will return true of there exists a metaclass stored in the 
+C<$name> key and return false otherwise.
+
 =item B<remove_metaclass_by_name ($name)>
 
+This will remove a the metaclass stored in the C<$name> key.
+
 =back
 
 =head1 SEE ALSO