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

renamed Lib->lib
author cin
date Fri, 04 Sep 2015 19:40:23 +0300
parents
children
comparison
equal deleted inserted replaced
406:f23fcb19d3c1 407:c6e90e02dd17
1 package IMPL::Serialization;
2 use strict;
3
4 package IMPL::Serialization::Context;
5
6 use IMPL::Exception();
7 use Scalar::Util qw(refaddr);
8
9 use IMPL::Const qw(:prop);
10 use IMPL::declare {
11 base => [ 'IMPL::Object' => undef ],
12 props => [
13 _objectWriter => PROP_RW | PROP_DIRECT,
14 _context => PROP_RW | PROP_DIRECT,
15 _nextId => PROP_RW | PROP_DIRECT,
16 serializer => PROP_RW | PROP_DIRECT,
17 _state => PROP_RW | PROP_DIRECT
18 ]
19 };
20
21 sub STATE_CLOSED () { 0 }
22 sub STATE_OPENED () { 1 }
23 sub STATE_COMPLEX () { 2 }
24 sub STATE_DATA () { 3 }
25
26 sub CTOR {
27 my ( $this, %args ) = @_;
28
29 $this->{$_objectWriter} = $args{'ObjectWriter'};
30 $this->{$_nextId} = 1;
31 $this->{$serializer} =
32 ( $args{'Serializer'} ? $args{'Serializer'} : \&DefaultSerializer );
33 $this->{$_state} = STATE_CLOSED;
34
35 return 1;
36 }
37
38 sub AddVar {
39 my ( $this, $sName, $Var ) = @_;
40
41 die new Exception('Invalid operation') if $this->{$_state} == STATE_DATA;
42
43 if ( not ref $Var ) {
44 my $prevState = $this->{$_state};
45
46 $this->{$_objectWriter}->BeginObject( name => $sName );
47 $this->{$_state} = STATE_OPENED;
48
49 $this->{$serializer}->( $this, \$Var );
50
51 $this->{$_objectWriter}->EndObject();
52
53 if ( $prevState == STATE_OPENED ) {
54 $this->{$_state} = STATE_COMPLEX;
55 }
56 else {
57 $this->{$_state} = $prevState;
58 }
59 return 0;
60 }
61
62 my $PrevState = $this->{$_state};
63
64 my $ObjID = $this->{$_context}->{ refaddr $Var};
65 if ($ObjID) {
66 $this->{$_objectWriter}->BeginObject( name => $sName, refid => $ObjID );
67 $this->{$_objectWriter}->EndObject();
68 return $ObjID;
69 }
70
71 $ObjID = $this->{$_nextId};
72 $this->{$_nextId} = $ObjID + 1;
73
74 $this->{$_context}->{ refaddr $Var} = $ObjID;
75
76 $this->{$_objectWriter}
77 ->BeginObject( name => $sName, type => ref($Var), id => $ObjID );
78
79 $this->{$_state} = STATE_OPENED;
80 $this->{$serializer}->( $this, $Var );
81
82 $this->{$_objectWriter}->EndObject();
83
84 if ( $PrevState == STATE_OPENED ) {
85 $this->{$_state} = STATE_COMPLEX;
86 }
87 else {
88 $this->{$_state} = $PrevState;
89 }
90
91 return $ObjID;
92 }
93
94 sub SetData {
95 my ( $this, $data, $type ) = @_;
96
97 die new Exception('The object should be a scalar value') if ref $data;
98 die new Exception('Invalid operation') if $this->{$_state} != STATE_OPENED;
99
100 $this->{$_objectWriter}->SetData( $data, $type );
101
102 $this->{$_state} = STATE_DATA;
103
104 return 1;
105 }
106
107 sub DefaultSerializer {
108 my ( $_context, $refObj ) = @_;
109
110 if ( ref($refObj) eq 'SCALAR' ) {
111 $_context->SetData( $$refObj, 'SCALAR' );
112 }
113 elsif ( ref($refObj) eq 'ARRAY' ) {
114 $_context->AddVar( 'item', $_ ) foreach @$refObj;
115 }
116 elsif ( ref($refObj) eq 'HASH' ) {
117 while ( my ( $key, $value ) = each %$refObj ) {
118 $_context->AddVar( $key, $value );
119 }
120 }
121 elsif ( ref($refObj) eq 'REF' ) {
122 $_context->AddVar( 'ref', $$refObj );
123 }
124 else {
125 if ( ref $refObj and $refObj->UNIVARSAL::can('save') ) {
126 $refObj->save($_context);
127 }
128 else {
129 die new Exception(
130 'Cant serialize the object of the type: ' . ref($refObj) );
131 }
132 }
133
134 return 1;
135 }
136
137 package IMPL::Deserialization::Context;
138
139 use IMPL::Const qw(:prop);
140 use IMPL::declare {
141 require => {
142 Exception => 'IMPL::Exception',
143 Loader => 'IMPL::Code::Loader'
144 },
145 base => [ 'IMPL::Object' => undef ],
146 props => [
147
148 # структура информации об объекте
149 # {
150 # Type => 'typename',
151 # Name => 'object_name',
152 # Data => $data,
153 # Id => 'object_id'
154 # }
155 _context => PROP_RW | PROP_DIRECT,
156 _currentObject => PROP_RW | PROP_DIRECT,
157 _objectsPath => PROP_RW | PROP_DIRECT,
158 root => PROP_RW | PROP_DIRECT
159 ]
160 };
161
162 sub CTOR {
163 my ( $this, %args ) = @_;
164 $this->{$_currentObject} = undef;
165 $this->{$root} = undef;
166 }
167
168 sub OnObjectBegin {
169 my ( $this, $name, $rhProps ) = @_;
170
171 die Exception->new(
172 "Invalid data from an ObjectReader",
173 "An object reader should pass a referense to a hash which contains attributes of an object"
174 ) if ( ref $rhProps ne 'HASH' );
175
176 die Exception->new("Trying to create second root object")
177 if not $this->{$_currentObject} and $this->{$root};
178
179 if ( $rhProps->{'refid'} ) {
180
181 my $refObj = $this->{$_context}->{ $rhProps->{'refid'} };
182
183 die Exception->new("A reference to a not existing object found")
184 if not $refObj;
185
186 my $rhCurrentObj = $this->{$_currentObject};
187
188 die Exception->new("The root object can't be a reference")
189 if not $rhCurrentObj;
190
191 if ( $rhCurrentObj->{'Data'} ) {
192
193 die Exception->new( "Invalid serializaed data",
194 "Plain deserialization data for an object already exist" )
195 if not ref $rhCurrentObj->{'Data'};
196
197 push @{ $rhCurrentObj->{'Data'} }, $name, $refObj;
198 } else {
199 $rhCurrentObj->{'Data'} = [ $name, $refObj ];
200 }
201
202 push @{ $this->{$_objectsPath} }, $rhCurrentObj;
203 $this->{$_currentObject} = undef;
204
205 } else {
206 push @{ $this->{$_objectsPath} }, $this->{$_currentObject}
207 if $this->{$_currentObject};
208
209 $this->{$_currentObject} = {
210 Name => $name,
211 Type => $rhProps->{'type'} || 'SCALAR',
212 Id => $rhProps->{'id'},
213 refId => $rhProps->{'refid'}
214 };
215
216 if ( defined $rhProps->{'id'} ) {
217 die new IMPL::Exception(
218 "Trying to create a simple object instead of a reference, type is missing.",
219 $name, $rhProps->{id}
220 ) unless $rhProps->{'type'};
221
222 $this->{$_context}->{ $rhProps->{'id'} } = $this->CreateSurrogate( $rhProps->{'type'} );
223 }
224 }
225
226 return 1;
227 }
228
229 sub OnObjectData {
230 my ( $this, $data ) = @_;
231
232 my $rhObject = $this->{$_currentObject};
233
234 die Exception->new("Trying to set data for an object which not exists")
235 if not $rhObject;
236
237 #die Exception->new(
238 # "Deserialization data already exists for a current object",
239 # "ObjectName= $rhObject->{'Name'}" )
240 # if $rhObject->{'Data'};
241
242 $rhObject->{'Data'} .= $data;
243
244 return 1;
245 }
246 {
247 my $autoId = 0;
248
249 sub OnObjectEnd {
250 my ( $this, $name ) = @_;
251
252 my $rhObject = $this->{$_currentObject};
253 my $rhPrevObject = pop @{ $this->{$_objectsPath} };
254
255 if ( ( not defined($rhObject) ) && $rhPrevObject ) {
256 $this->{$_currentObject} = $rhPrevObject;
257 return 1;
258 }
259
260 my $refObj = $this->CreateObject(
261 $rhObject->{'Type'},
262 $rhObject->{'Data'},
263 $rhObject->{'Id'}
264 ? $this->{$_context}->{ $rhObject->{'Id'} }
265 : undef
266 );
267
268 die Exception->new("Trying to close a non existing oject")
269 if not $rhObject;
270
271 my $data;
272
273 if ( $rhObject->{'Id'} ) {
274 $this->{$_context}->{ $rhObject->{'Id'} } = $refObj;
275 $data = $refObj;
276 }
277 else {
278 if ( ref $refObj ne 'SCALAR' ) {
279 $rhObject->{Id} = "auto$autoId";
280 $autoId++;
281 $this->{$_context}->{ $rhObject->{'Id'} } = $refObj;
282 $data = $refObj;
283 }
284 else {
285 $data = ${$refObj};
286 }
287 }
288
289 if ( not $rhPrevObject ) {
290 $this->{$root} = $data;
291 }
292 else {
293 if ( $rhPrevObject->{'Data'} ) {
294 die Exception->new(
295 "Trying append a reference to an object to the plain data")
296 if not ref $rhPrevObject->{'Data'};
297
298 push @{ $rhPrevObject->{'Data'} }, $rhObject->{'Name'}, $data;
299 }
300 else {
301 $rhPrevObject->{'Data'} = [ $rhObject->{'Name'}, $data ];
302 }
303 }
304
305 $this->{$_currentObject} = $rhPrevObject;
306
307 return 1;
308 }
309 }
310
311 sub CreateSurrogate {
312 my ($this,$type) = @_;
313
314 if ( $type eq 'SCALAR' or $type eq 'REF' ) {
315 my $var;
316 return \$var;
317 }
318 elsif ( $type eq 'ARRAY' ) {
319 return [];
320 }
321 elsif ( $type eq 'HASH' ) {
322 return {};
323 }
324 elsif ($type) {
325 Loader->safe->Require($type);
326 if ( eval { $type->can('surrogate') } ) {
327 return $type->surrogate();
328 }
329 else {
330 return bless {}, $type;
331 }
332 }
333 }
334
335 # deserialization context:
336 # [
337 # 'var_name',value,
338 # ....
339 # ]
340
341 sub CreateObject {
342 my ($this, $type, $data, $refSurogate ) = @_;
343
344 if ( $type eq 'SCALAR' ) {
345 die Exception->new("SCALAR needs a plain data for a deserialization")
346 if ref $data;
347 if ($refSurogate) {
348 $$refSurogate = $data;
349 return $refSurogate;
350 }
351 else {
352 return \$data;
353 }
354 }
355 elsif ( $type eq 'ARRAY' ) {
356 $data ||= [];
357 die Exception->new(
358 "Invalid a deserialization context when deserializing ARRAY")
359 if not ref $data and defined $data;
360 if ( not ref $refSurogate ) {
361 my @Array;
362 $refSurogate = \@Array;
363 }
364 for ( my $i = 0 ; $i < scalar( @{$data} ) / 2 ; $i++ ) {
365 push @$refSurogate, $data->[ $i * 2 + 1 ];
366 }
367 return $refSurogate;
368 }
369 elsif ( $type eq 'HASH' ) {
370 $data ||= [];
371 die Exception->new(
372 "Invalid a deserialization context when deserializing HASH")
373 if not ref $data and defined $data;
374 if ( not ref $refSurogate ) {
375 $refSurogate = {};
376 }
377 for ( my $i = 0 ; $i < @$data ; $i += 2 ) {
378 $refSurogate->{ $data->[$i] } = $data->[ $i + 1 ];
379 }
380 return $refSurogate;
381 }
382 elsif ( $type eq 'REF' ) {
383 $data ||= [];
384 die Exception->new(
385 "Invalid a deserialization context when deserializing REF")
386 if not ref $data and defined $data;
387 if ( not ref $refSurogate ) {
388 my $ref = $data->[1];
389 return \$ref;
390 }
391 else {
392 $$refSurogate = $data->[1];
393 return $refSurogate;
394 }
395 }
396 else {
397 Loader->safe->Require($type);
398 if ( eval { $type->can('restore') } ) {
399 return $type->restore( $data, $refSurogate );
400 }
401 else {
402 die Exception->new("Don't know how to deserialize $type");
403 }
404 }
405 }
406
407 package IMPL::Serializer;
408
409 use IMPL::Const qw(:prop);
410 use IMPL::declare {
411 require => {
412 Exception => 'IMPL::Exception',
413 SerializationContext => '-IMPL::Serialization::Context',
414 DeserializationContext => '-IMPL::Deserialization::Context'
415 },
416 base => [
417 'IMPL::Object' => undef
418 ],
419 props => [
420 _formatter => PROP_RW
421 ]
422 };
423
424 sub CTOR {
425 my ( $this, %args ) = @_;
426 $this->_formatter( $args{formatter} )
427 or die Exception->new("Omitted mandatory parameter 'formatter'");
428 }
429
430 sub Serialize {
431 my $this = shift;
432 my ( $hStream, $Object ) = @_;
433 my $ObjWriter = $this->_formatter->CreateWriter($hStream);
434 my $context =
435 SerializationContext->new( objectWriter => $ObjWriter );
436 $context->AddVar( 'root', $Object );
437 return 1;
438 }
439
440 sub Deserialize {
441 my $this = shift;
442 my ($hStream) = @_;
443 my $context = DeserializationContext->new();
444 my $ObjReader = $this->_formatter->CreateReader( $hStream, $context );
445 $ObjReader->Parse();
446 return $context->root;
447 }
448
449 1;