Mercurial > pub > Impl
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; |