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

# "You will never find a more wretched hive of scum and villainy.
#  We must be careful."

use feature ':5.10';
use Storable qw/dclone/;
use Module::Load;
use Carp qw(confess);
use Scalar::Util qw(refaddr);

use constant DEBUG => 0;

BEGIN {
  if (DEBUG) {
    say STDERR "\n[!] Pony::Object DEBUGing mode is turning on!\n";
    
    *{dumper} = sub {
      use Data::Dumper;
      $Data::Dumper::Indent = 1;
      say Dumper(@_);
      say '=' x 79;
    }
  }
}

our $VERSION = 0.09;

# Var: $DEFAULT
#   Use it to redefine default Pony's options.
our $DEFAULT = {
  '' => {
    'withExceptions' => 0,
    'baseClass' => [],
  }
};

# Function: import
#   This function will runs on each use of this module.
#   It changes caller - adds new keywords,
#   makes caller more strict and modern,
#   create from simple package almost normal class.
#   Also it provides some useful methods.
#   
#   Don't forget: it's still OOP with blessed refs,
#   but now it looks better - more sugar for your code.

sub import {
  my $this = shift;
  my $call = caller;
  
  # Modify caller just once.
  # We suppose, that only we can create function ALL.
  return if defined *{$call.'::ALL'};
  
  # Parse parameters.
  my $default = dclone $DEFAULT;
  my $profile;
  for my $prefix (sort {length $b <=> length $a} keys %$DEFAULT) {
    if ($call =~ /^$prefix/) {
      $profile->{$_} = $default->{$prefix}->{$_}
        for grep {not exists $profile->{$_}} keys %{ $default->{$prefix} };
      next;
    }
    last if keys %{$default->{''}} == keys %{$default->{$call}};
  }
  $profile->{isAbstract} = 0; # don't do default object abstract.
  $profile->{isSingleton} = 0; # don't do default object singleton.
  
  $profile = parseParams($call, $profile, @_);
  
  # Keywords, base methods, attributes.
  predefine($call, $profile);
  
  # Pony objects must be strict and modern.
  strict  ->import;
  warnings->import;
  feature ->import(':5.10');
  
  # Base classes and params.
  prepareClass($call, "${call}::ISA", $profile);
  
  methodsInheritance($call);
  propertiesInheritance($call);
  
  *{$call.'::new'} = sub { importNew($call, @_) };
}


# Function: importNew
#  Constructor for Pony::Objects.
#
# Parameters:
#   $call - Str - caller package.
#
# Returns:
#   self

sub importNew {
  my $call = shift;
  
  if ($call->META->{isAbstract}) {
    confess "Trying to use an abstract class $call";
  } else {
    $call->AFTER_LOAD_CHECK;
  }
  # For singletons.
  return ${$call.'::instance'} if defined ${$call.'::instance'};
  
  my $this = shift;
  my $obj = dclone { %{${this}.'::ALL'} };
  while (my ($k, $p) = each %{$this->META->{properties}}) {
    if (grep {$_ eq 'static'} @{$p->{access}}) {
      tie $obj->{$k}, 'Pony::Object::TieStatic',
        $call->META->{static}, $k, $call->META->{static}->{$k} || $obj->{$k};
    }
  }
  $this = bless $obj, $this;
  
  ${$call.'::instance'} = $this if $call->META->{isSingleton};
  
  # 'After hook' for user.
  $this->init(@_) if $call->can('init');
  return $this;
}


# Function: parseParams
#   Load all base classes and read class params.
#
# Parameters:
#   $call    - Str      - caller package.
#   $profile - HashRef  - profile of this use.
#   @params  - Array    - import params.
#
# Returns:
#   HashRef - $profile

