view Lib/IMPL/Serialization.pm @ 245:7c517134c42f

Added Unsupported media type Web exception corrected resourceLocation setting in the resource Implemented localizable resources for text messages fixed TT view scopings, INIT block in controls now sets globals correctly.
author sergey
date Mon, 29 Oct 2012 03:15:22 +0400
parents c477f24f1980
children 4ddb27ff4a0b
line wrap: on
line source

package IMPL::Serialization;
use strict;

package IMPL::Serialization::Context;
use parent qw(IMPL::Object);

use IMPL::Class::Property;
use IMPL::Class::Property::Direct;
use IMPL::Exception;
use Scalar::Util qw(refaddr);

BEGIN {
  private _direct property ObjectWriter => prop_all;
  private _direct property Context => prop_all;
  private _direct property NextID => prop_all;

  public _direct property Serializer => prop_all;
  
  private _direct property State => prop_all;
}

sub STATE_CLOSED () { 0 }
sub STATE_OPENED () { 1 }
sub STATE_COMPLEX () { 2 }
sub STATE_DATA () { 3 }

sub CTOR {
  my ($this,%args) = @_;
  
  $this->{$ObjectWriter} = $args{'ObjectWriter'};
  $this->{$NextID} = 1;
  $this->{$Serializer} = ($args{'Serializer'} ? $args{'Serializer'} : \&DefaultSerializer );
  $this->{$State} = STATE_CLOSED;
  
  return 1;
}

sub AddVar {
  my ($this,$sName,$Var) = @_;
  
  die new Exception ('Invalid operation') if $this->{$State} == STATE_DATA;
  
  if (not ref $Var) {
    my $prevState = $this->{$State};
    
    $this->{$ObjectWriter}->BeginObject(name => $sName);
    $this->{$State} = STATE_OPENED;
    
    $this->{$Serializer}->($this,\$Var);
    
    $this->{$ObjectWriter}->EndObject();
    
    if ($prevState == STATE_OPENED) {
      $this->{$State} = STATE_COMPLEX;
    } else {
      $this->{$State} = $prevState;
    }
    return 0;
  }
  
  my $PrevState = $this->{$State};
  
  my $ObjID = $this->{$Context}->{refaddr $Var};
  if ($ObjID) {
    $this->{$ObjectWriter}->BeginObject(name => $sName, refid => $ObjID);
    $this->{$ObjectWriter}->EndObject();
    return $ObjID;
  }
  
  $ObjID = $this->{$NextID};
  $this->{$NextID} = $ObjID + 1;
  
  $this->{$Context}->{refaddr $Var} = $ObjID;
  
  $this->{$ObjectWriter}->BeginObject(name => $sName, type => ref($Var), id => $ObjID);
  
  $this->{$State} = STATE_OPENED;
  $this->{$Serializer}->($this,$Var);
  
  $this->{$ObjectWriter}->EndObject();
  
  if ($PrevState == STATE_OPENED) {
    $this->{$State} = STATE_COMPLEX;
  } else {
    $this->{$State} = $PrevState;
  }  
  
  return $ObjID;
}

sub SetData {
  my ($this,$Data,$Type) = @_;
  
  die new Exception ('The object should be a scalar value') if ref $Data;
  die new Exception ('Invalid operation') if $this->{$State} != STATE_OPENED;
  
  $this->{$ObjectWriter}->SetData($Data,$Type);
  
  $this->{$State} = STATE_DATA;
  
  return 1;
}

sub DefaultSerializer {
  my ($Context, $refObj) = @_;
  
  if (ref($refObj) eq 'SCALAR') {
    $Context->SetData($$refObj, 'SCALAR');
  } elsif (ref($refObj) eq 'ARRAY') {
    $Context->AddVar('item',$_) foreach @$refObj;
  } elsif (ref($refObj) eq 'HASH') {
    while (my ($key,$value) = each %$refObj) {
      $Context->AddVar($key,$value);
    }
  } elsif (ref($refObj) eq 'REF') {
    $Context->AddVar('ref',$$refObj);
  } else {
    if (ref $refObj and $refObj->UNIVARSAL::can('save')) {
      $refObj->save($Context);
    } else {
      die new Exception('Cant serialize the object of the type: '.ref($refObj));
    }
  }
  
  return 1;
}

package IMPL::Deserialization::Context;
use parent qw(IMPL::Object);

use IMPL::Class::Property;
use IMPL::Class::Property::Direct;
use IMPL::Exception;

