Mercurial > pub > Impl
diff Lib/IMPL/Serialization.pm @ 0:03e58a454b20
Создан репозитарий
author | Sergey |
---|---|
date | Tue, 14 Jul 2009 12:54:37 +0400 |
parents | |
children | 16ada169ca75 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Lib/IMPL/Serialization.pm Tue Jul 14 12:54:37 2009 +0400 @@ -0,0 +1,409 @@ + +# 20060222 +# Ìîäóëü äëÿ ñåðèàëèçàöèè ñòðóêòóð äàííûõ +# (ö) Sourcer, cin.sourcer@gmail.com +# revision 3 (20090517) + + +package IMPL::Serialization; +use strict; + +package IMPL::Serialization::Context; +use base 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;# ñëåäóþùèé èäåíòèôèêàòîð äëÿ îáúåêòà + + # ïðîöåäóðà, êîòîðàÿ çíàåò, êàê ñåðèàëèçîâûâàòü îïðåäåëåííûå òèïû. Ïåðâûì ïàðàìåòðîì + # ïîëó÷àåì ññûëêó íà IMPL::Serialization::Context, âòîðûì ïàðàìåòðîì ññûëêó íà îáúåêò + 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->{$Context} = {}; + $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);#, type => 'SCALAR'); + $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 base 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 SurogateHelper => prop_all; +} + +sub CTOR { + my ($this,%args) = @_; + $this->{$CurrentObject} = undef; + $this->{$Root} = undef; +} + +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("Found a reference to an object as a root of an object's graph") 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'} + }; + $this->{$Context}->{$rhProps->{'id'}} = $this->{$SurogateHelper} ? $this->{$SurogateHelper}->($rhProps->{'type'}) : DefaultSurogateHelper($rhProps->{'type'}) if defined $rhProps->{'id'}; + } + + 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; + } +} + +sub _is_class { + no strict 'refs'; + scalar keys %{"$_[0]::"} ? 1 : 0; +} + +sub DefaultSurogateHelper { + my ($Type) = @_; + + if ($Type eq 'SCALAR' or $Type eq 'REF') { + my $var; + return \$var; + } elsif ($Type eq 'ARRAY') { + return []; + } elsif ($Type eq 'HASH') { + return {}; + } else { + eval "require $Type" unless _is_class($Type); + if ($Type->UNIVERSAL::can('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') { + 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') { + 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') { + 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 { + eval "require $Type" unless _is_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 base 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; \ No newline at end of file