source: Sophya/trunk/SophyaLib/BaseTools/ppersist.cc@ 2541

Last change on this file since 2541 was 2482, checked in by ansari, 22 years ago

remis declaration strptime pour gcc 2.95 et ajout statistiques en fin de fichier / modifs Write/ReadNameTagTable - Reza 9 Dec 2003

File size: 18.0 KB
RevLine 
[241]1#include "machdefs.h"
[251]2#include <stdio.h>
3#include <sys/types.h>
4#include <time.h>
[241]5#include "pexceptions.h"
[219]6#include "ppersist.h"
[802]7#include "anydataobj.h"
[2322]8#include <iostream>
[241]9#include <typeinfo>
[219]10
[2475]11// ------------------------- Historique ---------------------------
12// Le code de ppersist a ete separe en deux en Decembre 2003
13// a partir de la version CVS (1.26 - ppersist.cc) et
14// (1.19 - ppersist.h) .
15// la partie sur l'ecriture des donnees de base et leurs tableaux
16// a ete mis dans les fichiers ppfbinstream.h .cc
17// (Classes PPFBinaryInputStream et PPFBinaryOutputStream
18// -----------------------------------------------------------------
[219]19
20
[241]21#define MAXTAGLEN 255
22
[219]23//++
[241]24// Class PIOPersist
[219]25// Lib Outils++
26// include ppersist.h
27//
[241]28// Root class for persistant files. Handles the registration of
29// persistant classes
[219]30//--
31
32//++
[241]33// Links See
[219]34// PPersist
[241]35// PInPersist
36// POutPersist
[219]37//--
38
39
[742]40MD5_CONTEXT PIOPersist::ctx;
[802]41PIOPersist::ClassList * PIOPersist::ppclassList = NULL; // $CHECK$ Reza 26/04/99
42map<string, uint_8> * PIOPersist::ppclassNameList = NULL;
43map<string, uint_8> * PIOPersist::dobjclassNameList = NULL;
[241]44
[269]45//++
46void
47PIOPersist::Initialize()
48// Initialisation globale (objets statiques) $CHECK$ Reza 26/04/99
49//--
50{
[802]51ppclassList = new PIOPersist::ClassList;
52ppclassNameList = new map<string, uint_8>;
53dobjclassNameList = new map<string, uint_8>;
[1900]54cout << " PIOPersist::Initialize() Starting Sophya Persistence management service " << endl;
[269]55}
[241]56
[219]57//++
58void
[802]59PIOPersist::RegisterPPHandlerClass(uint_8 classId, string ppclass_name, ClassCreatorFunc f)
[219]60//
[802]61// Register a new persistence handler (PPersist) class.
[2475]62// The classId is usually a hash of the class< name, and
[802]63// ppclass_name is typeid(PPersistClass).name() .
64// This method is called only through the PPersistRegistrar template
[219]65//
66//--
67{
[802]68 if (ppclassList->size() && (ppclassList->find(classId) != ppclassList->end()) ) {
69 cerr << "RegisterClass : Error, " << hex << classId << dec
70 << " already registered." << endl;
[816]71 throw(DuplicateIdExc("PIOPersist::RegisterPPHandlerClass() Already registered (1)"));
[802]72 }
73 if (ppclassNameList->size() && (ppclassNameList->find(ppclass_name) != ppclassNameList->end())) {
74 cerr << "RegisterClass : Error (2) " << ppclass_name
75 << " already registered." << endl;
[816]76 throw(DuplicateIdExc("PIOPersist::RegisterPPHandlerClass() Already registered(2)"));
[219]77 }
78
[802]79 (*ppclassList)[classId] = f;
80 (*ppclassNameList)[ppclass_name] = classId;
[219]81}
82
[802]83//++
84void
85PIOPersist::RegisterDataObjClass(uint_8 classId, string class_name)
86// Register a new DataObj class corresponding to a PPersist classId
87// class_typename should be typeid(DataObject).name()
88//--
89{
[816]90 if (ppclassList->find(classId) == ppclassList->end() ) {
91 cerr << "PIOPersist::RegisterDataObjClass() Error (1) "
92 << hex << classId << dec << " Not Found !" << endl;
93 throw( NotFoundExc("PIOPersist::RegisterDataObjClass() Not found classId ") );
94 }
95 if (dobjclassNameList->size() && (dobjclassNameList->find(class_name) != dobjclassNameList->end())) {
96 cerr << "PIOPersist::RegisterDataObjClass() Error (2)" << class_name
97 << " already registered." << endl;
98 throw(DuplicateIdExc("PIOPersist::RegisterDataObjClass() - Already registered"));
99 }
[219]100
[802]101 (*dobjclassNameList)[class_name] = classId;
102}
103
104// class_typename should be typeid(DataObject).name(), to be
105// used by POutPersist::PutDataObject() methods.
106
[241]107PIOPersist::ClassCreatorFunc
108PIOPersist::FindCreatorFunc(uint_8 classId)
[802]109// Returns the PPersist class creator function for the specified classId
[219]110{
[802]111 ClassList::iterator i = ppclassList->find(classId);
112 if (i == ppclassList->end()) throw(NotFoundExc("PIOPersist::FindCreatorFunc() Not found classId"));
[241]113 return (*i).second;
[219]114}
115
[802]116string
117PIOPersist::getPPClassName(uint_8 classId)
118// Returns the PPersist class name for the specified classId
119{
120 map<string, uint_8>::iterator i;
121 for (i= ppclassNameList->begin(); i != ppclassNameList->end(); i++)
122 if ( (*i).second == classId ) return (*i).first;
[219]123
[802]124 throw(NotFoundExc("PIOPersist::getPPClassName() Not found classId"));
125}
126
127uint_8
128PIOPersist::getPPClassId(string const & typ_name)
129// Returns the classId for the specified PPersist class type name
130{
131 map<string, uint_8>::iterator i = ppclassNameList->find(typ_name);
132 if (i == ppclassNameList->end())
133 throw(NotFoundExc("PIOPersist::getPPClassId() Not found className"));
134 return (*i).second;
135}
136
137uint_8
138PIOPersist::getPPClassId(PPersist const & ppo)
139// Returns the classId for the specified PPersist class
140{
141 string typ_name = typeid(ppo).name() ;
142 return (getPPClassId(typ_name) );
143}
144
145
146string
147PIOPersist::getDataObjClassName(uint_8 classId)
148// Returns the PPersist class name for the specified classId
149{
150 map<string, uint_8>::iterator i;
151 for (i= dobjclassNameList->begin(); i != dobjclassNameList->end(); i++)
152 if ( (*i).second == classId ) return (*i).first;
153
154 throw(NotFoundExc("PIOPersist::getDataObjClassName() Not found classId"));
155}
156
157uint_8
158PIOPersist::getDataObjClassId(string const & typ_name)
159// Returns the classId for the specified PPersist class type name
160{
161 map<string, uint_8>::iterator i = dobjclassNameList->find(typ_name);
162 if (i == dobjclassNameList->end())
163 throw(NotFoundExc("PIOPersist::getDataObjClassId() Not found className"));
164 return (*i).second;
165}
166
167uint_8
168PIOPersist::getDataObjClassId(AnyDataObj const & o)
169// Returns the classId for the specified PPersist class
170{
171 string typ_name = typeid(o).name() ;
172 return (getDataObjClassId(typ_name) );
173}
174
[2476]175static inline void bswap8_hash(void* p)
176{
177 uint_8 tmp = *(uint_8*)p;
178 *(uint_8*)p = ((tmp >> (7*8)) & 0x000000FF) |
179 ((tmp >> (5*8)) & 0x0000FF00) |
180 ((tmp >> (3*8)) & 0x00FF0000) |
181 ((tmp >> (1*8)) & 0xFF000000) |
182 ((tmp & 0xFF000000) << (1*8)) |
183 ((tmp & 0x00FF0000) << (3*8)) |
184 ((tmp & 0x0000FF00) << (5*8)) |
185 ((tmp & 0x000000FF) << (7*8));
186}
[802]187
[1202]188
189
190uint_8 PIOPersist::Hash(string const& typname) {
191 md5_init(&ctx);
192 md5_write(&ctx, (unsigned char*) typname.c_str(), typname.size());
193 md5_final(&ctx);
194 uint_8 hash1 = *((uint_8*) ctx.buf);
195 uint_8 hash2 = *((uint_8*) (ctx.buf+8));
196#if IS_BIG_ENDIAN
[2476]197 bswap8_hash(&hash1);
198 bswap8_hash(&hash2);
[1202]199#endif
200
201 return (hash1+hash2);
202}
203
204
[219]205//++
206// Class PPersist
207// Lib Outils++
208// include ppersist.h
209//
210// Classe de base pour des objets persistants. Pour créer un objet
211// persistant :
212// - Hériter de PPersist.
213// - Définir un numéro d'identification de la classe, unique dans Peida
214// - Implémenter "ClassId()"
215// - Implémenter "WriteSelf" et "ReadSelf", qui doivent écrire toutes les variables
216// membres que l'on souhaite écrire, et les relire dans le même ordre.
217// Pour écrire une référence à un objet : l'objet doit être un PPersist,
218// et il suffit d'appeler "Write" sur cet objet, et "PPersistMgr::ReadObject".
219// Si plusieurs objets font référence au même, pour éviter de l'écrire plusieurs
220// fois, il faut que cet objet soit un PShPersist.
221// - Pour que le fichier soit portable, écrire et lire les variables membres en utilisant
222// les fonctions PutXX/GetXX de PInPersist/POutPersist.
223//
224// Attention: les méthodes à redéfinir sont WriteSelf et ReadSelf, mais il ne faut jamais
225// les appeler directement. Seuls Write et Read peuvent être appelées par l'utilisateur.
226//--
227
228//++
[241]229// Links See
[219]230// PInPersist
231// POutPersist
[241]232// PIOPersist
[219]233//--
234
235//++
236void
237PPersist::Write(string const& fn) const
238//
239// Ecrit l'objet dans un nouveau fichier ppersist "fn".
240//--
241{
242 POutPersist of(fn);
243 Write(of);
244}
245
246//++
247void
248PPersist::Read(string const& fn)
249//
250// Relit l'objet dans le fichier ppersist "fn". Il faut connaître a priori
251// le type de l'objet. Pour une relecture avec création automatique du bon
252// objet, utiliser PPersistMgr::ReadObject.
253//--
254{
255 PInPersist inf(fn);
256 Read(inf);
257}
258
259//++
260void
261PPersist::Write(POutPersist& s) const
262//
263// Ecrit l'objet dans le fichier PPersist.
264//--
265{
[802]266 s.PutPPObject(this);
[219]267}
268
269
270//++
271void
272PPersist::Read(PInPersist& s)
273//
274// Relit l'objet dans le fichier ppersist. Il faut connaître a priori
275// le type de l'objet. Pour une relecture avec création automatique du bon
[241]276// objet, utiliser PInPersist::ReadObject.
277// Il faut qu'on soit un objet ecrit
[219]278//--
279{
[241]280 // We should be the exact type
281 // Check tag value
[802]282 unsigned char ppstype,ppstag;
[588]283 s.GetTypeTag(ppstype);
[802]284 if ( (ppstype != PInPersist::PPS_OBJECT) && ( ppstype != PInPersist::PPS_REFERENCE ) ) {
[241]285 }
[802]286 if (ppstype == PInPersist::PPS_OBJECT) {
287 // Check class id
288 uint_8 classId;
289 s.GetRawU8(classId);
290 uint_8 oid,oid2;
291 s.GetRawU8(oid);
292 if (classId != PIOPersist::getPPClassId(*this) )
293 throw FileFormatExc("PPersist::Read (): not the same object type");
294 ReadSelf(s);
295 // Read the ENDOBJECT
296 s.GetRawUByte(ppstag);
297 if (ppstag != PInPersist::PPS_ENDOBJECT)
298 throw FileFormatExc("PPersist::Read() No PPS_ENDOBJECT tag");
299 s.GetRawU8(oid2);
300 if (oid2 != oid)
301 throw FileFormatExc("PPersist::Read() Inconsistent PPS-OId at PPS_ENDOBJECT ");
302 s.KeepOId(oid, *this); // Object should be kept with its PPS_OId (if oid > 0)
[241]303 }
[802]304 else if ( ppstype == PInPersist::PPS_REFERENCE )
305 s.ReadReference(*this);
[219]306
[802]307 else throw FileFormatExc("PPersist::Read() : not an object in flow");
308
[219]309}
310
311//++
[241]312void
313PPersist::Write(POutPersist& s, string const& tag) const
[219]314//
[241]315// Ecrit l'objet dans le fichier PPersist avec un tag
[219]316//--
317{
[2441]318 s.WriteNameTag(tag);
[802]319 s.PutPPObject(this);
[219]320}
321
322//++
323void
[241]324PPersist::ReadAtTag(PInPersist& s, string const& tag)
[219]325//
326// Lit l'objet à la position du tag numéro "tagid".
327//--
328{
[2459]329 if (!s.GotoNameTag(tag))
[241]330 throw NotFoundExc("PPersist::ReadAtTag tag not found");
331 Read(s);
[219]332}
333
[802]334// Renvoie l'identificateur de l'objet - par defaut=0
[219]335
[802]336uint_8
337PPersist::getMemOId() const
338{
339 return(0);
340}
341
342// Ces deux methodes doivent etre redefinies si getMemOId() renvoie non nul (>0)
343// ShareDataReference() et CloneSharedReference()
344void
345PPersist::ShareDataReference(PPersist & pcs)
346{
347 throw NotAvailableOperation("PPersist::ShareDataReference() - Unsupported operation !");
348}
349
350PPersist *
351PPersist::CloneSharedReference()
352{
353 throw NotAvailableOperation("PPersist::CloneSharedReference() - Unsupported operation !");
354}
355
[219]356//++
357// virtual void PPersist::ReadSelf(PInPersist&)=0
358// Méthode virtuelle pure à redéfinir. Elle est appelée par Read
359// et PPersistMgr::ReadObject. Il faut relire les variables membres,
360// dans l'ordre où elles ont été écrites par WriteSelf.
361// virtual void PPersist::WriteSelf(POutPersist&) const=0
362// Méthode virtuelle pure à redéfinir. Elle est appelée par Write.
363// Il faut écrire les variables membres,
364// dans l'ordre où elles seront relues par ReadSelf.
365//--
366
367
368
369//++
370// Class PInPersist
371// Lib Outils++
372// include ppersist.h
373//
374// Fichier d'objets persistants, en lecture.
375//--
376
[2482]377PInPersist::PInPersist(RawInOutStream * is, bool ad, bool scan)
378 : PPFBinaryInputStream(is, ad, scan)
379{
380}
381
[219]382PInPersist::PInPersist(string const& flnm, bool scan)
[2475]383 : PPFBinaryInputStream(flnm, scan)
[219]384{
385}
386
387
388
389PInPersist::~PInPersist()
390{
[802]391 ObjList::iterator i;
392 for(i=objList.begin(); i!= objList.end(); i++)
393 if ((*i).second) delete (*i).second;
[219]394}
395
396
[802]397
[582]398string
[802]399PInPersist::GetTagClassName(int itag)
400{
401 // A faire
402// if (itag<0 || itag >= (int)tags.size()) return "";
403// map<string, int_8>::iterator i = tags.begin();
404// for (int j=0; j<itag; j++) i++;
405// uint_8 cid = (*i).second;
406// return(GetClassName(cid));
407 return("");
408}
409
[241]410PPersist*
411PInPersist::ReadObject()
412{
[802]413 return(GetPPObject());
414}
415
416void
417PInPersist::GetObject(AnyDataObj & o)
418{
419 GetPPObject(&o);
420 return;
421}
422
423void
424PInPersist::GetObject(AnyDataObj & o, string tagname)
425{
[2459]426 GotoNameTag(tagname);
[802]427 GetPPObject(&o);
428 return;
429}
430
431PPersist*
432PInPersist::GetPPObject(AnyDataObj * po)
433{
[241]434 // Get tag
[802]435 unsigned char ppstype;
[588]436 GetTypeTag(ppstype);
[241]437 if (ppstype != PPS_OBJECT && ppstype != PPS_REFERENCE && ppstype != PPS_NULL) {
[256]438 throw FileFormatExc("PInPersist::ReadObject : not an object in flow");
[241]439 }
440
441 if (ppstype == PPS_NULL) {
442 return NULL;
443 } else if (ppstype == PPS_OBJECT) {
444 // Get class id
445 uint_8 classId;
446 GetRawU8(classId);
[802]447 uint_8 oid,oid2;
448 GetRawU8(oid);
[241]449
450 // Get factory method
451 ClassCreatorFunc f = FindCreatorFunc(classId);
452 if (!f) {
453 throw NotFoundExc("PInPersist::ReadObject class not registered");
454 }
455
456 // Create object
457 PPersist* object = f();
[802]458 // If a DataObject was specified , we assign it to the PPersistObject
459 if (po != NULL) object->SetDataObj(*po);
460
[241]461 object->ReadSelf(*this);
[802]462 unsigned char ppstag;
463 // Read the ENDOBJECT
464 GetRawUByte(ppstag);
465 if (ppstag != PPS_ENDOBJECT)
466 throw FileFormatExc("PInPersist::ReadObject No PPS_ENDOBJECT tag");
467 GetRawU8(oid2);
468 if (oid2 != oid)
469 throw FileFormatExc("PInPersist::ReadObject Inconsistent PPS-OId at PPS_ENDOBJECT ");
470
471 KeepOId(oid, *object);
[241]472 return object;
[802]473 }
474 else if (ppstype == PPS_REFERENCE)
475 return ReadReference();
476
477 else throw FileFormatExc("PInPersist::ReadObject invalide Tag Type !");
478}
479
480
481
482void
483PInPersist::ReadReference(PPersist & ppo)
[241]484{
[802]485 PPersist * pr = ReadReference();
486 ppo.ShareDataReference(*pr);
[241]487}
488
[802]489
490PPersist *
491PInPersist::ReadReference()
492{
493 uint_8 oid;
494 int_8 pos;
495 GetRawU8(oid);
496 GetRawI8(pos);
497 // cerr << " DBG - PInPersist::ReadReference-A " << oid << " Pos= " << pos << endl;
498 map<uint_8, PPersist *>::iterator i = objList.find(oid);
499 if (i != objList.end()) return (*i).second;
500 else { // We may have skeeped it !
501 // Let's try to read it
502 int_8 cpos;
503 cpos = s->tellg();
504 s->seekg(pos);
505 PPersist* ppo = ReadObject();
506 s->seekg(cpos);
507 delete ppo;
508 // cerr << " DBG - PInPersist::ReadReference-B ... " << endl;
509
510 map<uint_8, PPersist *>::iterator i2 = objList.find(oid);
511 if (i2 == objList.end())
512 throw FileFormatExc("PInPersist::ReadReference() Not found PPS_OId ");
513 return (*i2).second;
514 }
515}
516
517
518void
519PInPersist::KeepOId(uint_8 oid, PPersist & ppo)
520{
521 if ((oid&0x1) == 0) return; // This is not an object which can be referenced
522 // cerr << " DBG - PInPersist::KeepOId() " << oid << endl;
523 if ((objList.size() > 0) && (objList.find(oid) != objList.end()) ) {
[2459]524 // Ceci ne devrait arriver que si on lit dans le desordre (avec GotoNameTag)
[802]525 // et pas avec une lecture sequentielle ... Reza 03/2000
526 // cerr << "PInPersist::KeepOId()/Warning - already present PPS_ObjectId ! " << oid << endl;
527 if (seqread) throw FileFormatExc("PInPersist::KeepOId() already present PPS_ObjectId ");
528 PPersist *pp = (*objList.find(oid)).second;
529 ppo.ShareDataReference(*pp);
530 }
531 else {
532 PPersist * npp = ppo.CloneSharedReference();
533 if (npp == NULL) throw PError("PInPersist::KeepOId() NULL returned by PPersist.Clone() ! ");
534 objList[oid] = npp;
535 }
536 return;
537}
538
[219]539//++
540// Class POutPersist
541// Lib Outils++
542// include ppersist.h
543//
544// Fichier d'objets persistants, en écriture.
545//--
546
547
548//++
549// POutPersist(string const& flnm, int endianness = PPersist::PPS_NATIVE)
550//
551// Crée un nouveau fichier ppersist. Par défaut, il est petit=boutien
552// sur machines petit-boutiennes, et gros-boutien sur machines
553// gros-boutiennes. On peut explicitement spécifier PPersist::PPS_LITTLE_ENDIAN
554// ou PPersist::PPS_BIG_ENDIAN.
555//--
[2477]556POutPersist::POutPersist(RawInOutStream* os, bool ad, int endianness)
557 : PPFBinaryOutputStream(os, ad, endianness)
558{
559 pps_OId = 0;
560 wobj_level = 0;
561}
562
[219]563POutPersist::POutPersist(string const& flnm, int endianness)
[2475]564 : PPFBinaryOutputStream(flnm, endianness)
[219]565{
[802]566 // PPS (POutPersist stream) Object Id initialisation
567 pps_OId = 0;
[2477]568 wobj_level = 0;
[219]569}
570
571POutPersist::~POutPersist()
572{
573}
574
575
[241]576void
[802]577POutPersist::PutObject(AnyDataObj & o)
578{
579 ClassCreatorFunc f = FindCreatorFunc(getDataObjClassId(o));
580 if (!f)
581 throw NotFoundExc("PInPersist::PutObject() class not registered");
582 PPersist* ppo = f();
583 ppo->SetDataObj(o);
584 PutPPObject(ppo);
[219]585}
586
[241]587void
[802]588POutPersist::PutObject(AnyDataObj & o, string tagname)
[241]589{
[2441]590 WriteNameTag(tagname);
[802]591 PutObject(o);
592}
[219]593
[802]594
595void
596POutPersist::PutPPObject(PPersist const* obj)
597{
598 if (serializeNullAndRepeat(obj)) return; // NULL object or already written in stream
599
600 // We have to write the object
601 uint_8 oid = assignObjectId(obj); // We assing a PPS Object Id
602 PutRawUByte(PPS_OBJECT); // We write the Object Tag
[2477]603 wobj_level++; // Niveau d'imbrication d'ecriture d'objets
[802]604 PutRawU8(getPPClassId(*obj)); // Writing the PPersist ClassId
605 PutRawU8(oid); // Write the PPS Object Id
[241]606 obj->WriteSelf(*this);
[2477]607 // Comptage d'objets ecrits
608 _nbobjs++;
[2482]609 if (wobj_level > _maxnestlevel) _maxnestlevel = wobj_level;
[2477]610 if (wobj_level == 1) _nbtlobjs++;
611 wobj_level--;
[802]612 PutRawUByte(PPS_ENDOBJECT); // We write the End-Of-Object Tag
613 PutRawU8(oid); // and again its PPS Object Id
[241]614}
[219]615
[241]616bool
617POutPersist::serializeNullAndRepeat(PPersist const* x)
[219]618{
[241]619 if (x == NULL) {
[802]620 PutRawUByte(PPS_NULL);
[241]621 return true;
622 }
[219]623
[802]624 int_8 pos;
625 uint_8 id = findObjectId(x, pos);
626 if (id > 0) {
627 PutRawUByte(PPS_REFERENCE);
628 PutRawU8(id); // Writing the corresponding object Id
629 PutRawI8(pos); // The original object position
[2482]630 _nbrefs++; // Compteur de nombre de reference ecrits
[241]631 return true;
632 }
[219]633
[802]634 return false; // Object have to be written in stream ...
[219]635}
636
[802]637uint_8
[241]638POutPersist::assignObjectId(PPersist const* x)
[219]639{
[802]640 pps_OId += 16; // We keep the three first bytes for future usage
641 // Bit 1 non zero -> Object can be referenced
642 uint_8 id = pps_OId;
643 uint_8 mid = x->getMemOId();
644 if (mid > 0) {
645 int_8 pos;
646 if (findObjectId(x,pos) > 0)
647 throw PError("POutPersist::assignObjectId() Error - Already serialized object ! ");
[821]648 id += 1; // Bit 1 non zero -> Object can be referenced
[802]649 objreftag rt;
650 rt.ppsoid = id;
[2476]651 // cout << " DBG-rt.ppspos = s->tellp(); " << endl;
[802]652 rt.ppspos = s->tellp();
[2476]653 // cout << " DBG-rt.ppspos = s->tellp(); = " << rt.ppspos << endl;
[802]654 objList[mid] = rt;
655 }
[241]656 return id;
[219]657}
658
[802]659uint_8
660POutPersist::findObjectId(PPersist const* x, int_8 & pos)
[219]661{
[802]662 pos = -1;
663 uint_8 mid = x->getMemOId();
664 if (mid == 0) return(0);
665 ObjList::iterator i = objList.find(mid);
666 if (i == objList.end()) return 0;
667 pos = (*i).second.ppspos;
668 return (*i).second.ppsoid;
[219]669}
670
[241]671
Note: See TracBrowser for help on using the repository browser.