diff lib/IMPL/Serialization.pm @ 407:c6e90e02dd17 ref20150831

renamed Lib->lib
author cin
date Fri, 04 Sep 2015 19:40:23 +0300
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/IMPL/Serialization.pm	Fri Sep 04 19:40:23 2015 +0300
@@ -0,0 +1,449 @@
+package IMPL::Serialization;
+use strict;
+
+package IMPL::Serialization::Context;
+
+use IMPL::Exception();
+use Scalar::Util qw(refaddr);
+
+use IMPL::Const qw(:prop);
+use IMPL::declare {
+    base  => [ 'IMPL::Object' => undef ],
+    props => [
+        _objectWriter => PROP_RW | PROP_DIRECT,
+        _context      => PROP_RW | PROP_DIRECT,
+        _nextId       => PROP_RW | PROP_DIRECT,
+        serializer    => PROP_RW | PROP_DIRECT,
+        _state        => PROP_RW | PROP_DIRECT
+    ]
+};
+
+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 IMPL::Const qw(:prop);
+use IMPL::declare {
+    require => {
+        Exception => 'IMPL::Exception',
+        Loader    => 'IMPL::Code::Loader'
+      },
+      base  => [ 'IMPL::Object' => undef ],
+      props => [
+
+        # структура информации об объекте
+        # {
+        #   Type => 'typename',
+        #   Name => 'object_name',
+        #   Data => $data,
+        #   Id => 'object_id'
+        # }
+        _context       => PROP_RW | PROP_DIRECT,
+        _currentObject => PROP_RW | PROP_DIRECT,
+        _objectsPath   => PROP_RW | PROP_DIRECT,
+        root           => PROP_RW | PROP_DIRECT
+      ]
+};
+
+sub CTOR {
+    my ( $this, %args ) = @_;
+    $this->{$_currentObject} = undef;
+    $this->{$root}           = undef;
+}
+
+sub OnObjectBegin {
+    my ( $this, $name, $rhProps ) = @_;
+
+    die Exception->new(
+        "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 Exception->new("Trying to create second root object")
+      if not $this->{$_currentObject} and $this->{$root};
+
+    if ( $rhProps->{'refid'} ) {
+    
+        my $refObj = $this->{$_context}->{ $rhProps->{'refid'} };
+    
+        die Exception->new("A reference to a not existing object found")
+          if not $refObj;
+    
+        my $rhCurrentObj = $this->{$_currentObject};
+
+        die Exception->new("The root object can't be a reference")
+          if not $rhCurrentObj;
+
+        if ( $rhCurrentObj->{'Data'} ) {
+    
+            die Exception->new( "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 ];
+        }
+
+        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->CreateSurrogate( $rhProps->{'type'} );
+        }
+    }
+
+    return 1;
+}
+
+sub OnObjectData {
+    my ( $this, $data ) = @_;
+
+    my $rhObject = $this->{$_currentObject};
+
+    die Exception->new("Trying to set data for an object which not exists")
+      if not $rhObject;
+
+    #die Exception->new(
+    #    "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->CreateObject(
+            $rhObject->{'Type'},
+            $rhObject->{'Data'},
+            $rhObject->{'Id'}
+            ? $this->{$_context}->{ $rhObject->{'Id'} }
+            : undef
+          );
+
+        die Exception->new("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 Exception->new(
+                    "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;
+    }
+}
+
+sub CreateSurrogate {
+    my ($this,$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) {
+        Loader->safe->Require($type);
+        if ( eval { $type->can('surrogate') } ) {
+            return $type->surrogate();
+        }
+        else {
+            return bless {}, $type;
+        }
+    }
+}
+
+# deserialization context:
+# [
+#   'var_name',value,
+#   ....
+# ]
+
+sub CreateObject {
+    my ($this, $type, $data, $refSurogate ) = @_;
+
+    if ( $type eq 'SCALAR' ) {
+        die Exception->new("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 Exception->new(
+            "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 Exception->new(
+            "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 Exception->new(
+            "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 {
+        Loader->safe->Require($type);
+        if ( eval { $type->can('restore') } ) {
+            return $type->restore( $data, $refSurogate );
+        }
+        else {
+            die Exception->new("Don't know how to deserialize $type");
+        }
+    }
+}
+
+package IMPL::Serializer;
+
+use IMPL::Const qw(:prop);
+use IMPL::declare {
+    require => {
+        Exception => 'IMPL::Exception',
+        SerializationContext => '-IMPL::Serialization::Context',
+        DeserializationContext => '-IMPL::Deserialization::Context'
+    },
+    base => [
+        'IMPL::Object' => undef
+    ],
+    props => [
+        _formatter => PROP_RW
+    ]
+};
+
+sub CTOR {
+    my ( $this, %args ) = @_;
+    $this->_formatter( $args{formatter} )
+      or die Exception->new("Omitted mandatory parameter 'formatter'");
+}
+
+sub Serialize {
+    my $this = shift;
+    my ( $hStream, $Object ) = @_;
+    my $ObjWriter = $this->_formatter->CreateWriter($hStream);
+    my $context =
+      SerializationContext->new( objectWriter => $ObjWriter );
+    $context->AddVar( 'root', $Object );
+    return 1;
+}
+
+sub Deserialize {
+    my $this      = shift;
+    my ($hStream) = @_;
+    my $context  = DeserializationContext->new();
+    my $ObjReader = $this->_formatter->CreateReader( $hStream, $context );
+    $ObjReader->Parse();
+    return $context->root;
+}
+
+1;