sub parseParams {
  my ($call, $profile, @params) = @_;
  
  for my $param (@params) {
    
    # Define singleton class.
    if ($param =~ /^-?singleton$/) {
      $profile->{isSingleton} = 1;
      next;
    }
    
    # Define abstract class.
    elsif ($param =~ /^-?abstract$/) {
      $profile->{isAbstract} = 1;
      next;
    }
    
    # Features:
    
    # Use exceptions featureset.
    elsif ($param =~ /^:exceptions?$/ || $param =~ /^:try$/) {
      $profile->{withExceptions} = 1;
      next;
    }
    
    # Don't use exceptions featureset.
    elsif ($param =~ /^:noexceptions?$/ || $param =~ /^:notry$/) {
      $profile->{withExceptions} = 0;
      next;
    }
    
    # Base classes:
    
    # Save class' base classes.
    else {
      push @{$profile->{baseClass}}, $param;
    }
  }
  
  return $profile;
}


# Function: prepareClass
#   Load all base classes and process class params.
#
# Parameters:
#   $call     - Str       - caller package.
#   $isaRef   - ArrayRef  - ref to @ISA.
#   $profile  - HashRef   - parsed params profile.

sub prepareClass {
  my ($call, $isaRef, $profile) = @_;

  $call->META->{isSingleton} = $profile->{isSingleton} // 0;
  $call->META->{isAbstract} = $profile->{isAbstract} // 0;

  for my $base (@{ $profile->{baseClass} }) {
    next if $call eq $base;
    load $base;
    $base->AFTER_LOAD_CHECK if $base->can('AFTER_LOAD_CHECK');
    push @$isaRef, $base;
  }
}


# Function: predefine
#   Predefine keywords and base methods.
#
# Parameters:
#   $call - Str - caller package.
#   $profile - HashRef

sub predefine {
  my ($call, $profile) = @_;
  
  # Predefine ALL and META.
  %{$call.'::ALL' } = ();
  %{$call.'::META'} = ();
  ${$call.'::META'}{isSingleton}= 0;
  ${$call.'::META'}{isAbstract} = 0;
  ${$call.'::META'}{abstracts}  = [];
  ${$call.'::META'}{methods}    = {};
  ${$call.'::META'}{properties} = {};
  ${$call.'::META'}{symcache}   = {};
  ${$call.'::META'}{checked}    = 0;
  ${$call.'::META'}{static}     = {};
  
  #====================
  # Define "keywords".
  #====================
  
  # Access for properties.
  *{$call.'::has'}      = sub { addProperty ($call, @_) };
  *{$call.'::static'}   = sub { addStatic   ($call, @_) };
  *{$call.'::public'}   = sub { addPublic   ($call, @_) };
  *{$call.'::private'}  = sub { addPrivate  ($call, @_) };
  *{$call.'::protected'}= sub { addProtected($call, @_) };
  
  # Try, Catch, Finally.
  # Define them if user wants.
  if ($profile->{withExceptions}) {
    *{$call.'::try'} = sub (&;@) {
      my($try, $catch, $finally) = @_;
      local $@;
      
      # If some one wanna to get some
      # values from try/catch/finally blocks.
      if (defined wantarray) {
        if (wantarray == 0) {
          my $ret = eval{ $try->() };
          $ret = $catch->($@) if $@;
          $ret = $finally->() if defined $finally;
          return $ret;
        }
        elsif (wantarray == 1) {
          my @ret = eval{ $try->() };
          @ret = $catch->($@) if $@;
          @ret = $finally->() if defined $finally;
          return @ret;
        }
      }
      else {
        eval{ $try->() };
        $catch->($@) if $@;
        $finally->() if defined $finally;
      }
    };
    *{$call.'::catch'} = sub (&;@) { @_ };
    *{$call.'::finally'} = sub (&) { @_ };
  }
  
  #=========================
  # Define special methods.
  #=========================
  
  # Getters for REFs to special variables %ALL and %META.
  *{$call.'::ALL'}  = sub { \%{ $call.'::ALL' } };
  *{$call.'::META'} = sub { \%{ $call.'::META'} };
  
  # This method provides deep copy
  # for Pony::Objects
  *{$call.'::clone'}  = sub { dclone shift };
  
  # Convert object's data into hash.
  # Uses ALL() to get properties' list.
  *{$call.'::toHash'} = *{$call.'::to_h'} = sub {
    my $this = shift;
    my %hash = map { $_, $this->{$_} } keys %{ $this->ALL() };
    return \%hash;
  };
  
  # Simple Data::Dumper wrapper.
  *{$call.'::dump'} = sub {
    use Data::Dumper;
    $Data::Dumper::Indent = 1;
    Dumper(@_);
  };
  
  *{$call.'::AFTER_LOAD_CHECK'} = sub { checkImplementations($call) };
  
  # Save method's attributes.
  *{$call.'::MODIFY_CODE_ATTRIBUTES'} = sub {
    my ($pkg, $ref, @attrs) = @_;
    my $sym = findsym($pkg, $ref);
    
    $call->META->{methods}->{ *{$sym}{NAME} } = {
      attributes => \@attrs,
      package => $pkg
    };
    
    for my $attr (@attrs) {
      if    ($attr eq 'Public'   ) { makePublic   ($pkg, $sym, $ref) }
      elsif ($attr eq 'Protected') { makeProtected($pkg, $sym, $ref) }
      elsif ($attr eq 'Private'  ) { makePrivate  ($pkg, $sym, $ref) }
      elsif ($attr eq 'Abstract' ) { makeAbstract ($pkg, $sym, $ref) }
    }
    return;
  };
}