BEGIN {
  private _direct property Context => prop_all;

  # структура информации об объекте
  # {
  #   Type => 'typename',
  #   Name => 'object_name',
  #   Data => $Data,
  #   Id => 'object_id'
  # }
  private _direct property CurrentObject => prop_all;

  private _direct property ObjectsPath => prop_all;

  public _direct property Root => prop_get;

  # пїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅ пїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅ пїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅ
  # ObjectFactory($Type,$DeserializationData,$refSurogate)
  # $Type - пїЅпїЅпїЅ пїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅ
  # $DeserializationData - пїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅ пїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅ,
  # пїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅ.
  # $refSurogate - пїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ, пїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅ.
  # пїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ undef
  private _direct property ObjectFactory => prop_all;

  # пїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅ.
  # SurogateHelper($Type)
  # $Type пїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅ, пїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅ.
  private _direct property SurrogateHelper => prop_all;
}

sub CTOR {
  my ($this,%args) = @_;
  $this->{$CurrentObject} = undef;
  $this->{$Root} = undef;
  $this->{$ObjectFactory} = $args{ObjectFactory} if $args{ObjectFactory};
  $this->{$SurrogateHelper} = $args{SurrogateHelper} if $args{SurrogateHelper};
}

sub OnObjectBegin {
  my ($this,$name,$rhProps) = @_;
  
  die new Exception("Invalid data from an ObjectReader","An object reader should pass a referense to a hash which contains attributes of an object") if (ref $rhProps ne 'HASH');
  die new Exception("Trying to create second root object") if not $this->{$CurrentObject} and $this->{$Root};
  
  if ($rhProps->{'refid'}) {
    my $refObj = $this->{$Context}->{$rhProps->{'refid'}};
    die new Exception("A reference to a not existing object found") if not $refObj;
    my $rhCurrentObj = $this->{$CurrentObject};
    
    die new Exception("The root object can't be a reference") if not $rhCurrentObj;
    
    if ($rhCurrentObj->{'Data'}) {
      die new Exception("Invalid serializaed data","Plain deserialization data for an object already exist") if not ref $rhCurrentObj->{'Data'};
      push @{$rhCurrentObj->{'Data'}}, $name,$refObj;
    } else {
      $rhCurrentObj->{'Data'} = [$name,$refObj];
    }

    # пїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅ, пїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅ OnObjectEnd пїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅ, пїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅ. пїЅ.пїЅ. пїЅпїЅ пїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅ
    push @{$this->{$ObjectsPath}},$rhCurrentObj;
    $this->{$CurrentObject} = undef;
    
  } else {
    push @{$this->{$ObjectsPath}},$this->{$CurrentObject} if $this->{$CurrentObject};
    
    $this->{$CurrentObject} = {
                          Name => $name,
                          Type => $rhProps->{'type'} || 'SCALAR',
                          Id => $rhProps->{'id'},
                          refId => $rhProps->{'refid'}
                          };

    if (defined $rhProps->{'id'}) {                          
        die new IMPL::Exception("Trying to create a simple object instead of a reference, type is missing.",$name,$rhProps->{id}) unless $rhProps->{'type'} ;
        $this->{$Context}->{$rhProps->{'id'}} = $this->{$SurrogateHelper} ? $this->{$SurrogateHelper}->($rhProps->{'type'}) : DefaultSurrogateHelper($rhProps->{'type'});
    }
  }
  
  return 1;
}

sub OnObjectData {
  my ($this,$data) = @_;
  
  my $rhObject = $this->{$CurrentObject};
  
  die new Exception("Trying to set data for an object which not exists") if not $rhObject;
  
  die new Exception("Deserialization data already exists for a current object", "ObjectName= $rhObject->{'Name'}") if $rhObject->{'Data'};
  
  $rhObject->{'Data'} = $data;
  
  return 1;
}
{
  my $AutoId = 0;
  sub OnObjectEnd {
    my ($this,$name) = @_;
    
    my $rhObject = $this->{$CurrentObject};
    my $rhPrevObject = pop @{$this->{$ObjectsPath}};
    
    # пїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ, пїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ - пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ, пїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅ - пїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅ
    # пїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅ пїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅ пїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅ
    if ((not defined($rhObject)) && $rhPrevObject) {
      $this->{$CurrentObject} = $rhPrevObject;
      return 1;
    }
    
    my $refObj = $this->{$ObjectFactory} ?$this->{$ObjectFactory}->($rhObject->{'Type'},$rhObject->{'Data'},$rhObject->{'Id'} ? $this->{$Context}->{$rhObject->{'Id'}} : undef) : DefaultFactory($rhObject->{'Type'},$rhObject->{'Data'},$rhObject->{'Id'} ? $this->{$Context}->{$rhObject->{'Id'}} : undef);
      
    die new Exception("Trying to close a non existing oject") if not $rhObject;
  
    my $Data;
    
    if ($rhObject->{'Id'}) {
      $this->{$Context}->{$rhObject->{'Id'}} = $refObj;
      $Data = $refObj;
    } else {
      if (ref $refObj ne 'SCALAR') {
        $rhObject->{Id} = "auto$AutoId";
        $AutoId ++;
        $this->{$Context}->{$rhObject->{'Id'}} = $refObj;
        $Data = $refObj;
      } else {
        $Data = ${$refObj};
      }
    }
      
    if (not $rhPrevObject) {
      $this->{$Root} = $Data;
    } else {
      if ($rhPrevObject->{'Data'}) {
        die new Exception("Trying append a reference to an object to the plain data") if not ref $rhPrevObject->{'Data'};
        push @{$rhPrevObject->{'Data'}},$rhObject->{'Name'},$Data;
      } else {
        $rhPrevObject->{'Data'} = [$rhObject->{'Name'},$Data];
      }
    }
    
    $this->{$CurrentObject} = $rhPrevObject;
    
    return 1;
  }
}