# Function: methodsInheritance
#   Inheritance of methods.
#
# Parameters:
#   $this - Str - caller package.

sub methodsInheritance {
  my $this = shift;
  
  for my $base ( @{$this.'::ISA'} ) {
    # All Pony-like classes.
    if ($base->can('META')) {
      my $methods = $base->META->{methods};
      
      while (my($k, $v) = each %$methods) {
        $this->META->{methods}->{$k} = $v
          unless exists $this->META->{methods}->{$k};
      }
      
      # Abstract classes.
      if ($base->META->{isAbstract}) {
        my $abstracts = $base->META->{abstracts};
        push @{ $this->META->{abstracts} }, @$abstracts;
      }
    }
  }
}


# Function: checkImplementations
#   Check for implementing abstract methods
#   in our class in non-abstract classes.
#
# Parameters:
#   $this - Str - caller package.

sub checkImplementations {
  my $this = shift;
  
  return if $this->META->{checked};
  $this->META->{checked} = 1;
  
  # Check: does all abstract methods implemented.
  for my $base (@{$this.'::ISA'}) {
    if ( $base->can('META') && $base->META->{isAbstract} ) {
      my $methods = $base->META->{abstracts};
      my @bad;
      
      # Find Abstract methods,
      # which was not implements.
      for my $method (@$methods) {
        # Get Abstract methods.
        push @bad, $method
          if grep { $_ eq 'Abstract' }
            @{ $base->META->{methods}->{$method}->{attributes} };
        
        # Get abstract methods,
        # which doesn't implement.
        @bad = grep { !exists $this->META->{methods}->{$_} } @bad;
      }
      
      if (@bad) {
        my @messages = map
          {"Didn't find method ${this}::$_() defined in $base."}
            @bad;
        push @messages, "You should implement abstract methods before.\n";
        confess join("\n", @messages);
      }
    }
  }
}


# Function: addProperty
#   Guessing access type of property.
#
# Parameters:
#   $this - Str - caller package.
#   $attr - Str - name of property.
#   $value - Mixed - default value of property.

sub addProperty {
  my ($this, $attr, $value) = @_;
  
  # Properties
  if (ref $value ne 'CODE') {
    if ($attr =~ /^__/) {
      return addPrivate(@_);
    } elsif ($attr =~ /^_/) {
      return addProtected(@_);
    } else {
      return addPublic(@_);
    }
  }
  
  # Methods
  else {
    *{$this."::$attr"} = $value;
    my $sym = findsym($this, $value);
    my @attrs = qw/Public/;
    
    if ($attr =~ /^__/) {
      @attrs = qw/Private/;
      return makePrivate($this, $sym, $value);
    } elsif ($attr =~ /^_/) {
      @attrs = qw/Protected/;
      return makeProtected($this, $sym, $value);
    } else {
      return makePublic($this, $sym, $value);
    }
    
    $this->META->{methods}->{ *{$sym}{NAME} } = {
      attributes => \@attrs,
      package => $this
    };
  }
}


# Function: addStatic
#   Add static property or make property static.
#
# Parameters:
#   $call - Str - caller package.
#   $name - Str - property's name.
#   $value - Mixed - default value.
#
# Returns:
#   $name - Str - property's name.
#   $value - Mixed - default value.

sub addStatic {
  my $call = shift;
  my ($name, $value) = @_;
  push @{ $call->META->{statics} }, $name;
  addPropertyToMeta('static', $call, @_);
  return @_;
}


# Function: addPropertyToMeta
#   Save property's info into META
#
# Parameters:
#   $access - Str - property's access type.
#   $call - Str - caller package.
#   $name - Str - property's name.
#   $value - Mixed - property's default value.

sub addPropertyToMeta {
  my $access = shift;
  my $call = shift;
  my ($name, $value) = @_;
  
  my $props = $call->META->{properties};
  
  # Delete inhieritated properties for polymorphism.
  delete $call->META->{properties}->{$name} if
    exists $call->META->{properties}->{$name} &&
    $call->META->{properties}->{$name}->{package} ne $call;
  
  # Create if doesn't exist
  %$props = (%$props, $name => {access => []}) if
    not exists $props->{$name} ||
    ( $props->{$name}->{package} && $props->{$name}->{package} ne $call );
  
  push @{$props->{$name}->{access}}, $access;
  $props->{$name}->{package} = $call;
}


# Function: addPublic
#   Create public property with accessor.
#   Save it in special variable ALL.
#
# Parameters:
#   $call  - Str - caller package.
#   $name  - Str - name of property.
#   $value - Mixed - default value of property.

sub addPublic {
  my $call = shift;
  my ($name, $value) = @_;
  addPropertyToMeta('public', $call, @_);
  
  # Save pair (property name => default value)
  %{ $call.'::ALL' } = ( %{ $call.'::ALL' }, $name => $value );
  *{$call."::$name"} = sub : lvalue { my $call = shift; $call->{$name} };
  return @_;
}


# Function: addProtected
#   Create protected property with accessor.
#   Save it in special variable ALL.
#   Can die on wrong access attempt.
#
# Parameters:
#   $pkg  - Str - caller package.
#   $name - Str - name of property.
#   $value - Mixed - default value of property.

sub addProtected {
  my $pkg = shift;
  my ($name, $value) = @_;
  addPropertyToMeta('protected', $pkg, @_);
  
  # Save pair (property name => default value)
  %{$pkg.'::ALL'} = (%{$pkg.'::ALL'}, $name => $value);
  
  *{$pkg."::$name"} = sub : lvalue {
    my $this = shift;
    my $call = caller;
    confess "Protected ${pkg}::$name called"
      unless ($call->isa($pkg) || $pkg->isa($call)) and $this->isa($pkg);
    $this->{$name};
  };
  return @_;
}


# Function: addPrivate
#   Create private property with accessor.
#   Save it in special variable ALL.
#   Can die on wrong access attempt.
#
# Parameters:
#   $pkg  - Str - caller package.
#   $name - Str - name of property.
#   $value - Mixed - default value of property.

sub addPrivate {
  my $pkg = shift;
  my ($name, $value) = @_;
  addPropertyToMeta('private', $pkg, @_);
  
  # Save pair (property name => default value)
  %{ $pkg.'::ALL' } = ( %{ $pkg.'::ALL' }, $name => $value );
  
  *{$pkg."::$name"} = sub : lvalue {
    my $this = shift;
    my $call = caller;
    confess "Private ${pkg}::$name called"
      unless $pkg->isa($call) && $this->isa($pkg);
    $this->{$name};
  };
  return @_;
}

# Function: makeProtected
#   Function's attribute.
#   Uses to define, that this code can be used
#   only inside this class and his childs.
#
# Parameters:
#   $pkg - Str - name of package, where this function defined.
#   $symbol - Symbol - reference to perl symbol.
#   $ref - CodeRef - reference to function's code.