{
	my %classes;
	sub _load_class {
		return if $classes{$_[0]};
		
		die new IMPL::Exception("Invalid class name",$_[0]) unless $_[0] =~ m/^(\w+(?:\:\:\w+)*)$/;
		
		eval "require $1";
		$classes{$_[0]} = 1;
	}
}

sub DefaultSurrogateHelper {
  my ($Type) = @_;
  
  if ($Type eq 'SCALAR' or $Type eq 'REF') {
    my $var;
    return \$var;
  } elsif ($Type eq 'ARRAY') {
    return [];
  } elsif ($Type eq 'HASH') {
    return {};
  } elsif ($Type) {
    _load_class($Type);
    if (UNIVERSAL::can($Type,'surrogate')) {
      return $Type->surrogate();
    } else {
      return bless {}, $Type;
    }
  }
}

# deserialization context:
# [
#   'var_name',value,
#   ....
# ]

sub DefaultFactory {
  my ($Type,$Data,$refSurogate) = @_;
  
  if ($Type eq 'SCALAR') {
    die new Exception("SCALAR needs a plain data for a deserialization") if ref $Data;
    if ($refSurogate) {
      $$refSurogate = $Data;
      return $refSurogate;
    } else {
      return \$Data;
    }
  } elsif ($Type eq 'ARRAY') {
  	$Data ||= [];
    die new Exception("Invalid a deserialization context when deserializing ARRAY") if not ref $Data and defined $Data;
    if (not ref $refSurogate) {
      my @Array;
      $refSurogate = \@Array;
    }
    for (my $i = 0; $i < scalar(@{$Data})/2; $i++) {
      push @$refSurogate,$Data->[$i*2+1];
    }
    return $refSurogate;
  } elsif ($Type eq 'HASH') {
  	$Data ||= [];
    die new Exception("Invalid a deserialization context when deserializing HASH") if not ref $Data and defined $Data;
    if (not ref $refSurogate) {
      $refSurogate = {};
    }
    for (my $i = 0; $i< @$Data; $i+= 2) {
      $refSurogate->{$Data->[$i]} = $Data->[$i+1];
    }
    return $refSurogate;
  } elsif ($Type eq 'REF') {
  	$Data ||= [];
    die new Exception("Invalid a deserialization context when deserializing REF") if not ref $Data and defined $Data;
    if (not ref $refSurogate) {
      my $ref = $Data->[1]; 
      return \$ref;
    } else {
      $$refSurogate = $Data->[1];
      return $refSurogate;
    }
  } else {
    _load_class($Type);
    if ( $Type->UNIVERSAL::can('restore') ) {
      return $Type->restore($Data,$refSurogate);
    } else {
      die new Exception("Don't know how to deserialize $Type");
    }
  }
}

package IMPL::Serializer;
use parent qw(IMPL::Object);

use IMPL::Class::Property;
use IMPL::Class::Property::Direct;
use IMPL::Exception;

BEGIN {
  private _direct property Formatter => prop_all;
}

sub CTOR {
  my ($this,%args) = @_;
  $this->Formatter($args{'Formatter'}) or die new Exception("Omitted mandatory parameter 'Formatter'");
}

sub Serialize {
  my $this = shift;
  my ($hStream,$Object) = @_;
  my $ObjWriter = $this->Formatter()->CreateWriter($hStream);
  my $Context = new IMPL::Serialization::Context(ObjectWriter => $ObjWriter);
  $Context->AddVar('root',$Object);
  return 1;
}

sub Deserialize {
  my $this = shift;
  my ($hStream) = @_;
  my $Context = new IMPL::Deserialization::Context();
  my $ObjReader = $this->Formatter()->CreateReader($hStream,$Context);
  $ObjReader->Parse();
  return $Context->Root();
}

1;