sub makeProtected {
  my ($pkg, $symbol, $ref) = @_;
  my $method = *{$symbol}{NAME};
  
  no warnings 'redefine';
  
  *{$symbol} = sub {
    my $this = $_[0];
    my $call = caller;
    confess "Protected ${pkg}::$method() called"
      unless ($call->isa($pkg) || $pkg->isa($call)) and $this->isa($pkg);
    goto &$ref;
  }
}

# Function: makePrivate
#   Function's attribute.
#   Uses to define, that this code can be used
#   only inside this class. NOT for his childs.
#
# Parameters:
#   $pkg - Str - name of package, where this function defined.
#   $symbol - Symbol - reference to perl symbol.
#   $ref - CodeRef - reference to function's code.

sub makePrivate {
  my ($pkg, $symbol, $ref) = @_;
  my $method = *{$symbol}{NAME};
  
  no warnings 'redefine';
  
  *{$symbol} = sub {
    my $this = $_[0];
    my $call = caller;
    confess "Private ${pkg}::$method() called"
      unless $pkg->isa($call) && $this->isa($pkg);
    goto &$ref;
  }
}


# Function: makePublic
#   Function's attribute.
#   Uses to define, that this code can be used public.
#
# Parameters:
#   $pkg - Str - name of package, where this function defined.
#   $symbol - Symbol - reference to perl symbol.
#   $ref - CodeRef - reference to function's code.

sub makePublic {
  # do nothing
}


# Function: makeAbstract
#   Function's attribute.
#   Define abstract attribute.
#   It means, that it doesn't conteins realisation,
#   but none abstract class, which will extends it,
#   MUST implement it.
#
# Parameters:
#   $pkg - Str - name of package, where this function defined.
#   $symbol - Symbol - reference to perl symbol.
#   $ref - CodeRef - reference to function's code.

sub makeAbstract {
  my ($pkg, $symbol, $ref) = @_;
  my $method = *{$symbol}{NAME};
  
  # Can't define abstract method
  # in none-abstract class.
  confess "Abstract ${pkg}::$method() defined in non-abstract class"
    unless $pkg->META->{isAbstract};
  
  # Push abstract method
  # into object meta.
  push @{ $pkg->META->{abstracts} }, $method;
  
  no warnings 'redefine';
  
  # Can't call abstract method.
  *{$symbol} = sub { confess "Abstract ${pkg}::$method() called" };
}


# Function: propertiesInheritance
#   This function calls when we need to get
#   properties (with thier default values)
#   form classes which our class extends to our class.
#
# Parameters:
#   $this - Str - caller package.

sub propertiesInheritance {
  my $this = shift;
  my %classes;
  my @classes = @{ $this.'::ISA' };
  my @base;
  my %props;
  
  # Get all parent's properties
  while (@classes) {
    my $c = pop @classes;
    next if exists $classes{$c};
    %classes = (%classes, $c => 1);
    push @base, $c;
    push @classes, @{$c.'::ISA'};
  }
  
  for my $base (reverse @base) {
    if ($base->can('ALL')) {
      # Default values
      my $all = $base->ALL();
      for my $k (keys %$all) {
        unless (exists ${$this.'::ALL'}{$k}) {
          %{$this.'::ALL'} = (%{$this.'::ALL'}, $k => $all->{$k});
        }
      }
      # Statics
      $all = $base->META->{properties};
      for my $k (keys %$all) {
        unless (exists $this->META->{properties}->{$k}) {
          %{$this->META->{properties}} = (%{$this->META->{properties}},
            $k => $base->META->{properties}->{$k});
        }
      }
    }
  }
}


# Function: findsym
#   Get perl symbol by ref.
#
# Parameters:
#   $pkg - Str - package, where it defines.
#   $ref - CodeRef - reference to method.
#
# Returns:
#   Symbol

sub findsym {
  my ($pkg, $ref) = @_;
  my $symcache = $pkg->META->{symcache};
  
  return $symcache->{$pkg, $ref} if $symcache->{$pkg, $ref};
  
  my $type = 'CODE';
  
  for my $sym (values %{$pkg."::"}) {
    next unless ref ( \$sym ) eq 'GLOB';
    
    return $symcache->{$pkg, $ref} = \$sym
      if *{$sym}{$type} && *{$sym}{$type} == $ref;
  }
}


###############################################################################
# Class: Pony::Object::TieStatic
#   Tie class. Use for make properties are static.
package Pony::Object::TieStatic;


# Method: TIESCALAR
#   tie constructor
#
# Parameters:
#   $storage - HashRef - data storage
#   $name - Str - property's name
#   $val - Mixed - Init value
#
# Returns:
#   Pony::Object::TieStatic

sub TIESCALAR {
  my $class = shift;
  my ($storage, $name, $val) = @_;
  $storage->{$name} = $val unless exists $storage->{$name};

  bless {name => $name, storage => $storage}, $class;
}


# Method: FETCH
#   Defines fetch for scalar.
#
# Returns:
#   Mixed - property's value

sub FETCH {
  my $self = shift;
  return $self->{storage}->{ $self->{name} };
}


# Method: STORE
#   Defines store for scalar.
#
# Parameters:
#   $val - Mixed - property's value

sub STORE {
  my $self = shift;
  my $val = shift;
  $self->{storage}->{ $self->{name} } = $val;
}

1;

__END__

=head1 NAME

Pony::Object - An object system.

=head1 OVERVIEW

If you wanna protected methods, abstract classes and other staff like with, you
may use Pony::Object. Also Pony::Objects are strict and modern.

=head1 SYNOPSIS

  # Class: MyArticle (Example)
  #   Abstract class for articles.
  
  package MyArticle;
  use Pony::Object qw(-abstract :exceptions);
  use MyArticle::Exception::IO; # Based on Pony::Object::Throwable class.
    
    protected date => undef;
    protected authors => [];
    public title => '';
    public text => '';
    
    
    # Function: init
    #   Constructor.
    #
    # Parameters:
    #   date - Int
    #   authors - ArrayRef
    
    sub init : Public
      {
        my $this = shift;
        ($this->date, $this->authors) = @_;
      }
    
    
    # Function: getDate
    #   Get formatted date.
    #
    # Returns:
    #   Str
    
    sub getDate : Public
      {
        my $this = shift;
        return $this->dateFormat($this->date);
      }
    
    
    # Function: dateFormat
    #   Convert Unix time to good looking string. Not implemented.
    #
    # Parameters:
    #   date - Int
    #
    # Returns:
    #   String
    
    sub dateFormat : Abstract;
    
    
    # Function: fromPdf
    #   Trying to create article from pdf file.
    #
    # Parameters:
    #   file - Str - pdf file.
    
    sub fromPdf : Public
      {
        my $this = shift;
        my $file = shift;
        
        try {
          open F, $file or
            throw MyArticle::Exception::IO(action => "read", file => $file);
          
          # do smth
          
          close F;
        } catch {
          my $e = shift; # get exception object
          
          if ($e->isa('MyArticle::Exception::IO')) {
            # handler for MyArticle::Exception::IO exceptions
          }
        };
      }
    
  1;

=head1 Methods and properties

=head2 has

Keyword C<has> declares new property. You also can define methods via C<has>.

  package News;
  use Pony::Object;
    
    # Properties:
    has 'title';
    has text => '';
    has authors => [ qw/Alice Bob/ ];
    
    # Methods:
    has printTitle => sub {
      my $this = shift;
      say $this->title;
    };
    
    sub printAuthors
      {
        my $this = shift;
        print @{$this->authors};
      }
  1;



  package main;
  use News;
  my $news = new News;
  $news->printAuthors();
  $news->title = 'Sensation!'; # Yep, you can assign property's value via "=".
  $news->printTitle();

=head2 new

Pony::Objects hasn't method C<new>. In fact, of course they has. But C<new> is an
internal function, so you should not use C<new> as name of method.

Instead of this Pony::Objects has C<init> methods, where you can write the same,
what you wish write in C<new>. C<init> is after-hook for C<new>.

  package News;
  use Pony::Object;
    
    has title => undef;
    has lower => undef;
    
    sub init
      {
        my $this = shift;
        $this->title = shift;
        $this->lower = lc $this->title;
      }
    
  1;



  package main;
  use News;
  my $news = new News('Big Event!');
  print $news->lower;

=head2 public, protected, private properties

You can use C<has> keyword to define property. If your variable starts with "_", variable becomes 
protected. "__" for private.

  package News;
  use Pony::Object;
  
    has text => '';
    has __authors => [ qw/Alice Bob/ ];
    
    sub getAuthorString
      {
        my $this = shift;
        return join(' ', @{$this->__authors});
      }
    
  1;



  package main;
  use News;
  my $news = new News;
  say $news->getAuthorString();

The same but with keywords C<public>, C<protected> and C<private>.

  package News;
  use Pony::Object;
    
    public text => '';
    private authors => [ qw/Alice Bob/ ];
    
    sub getAuthorString
      {
        my $this = shift;
        return join(' ', @{$this->authors});
      }
    
  1;



  package main;
  use News;
  my $news = new News;
  say $news->getAuthorString();

=head2 Public, Protected, Private methods

Use attributes C<Public>, C<Private> and C<Protected> to define method's access type.

  package News;
  use Pony::Object;
    
    public text => '';
    private authors => [ qw/Alice Bob/ ];
    
    sub getAuthorString : Public
      {
        return shift->joinAuthors(', ');
      }
    
    sub joinAuthors : Private
      {
        my $this = shift;
        my $delim = shift;
        
        return join( $delim, @{$this->authors} );
      }
    
  1;



  package main;
  use News;
  my $news = new News;
  say $news->getAuthorString();

=head2 Static properties

Just say "C<static>" and property will the same in all objects of class.

  package News;
  use Pony::Object;
    
    public static 'default_publisher' => 'Georgy';
    public 'publisher';
    
    sub init : Public
      {
        my $this = shift;
        $this->publisher = $this->default_publisher;
      }
    
  1;



  package main;
  use News;
  
  my $n1 = new News;
  $n1->default_publisher = 'Bazhukov';
  my $n2 = new News;
  print $n1->publisher; # "Georgy"
  print $n2->publisher; # "Bazhukov"

=head1 Default methods

=head2 toHash or to_h

Get object's data structure and return this as a hash.

  package News;
  use Pony::Object;
    
    has title => 'World';
    has text => 'Hello';
    
  1;



  package main;
  use News;
  my $news = new News;
  print $news->toHash()->{text};
  print $news->to_h()->{title};

=head2 dump

Shows object's current struct.

  package News;
  use Pony::Object;
    
    has title => 'World';
    has text => 'Hello';
    
  1;



  package main;
  use News;
  my $news = new News;
  $news->text = 'Hi';
  print $news->dump();

Returns

  $VAR1 = bless( {
    'text' => 'Hi',
    'title' => 'World'
  }, 'News' );

=head1 Classes

=head2 Inheritance

You can define base classes via C<use> params.
For example, C<use Pony::Object 'Base::Class';>

  package BaseCar;
  use Pony::Object;
    
    public speed => 0;
    protected model => "Base Car";
    
    sub get_status_line : Public
      {
        my $this = shift;
        my $status = ($this->speed ? "Moving" : "Stopped");
        return $this->model . " " . $status;
      }
    
  1;



  package MyCar;
  # extends BaseCar
  use Pony::Object qw/BaseCar/;
    
    protected model => "My Car";
    protected color => undef;
    
    sub set_color : Public
      {
        my $this = shift;
        ($this->color) = @_;
      }
    
  1;



  package main;
  use MyCar;
  my $car = new MyCar;
  $car->speed = 20;
  $car->set_color("White");
  print $car->get_status_line();
  # "My Car Moving"

=head2 Singletons

Pony::Object has simple syntax for singletons . You can declare this via C<use> param;

  package Notes;
  use Pony::Object 'singleton';
    
    protected list => [];
    
    sub add : Public
      {
        my $this = shift;
        push @{ $this->list }, @_;
      }
    
    sub show : Public
      {
        my $this = shift;
        say for @{$this->list};
      }
    
    sub flush : Public
      {
        my $this = shift;
        $this->list = [];
      }
    
  1;



  package main;
  use Notes;
  
  my $n1 = new Notes;
  my $n2 = new Notes;
  
  $n1->add(qw/eat sleep/);
  $n1->add('Meet with Mary at 8 o`clock');
  
  $n2->flush;
  
  $n1->show();  # Print nothing.
                # Em... When I should meet Mary? 

=head2 Abstract methods and classes

You can use abstract methods and classes follows way:

  # Let's define simple interface for texts.
  package Text::Interface;
  use Pony::Object -abstract; # Use 'abstract' or '-abstract'
                              # params to define abstract class.
    
    sub getText : Abstract; # Use 'Abstract' attribute to
    sub setText : Abstract; # define abstract method.
    
  1;



  # Now we can define base class for texts.
  # It's abstract too but now it has some code.
  package Text::Base;
  use Pony::Object qw/abstract Text::Interface/;
    
    protected text => '';
    
    sub getText : Public
      {
        my $this = shift;
        return $this->text;
      }
    
  1;



  # In the end we can write Text class.
  package Text;
  use Pony::Object 'Text::Base';
    
    sub setText : Public
      {
        my $this = shift;
        $this->text = shift;
      }
    
  1;



  # Main file.
  package main;
  use Text;
  use Text::Base;
  
  my $textBase = new Text::Base;  # Raises an error!
  
  my $text = new Text;
  $text->setText('some text');
  print $text->getText();   # Returns 'some text';

Don't forget, that perl looking for functions from left to right in list of
inheritance. You should define abstract classes in the end of
Pony::Object param list.

=head2 Exceptions

See L<Pony::Object::Throwable>.

=head2 Inside

=head3 ALL

If you wanna get all default values of Pony::Object-based class,
you can call C<ALL> method. I don't know why you need them, but you can.

  package News;
  use Pony::Object;
    
    has 'title';
    has text => '';
    has authors => [ qw/Alice Bob/ ];
    
  1;



  package main;
  my $news = new News;
  print for keys %{ $news->ALL() };

=head3 META

One more internal method. It provides access to special hash C<%META>.
You can use this for Pony::Object introspection. It can be changed in next versions.

  my $news = new News;
  say dump $news->META;

=head3 $Pony::Object::DEFAULT

This is a global variable. It defines default Pony::Object's params. For example you can set
C<$Pony::Object::DEFAULT->{''}->{withExceptions} = 1> to enable exceptions
(try, catch, finally blocks) by default.
Use it carefully.

  # Startup script
  ...
  use Pony::Object;
  
  BEGIN {
    # Use exceptions by default.
    $Pony::Object::DEFAULT->{''}->{withExceptions} = 1;
    # All classes will extends Default::Base.
    $Pony::Object::DEFAULT->{''}->{baseClass} = [qw/Default::Base/];
    # All classes in namespace "Default::NoBase" will not.
    $Pony::Object::DEFAULT->{'Default::NoBase'}->{baseClass} = [];
  }
  ...

One more example:

  # Startup script
  ...
  use Pony::Object;
  
  BEGIN {
    $Pony::Object::DEFAULT->{'My::Awesome::Project'} = {
      withExceptions => 1,
      baseClass => [],
    };
    
    $Pony::Object::DEFAULT->{'My::Awesome::Project::Model'} = {
      withExceptions => 1,
      baseClass => [qw/My::Awesome::Project::Model::Abstract/],
    };
  }
  ...

=head1 SEE

=over

=item Git

L<https://github.com/h15/pony-object>

=back

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2011 - 2013, Georgy Bazhukov.

This program is free software, you can redistribute it and/or modify it under
the terms of the Artistic License version 2.0.

=cut