persistencexml.bmx 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191
  1. ' Copyright (c) 2008-2022 Bruce A Henderson
  2. '
  3. ' Permission is hereby granted, free of charge, to any person obtaining a copy
  4. ' of this software and associated documentation files (the "Software"), to deal
  5. ' in the Software without restriction, including without limitation the rights
  6. ' to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  7. ' copies of the Software, and to permit persons to whom the Software is
  8. ' furnished to do so, subject to the following conditions:
  9. '
  10. ' The above copyright notice and this permission notice shall be included in
  11. ' all copies or substantial portions of the Software.
  12. '
  13. ' THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14. ' IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15. ' FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16. ' AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17. ' LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18. ' OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  19. ' THE SOFTWARE.
  20. '
  21. SuperStrict
  22. Rem
  23. bbdoc: Persistence XML
  24. about: An XML-based object-persistence framework.
  25. End Rem
  26. Module Text.PersistenceXml
  27. ModuleInfo "Version: 1.06"
  28. ModuleInfo "Author: Bruce A Henderson"
  29. ModuleInfo "License: MIT"
  30. ModuleInfo "Copyright: 2008-2022 Bruce A Henderson"
  31. ModuleInfo "History: 1.06"
  32. ModuleInfo "History: Updated to use Text.XML."
  33. ModuleInfo "History: 1.05"
  34. ModuleInfo "History: Improved persistence."
  35. ModuleInfo "History: 1.04"
  36. ModuleInfo "History: Improved persistence."
  37. ModuleInfo "History: Added unit tests."
  38. ModuleInfo "History: 1.03"
  39. ModuleInfo "History: Added custom serializers."
  40. ModuleInfo "History: 1.02"
  41. ModuleInfo "History: Added XML parsing options arg for deserialization."
  42. ModuleInfo "History: Fixed 64-bit address ref issue."
  43. ModuleInfo "History: 1.01"
  44. ModuleInfo "History: Added encoding for String and String Array fields. (Ronny Otto)"
  45. ModuleInfo "History: 1.00"
  46. ModuleInfo "History: Initial Release"
  47. Import Text.XML
  48. Import BRL.Reflection
  49. Import BRL.Map
  50. Import BRL.Stream
  51. Import "glue.c"
  52. Rem
  53. bbdoc: Object Persistence.
  54. End Rem
  55. Type TPersist
  56. Rem
  57. bbdoc: File format version
  58. End Rem
  59. Const BMO_VERSION:Int = 8
  60. Field doc:TxmlDoc
  61. Field objectMap:TMap = New TMap
  62. Field lastNode:TxmlNode
  63. Rem
  64. bbdoc: Serialized formatting.
  65. about: Set to True to have the data formatted nicely. Default is False - off.
  66. End Rem
  67. Global format:Int = False
  68. Rem
  69. bbdoc: Compressed serialization.
  70. about: Set to True to compress the serialized data. Default is False - no compression.
  71. End Rem
  72. Global compressed:Int = False
  73. ?ptr64
  74. Global bbEmptyString:String = Base36(Long(bbEmptyStringPtr()))
  75. Global bbNullObject:String = Base36(Long(bbNullObjectPtr()))
  76. Global bbEmptyArray:String = Base36(Long(bbEmptyArrayPtr()))
  77. ?Not ptr64
  78. Global bbEmptyString:String = Base36(Int(bbEmptyStringPtr()))
  79. Global bbNullObject:String = Base36(Int(bbNullObjectPtr()))
  80. Global bbEmptyArray:String = Base36(Int(bbEmptyArrayPtr()))
  81. ?
  82. Field fileVersion:Int
  83. Field serializers:TMap = New TMap
  84. Field _inited:Int
  85. Rem
  86. bbdoc: Serializes the specified Object into a String.
  87. End Rem
  88. Method Serialize:String(obj:Object)
  89. Return SerializeToString(obj)
  90. End Method
  91. Method Free()
  92. If doc Then
  93. doc.Free()
  94. doc = Null
  95. End If
  96. If lastNode Then
  97. lastNode = Null
  98. End If
  99. objectMap.Clear()
  100. End Method
  101. Rem
  102. bbdoc: Serializes an Object to a String.
  103. End Rem
  104. Method SerializeToString:String(obj:Object)
  105. If Not _inited Throw "Use TXMLPersistenceBuilder to create TPersist instance."
  106. Free()
  107. SerializeObject(obj)
  108. Return ToString()
  109. End Method
  110. Rem
  111. bbdoc: Serializes an Object to the file @filename.
  112. End Rem
  113. Method SerializeToFile(obj:Object, filename:String)
  114. If Not _inited Throw "Use TXMLPersistenceBuilder to create TPersist instance."
  115. Free()
  116. SerializeObject(obj)
  117. If doc Then
  118. doc.saveFile(filename, True, format)
  119. End If
  120. Free()
  121. End Method
  122. Rem
  123. bbdoc: Serializes an Object to a TxmlDoc structure.
  124. about: It is up to the user to free the returned TxmlDoc object.
  125. End Rem
  126. Method SerializeToDoc:TxmlDoc(obj:Object)
  127. If Not _inited Throw "Use TXMLPersistenceBuilder to create TPersist instance."
  128. Free()
  129. SerializeObject(obj)
  130. Local exportDoc:TxmlDoc = doc
  131. doc = Null
  132. Free()
  133. Return exportDoc
  134. End Method
  135. Rem
  136. bbdoc: Serializes an Object to a Stream.
  137. about: It is up to the user to close the stream.
  138. End Rem
  139. Method SerializeToStream(obj:Object, stream:TStream)
  140. If Not _inited Throw "Use TXMLPersistenceBuilder to create TPersist instance."
  141. Free()
  142. SerializeObject(obj)
  143. If doc Then
  144. doc.saveFile(stream, False, format)
  145. End If
  146. Free()
  147. End Method
  148. Rem
  149. bbdoc: Returns the serialized object as a string.
  150. End Rem
  151. Method ToString:String()
  152. If doc Then
  153. Return doc.ToStringFormat(format)
  154. End If
  155. End Method
  156. Method ProcessArray(arrayObject:Object, size:Int, node:TxmlNode, typeId:TTypeId)
  157. Local elementType:TTypeId = typeId.ElementType()
  158. Select elementType
  159. Case ByteTypeId, ShortTypeId, IntTypeId, LongTypeId, FloatTypeId, DoubleTypeId, UIntTypeId, ULongTypeId, LongIntTypeId, ULongIntTypeId
  160. Local sb:TStringBuilder = new TStringBuilder()
  161. For Local i:Int = 0 Until size
  162. Local aObj:Object = typeId.GetArrayElement(arrayObject, i)
  163. If i Then
  164. sb.Append(" ")
  165. End If
  166. sb.Append(String(aObj))
  167. Next
  168. node.SetContent(sb.ToString())
  169. Default
  170. For Local i:Int = 0 Until size
  171. Local elementNode:TxmlNode = node.addChild("val")
  172. Local aObj:Object = typeId.GetArrayElement(arrayObject, i)
  173. Select elementType
  174. Case StringTypeId
  175. ' only if not empty
  176. If String(aObj) Then
  177. elementNode.setContent(String(aObj))
  178. End If
  179. Default
  180. Local objRef:String = GetObjRef(aObj)
  181. ' file version 5 ... array cells can contain references
  182. If Not Contains(objRef, aObj) Then
  183. SerializeObject(aObj, elementNode)
  184. Else
  185. elementNode.setAttribute("ref", objRef)
  186. End If
  187. End Select
  188. Next
  189. End Select
  190. End Method
  191. Rem
  192. bbdoc:
  193. End Rem
  194. Method SerializeFields(tid:TTypeId, obj:Object, node:TxmlNode)
  195. For Local f:TField = EachIn tid.EnumFields()
  196. SerializeField(f, obj, node)
  197. Next
  198. End Method
  199. Rem
  200. bbdoc:
  201. End Rem
  202. Method CreateSerializedFieldNode:TxmlNode(f:TField, node:TxmlNode)
  203. Local fieldNode:TxmlNode = node.addChild("field")
  204. fieldNode.setAttribute("name", f.Name())
  205. Return fieldNode
  206. End Method
  207. Rem
  208. bbdoc:
  209. End Rem
  210. Method SerializeField(f:TField, obj:Object, node:TxmlNode)
  211. If f.MetaData("nopersist") Then
  212. Return
  213. End If
  214. Local fieldType:TTypeId = f.TypeId()
  215. Local fieldNode:TxmlNode = CreateSerializedFieldNode(f, node)
  216. Local t:String
  217. Select fieldType
  218. Case ByteTypeId
  219. t = "byte"
  220. fieldNode.setContent(f.GetInt(obj))
  221. Case ShortTypeId
  222. t = "short"
  223. fieldNode.setContent(f.GetInt(obj))
  224. Case IntTypeId
  225. t = "int"
  226. fieldNode.setContent(f.GetInt(obj))
  227. Case LongTypeId
  228. t = "long"
  229. fieldNode.setContent(f.GetLong(obj))
  230. Case FloatTypeId
  231. t = "float"
  232. fieldNode.setContent(f.GetFloat(obj))
  233. Case DoubleTypeId
  234. t = "double"
  235. fieldNode.setContent(f.GetDouble(obj))
  236. Case UIntTypeId
  237. t = "uint"
  238. fieldNode.setContent(f.GetUInt(obj))
  239. Case ULongTypeId
  240. t = "ulong"
  241. fieldNode.setContent(f.GetULong(obj))
  242. Case LongIntTypeId
  243. t = "longint"
  244. fieldNode.setContent(f.GetLongInt(obj))
  245. Case ULongIntTypeId
  246. t = "ulongint"
  247. fieldNode.setContent(f.GetULongInt(obj))
  248. Default
  249. t = fieldType.Name()
  250. If fieldType.ExtendsType( ArrayTypeId ) Then
  251. ' prefix and strip brackets
  252. Local dims:Int = t.split("[").length
  253. If dims = 1 Then
  254. t = "array:" + t.Replace("[]", "")
  255. Else
  256. t = "array:" + t
  257. End If
  258. dims = fieldType.ArrayDimensions(f.Get(obj))
  259. If dims > 1 Then
  260. Local scales:String
  261. For Local i:Int = 0 Until dims - 1
  262. scales :+ (fieldType.ArrayLength(f.Get(obj), i) / fieldType.ArrayLength(f.Get(obj), i + 1))
  263. scales :+ ","
  264. Next
  265. scales:+ fieldType.ArrayLength(f.Get(obj), dims - 1)
  266. fieldNode.setAttribute("scales", scales)
  267. End If
  268. ProcessArray(f.Get(obj), fieldType.ArrayLength(f.Get(obj)), fieldNode, fieldType)
  269. Else
  270. Local fieldObject:Object = f.Get(obj)
  271. Local fieldRef:String = GetObjRef(fieldObject)
  272. If fieldRef <> bbEmptyString And fieldRef <> bbNullObject And fieldRef <> bbEmptyArray Then
  273. If fieldObject Then
  274. If Not Contains(fieldRef, fieldObject) Then
  275. SerializeObject(fieldObject, fieldNode)
  276. Else
  277. fieldNode.setAttribute("ref", fieldRef)
  278. End If
  279. End If
  280. End If
  281. End If
  282. End Select
  283. fieldNode.setAttribute("type", t)
  284. End Method
  285. Method SerializeByType(tid:TTypeId, obj:Object, node:TxmlNode)
  286. Local serializer:TXMLSerializer = TXMLSerializer(serializers.ValueForKey(tid.Name()))
  287. If serializer Then
  288. serializer.Serialize(tid, obj, node)
  289. Else
  290. SerializeFields(tid, obj, node)
  291. End If
  292. End Method
  293. Rem
  294. bbdoc:
  295. End Rem
  296. Method SerializeObject:TxmlNode(obj:Object, parent:TxmlNode = Null)
  297. Local node:TxmlNode
  298. If Not doc Then
  299. doc = TxmlDoc.newDoc("1.0")
  300. parent = TxmlNode.newNode("bmo") ' BlitzMax Object
  301. parent.SetAttribute("ver", BMO_VERSION) ' set the format version
  302. doc.setRootElement(parent)
  303. Else
  304. If Not parent Then
  305. parent = doc.GetRootElement()
  306. End If
  307. End If
  308. If obj Then
  309. Local objRef:String = GetObjRef(obj)
  310. If objRef = bbEmptyString Or objRef = bbNullObject Or objRef = bbEmptyArray Then
  311. Return Null
  312. End If
  313. Local objectIsArray:Int = False
  314. Local tid:TTypeId = TTypeId.ForObject(obj)
  315. Local tidName:String = tid.Name()
  316. ' Is this an array "Object" ?
  317. If tidName.EndsWith("[]") Then
  318. tidName = "_array_"
  319. objectIsArray = True
  320. End If
  321. node = parent.addChild(tidName)
  322. node.setAttribute("ref", objRef)
  323. AddObjectRef(obj, node)
  324. ' We need to handle array objects differently..
  325. If objectIsArray Then
  326. tidName = tid.Name()[..tid.Name().length - 2]
  327. Local size:Int
  328. ' it's possible that the array is zero-length, in which case the object type
  329. ' is undefined. Therefore we default it to type "Object".
  330. ' This doesn't matter, since it's essentially a Null Object which has no
  331. ' inherent value. We only store the instance so that the de-serialized object will
  332. ' look similar.
  333. Try
  334. size = tid.ArrayLength(obj)
  335. Catch e$
  336. tidName = "Object"
  337. size = 0
  338. End Try
  339. node.setAttribute("type", tidName)
  340. node.setAttribute("size", size)
  341. If size > 0 Then
  342. ProcessArray(obj, size, node, tid)
  343. End If
  344. Else
  345. ' special case for String object
  346. If tid = StringTypeId Then
  347. node.setContent(String(obj))
  348. Else
  349. SerializeByType(tid, obj, node)
  350. End If
  351. End If
  352. End If
  353. Return node
  354. End Method
  355. Method Contains:Int(ref:String, obj:Object)
  356. Local cobj:Object = objectMap.ValueForKey(ref)
  357. If Not cobj Then
  358. Return False
  359. End If
  360. ' same object already exists!
  361. If cobj = obj Then
  362. Return True
  363. End If
  364. ' same ref but different object????
  365. Throw TPersistCollisionException.CreateException(ref, obj, cobj)
  366. End Method
  367. Method Delete()
  368. Free()
  369. End Method
  370. Rem
  371. bbdoc: De-serializes @text into an Object structure.
  372. about: Accepts a TxmlDoc, TStream or a String (of data).
  373. End Rem
  374. Method DeSerialize:Object(data:Object)
  375. If Not _inited Throw "Use TXMLPersistenceBuilder to create TPersist instance."
  376. If TxmlDoc(data) Then
  377. Return DeSerializeFromDoc(TxmlDoc(data))
  378. Else If TStream(data) Then
  379. Return DeSerializeFromStream(TStream(data))
  380. Else If String(data) Then
  381. Return DeSerializeObject(String(data), Null)
  382. End If
  383. End Method
  384. Rem
  385. bbdoc: De-serializes @doc into an Object structure.
  386. about: It is up to the user to free the supplied TxmlDoc.
  387. End Rem
  388. Method DeSerializeFromDoc:Object(xmlDoc:TxmlDoc)
  389. If Not _inited Throw "Use TXMLPersistenceBuilder to create TPersist instance."
  390. doc = xmlDoc
  391. Local root:TxmlNode = doc.GetRootElement()
  392. fileVersion = root.GetAttribute("ver").ToInt() ' get the format version
  393. Local obj:Object = DeSerializeObject("", root)
  394. doc = Null
  395. Free()
  396. Return obj
  397. End Method
  398. Rem
  399. bbdoc: De-serializes the file @filename into an Object structure.
  400. End Rem
  401. Method DeSerializeFromFile:Object(filename:Object)
  402. If Not _inited Throw "Use TXMLPersistenceBuilder to create TPersist instance."
  403. doc = TxmlDoc.parseFile(filename)
  404. If doc Then
  405. Local root:TxmlNode = doc.GetRootElement()
  406. fileVersion = root.GetAttribute("ver").ToInt() ' get the format version
  407. Local obj:Object = DeSerializeObject("", root)
  408. Free()
  409. Return obj
  410. End If
  411. End Method
  412. Rem
  413. bbdoc: De-serializes @stream into an Object structure.
  414. End Rem
  415. Method DeSerializeFromStream:Object(stream:TStream)
  416. If Not _inited Throw "Use TXMLPersistenceBuilder to create TPersist instance."
  417. Return DeSerializeFromFile(stream)
  418. End Method
  419. Method DeserializeByType:Object(objType:TTypeId, node:TxmlNode)
  420. Local serializer:TXMLSerializer = TXMLSerializer(serializers.ValueForKey(objType.Name()))
  421. If serializer Then
  422. Return serializer.Deserialize(objType, node)
  423. Else
  424. Local obj:Object = CreateObjectInstance(objType, node)
  425. DeserializeFields(objType, obj, node)
  426. Return obj
  427. End If
  428. End Method
  429. Method AddObjectRef(obj:Object, node:TxmlNode)
  430. objectMap.Insert(node.getAttribute("ref"), obj)
  431. End Method
  432. Method CreateObjectInstance:Object(objType:TTypeId, node:TxmlNode)
  433. ' create the object
  434. Local obj:Object = objType.NewObject()
  435. AddObjectRef(obj, node)
  436. Return obj
  437. End Method
  438. Method DeserializeFields(objType:TTypeId, obj:Object, node:TxmlNode)
  439. ' does the node contain child nodes?
  440. If node.getChildren() <> Null Then
  441. For Local fieldNode:TxmlNode = EachIn node.getChildren()
  442. ' this should be a field
  443. If fieldNode.GetName() = "field" Then
  444. Local fieldObj:TField = objType.FindField(fieldNode.getAttribute("name"))
  445. Local fieldType:String = fieldNode.getAttribute("type")
  446. Select fieldType
  447. Case "byte", "short", "int"
  448. fieldObj.SetInt(obj, fieldNode.GetContent().toInt())
  449. Case "long"
  450. fieldObj.SetLong(obj, fieldNode.GetContent().toLong())
  451. Case "float"
  452. fieldObj.SetFloat(obj, fieldNode.GetContent().toFloat())
  453. Case "double"
  454. fieldObj.SetDouble(obj, fieldNode.GetContent().toDouble())
  455. Case "uint"
  456. fieldObj.SetUInt(obj, fieldNode.GetContent().toUInt())
  457. Case "ulong"
  458. fieldObj.SetULong(obj, fieldNode.GetContent().toULong())
  459. Case "longint"
  460. fieldObj.SetLongInt(obj, LongInt(fieldNode.GetContent().toLongInt())) ' FIXME : why do we need to cast here?
  461. Case "ulongint"
  462. fieldObj.SetULongInt(obj, fieldNode.GetContent().toULongInt())
  463. Default
  464. If fieldType.StartsWith("array:") Then
  465. Local arrayType:TTypeId = fieldObj.TypeId()
  466. Local arrayElementType:TTypeId = arrayType.ElementType()
  467. ' for file version 3+
  468. Local scalesi:Int[]
  469. Local scales:String[] = fieldNode.getAttribute("scales").split(",")
  470. If scales.length > 1 Then
  471. scalesi = New Int[scales.length]
  472. For Local i:Int = 0 Until scales.length
  473. scalesi[i] = Int(scales[i])
  474. Next
  475. End If
  476. ' for file Version 1+
  477. Select arrayElementType
  478. Case ByteTypeId, ShortTypeId, IntTypeId, LongTypeId, FloatTypeId, DoubleTypeId, UIntTypeId, ULongTypeId, LongIntTypeId, ULongIntTypeId
  479. Local arrayList:String[]
  480. Local content:String = fieldNode.GetContent().Trim()
  481. If content Then
  482. arrayList = content.Split(" ")
  483. Else
  484. arrayList = New String[0]
  485. End If
  486. Local arrayObj:Object = arrayType.NewArray(arrayList.length, scalesi)
  487. fieldObj.Set(obj, arrayObj)
  488. For Local i:Int = 0 Until arrayList.length
  489. arrayType.SetArrayElement(arrayObj, i, arrayList[i])
  490. Next
  491. Default
  492. Local arrayList:TList = fieldNode.getChildren()
  493. If arrayList ' Birdie
  494. Local arrayObj:Object = arrayType.NewArray(arrayList.Count(), scalesi)
  495. fieldObj.Set(obj, arrayObj)
  496. Local i:Int
  497. For Local arrayNode:TxmlNode = EachIn arrayList
  498. Select arrayElementType
  499. Case StringTypeId
  500. arrayType.SetArrayElement(arrayObj, i, arrayNode.GetContent())
  501. Default
  502. ' file version 5 ... array cells can contain references
  503. ' is this a reference?
  504. Local ref:String = arrayNode.getAttribute("ref")
  505. If ref Then
  506. Local objRef:Object = objectMap.ValueForKey(ref)
  507. If objRef Then
  508. arrayType.SetArrayElement(arrayObj, i, objRef)
  509. Else
  510. Throw "Reference not mapped yet : " + ref
  511. End If
  512. Else
  513. arrayType.SetArrayElement(arrayObj, i, DeSerializeObject("", arrayNode))
  514. End If
  515. End Select
  516. i:+ 1
  517. Next
  518. EndIf
  519. End Select
  520. Else
  521. ' is this a reference?
  522. Local ref:String = fieldNode.getAttribute("ref")
  523. If ref Then
  524. Local objRef:Object = objectMap.ValueForKey(ref)
  525. If objRef Then
  526. fieldObj.Set(obj, objRef)
  527. Else
  528. Throw "Reference not mapped yet : " + ref
  529. End If
  530. Else
  531. fieldObj.Set(obj, DeSerializeObject("", fieldNode))
  532. End If
  533. End If
  534. End Select
  535. End If
  536. Next
  537. End If
  538. End Method
  539. Rem
  540. bbdoc:
  541. End Rem
  542. Method DeSerializeObject:Object(Text:String, parent:TxmlNode = Null, parentIsNode:Int = False)
  543. Local node:TxmlNode
  544. If Not doc Then
  545. doc = TxmlDoc.readDoc(Text)
  546. parent = doc.GetRootElement()
  547. fileVersion = parent.GetAttribute("ver").ToInt() ' get the format version
  548. node = TxmlNode(parent.GetFirstChild())
  549. lastNode = node
  550. Else
  551. If Not parent Then
  552. ' find the next element node, if there is one. (content are also "nodes")
  553. node = TxmlNode(lastNode.NextSibling())
  554. 'While node And (node.getType() <> XML_ELEMENT_NODE)
  555. ' node = TxmlNode(node.NextSibling())
  556. 'Wend
  557. If Not node Then
  558. Return Null
  559. End If
  560. lastNode = node
  561. Else
  562. If parentIsNode Then
  563. node = parent
  564. Else
  565. node = TxmlNode(parent.GetFirstChild())
  566. End If
  567. lastNode = node
  568. End If
  569. End If
  570. Local obj:Object
  571. If node Then
  572. Local nodeName:String = node.GetName()
  573. ' Is this an array "Object" ?
  574. If nodeName = "_array_" Then
  575. Local objType:TTypeId = TTypeId.ForName(node.getAttribute("type") + "[]")
  576. Local size:Int = node.getAttribute("size").toInt()
  577. obj = objType.NewArray(size)
  578. AddObjectRef(obj, node)
  579. If size > 0 Then
  580. Local arrayElementType:TTypeId = objType.ElementType()
  581. Select arrayElementType
  582. Case ByteTypeId, ShortTypeId, IntTypeId, LongTypeId, FloatTypeId, DoubleTypeId, UIntTypeId, ULongTypeId, LongIntTypeId, ULongIntTypeId
  583. Local arrayList:String[] = node.GetContent().Split(" ")
  584. For Local i:Int = 0 Until arrayList.length
  585. objType.SetArrayElement(obj, i, arrayList[i])
  586. Next
  587. Default
  588. Local arrayList:TList = node.getChildren()
  589. If arrayList
  590. Local i:Int
  591. For Local arrayNode:TxmlNode = EachIn arrayList
  592. Select arrayElementType
  593. Case StringTypeId
  594. objType.SetArrayElement(obj, i, arrayNode.GetContent())
  595. Default
  596. ' file version 5 ... array cells can contain references
  597. ' is this a reference?
  598. Local ref:String = arrayNode.getAttribute("ref")
  599. If ref Then
  600. Local objRef:Object = objectMap.ValueForKey(ref)
  601. If objRef Then
  602. objType.SetArrayElement(obj, i, objRef)
  603. Else
  604. Throw "Reference not mapped yet : " + ref
  605. End If
  606. Else
  607. objType.SetArrayElement(obj, i, DeSerializeObject("", arrayNode))
  608. End If
  609. End Select
  610. i:+ 1
  611. Next
  612. EndIf
  613. End Select
  614. End If
  615. Else
  616. Local objType:TTypeId = TTypeId.ForName(nodeName)
  617. ' special case for String object
  618. If objType = StringTypeId Then
  619. obj = node.GetContent()
  620. AddObjectRef(obj, node)
  621. Return obj
  622. End If
  623. obj = DeserializeByType(objType, node)
  624. End If
  625. End If
  626. Return obj
  627. End Method
  628. Function GetObjRef:String(obj:Object)
  629. ?ptr64
  630. Return Base36(Long(bbObjectRef(obj)))
  631. ?Not ptr64
  632. Return Base36(Int(bbObjectRef(obj)))
  633. ?
  634. End Function
  635. ?ptr64
  636. Function Base36:String( val:Long )
  637. Const size:Int = 13
  638. ?Not ptr64
  639. Function Base36:String( val:Int )
  640. Const size:Int = 6
  641. ?
  642. Local vLong:Long = $FFFFFFFFFFFFFFFF:Long & Long(Byte Ptr(val))
  643. Local buf:Short[size]
  644. For Local k:Int=(size-1) To 0 Step -1
  645. Local n:Int=(vLong Mod 36) + 48
  646. If n > 57 n:+ 7
  647. buf[k]=n
  648. vLong = vLong / 36
  649. Next
  650. ' strip leading zeros
  651. Local offset:Int = 0
  652. While offset < size
  653. If buf[offset] - Asc("0") Exit
  654. offset:+ 1
  655. Wend
  656. Return String.FromShorts( Short Ptr(buf) + offset,size-offset )
  657. End Function
  658. Method AddSerializer(serializer:TXMLSerializer)
  659. serializers.Insert(serializer.TypeName(), serializer)
  660. serializer.persist = Self
  661. End Method
  662. Method DeserializeReferencedObject:Object(node:TxmlNode, direct:Int = False)
  663. Local obj:Object
  664. Local ref:String = node.getAttribute("ref")
  665. If ref Then
  666. Local objRef:Object = objectMap.ValueForKey(ref)
  667. If objRef Then
  668. obj = objRef
  669. Else
  670. Throw "Reference not mapped yet : " + ref
  671. End If
  672. Else
  673. obj = DeserializeObject("", node, direct)
  674. End If
  675. Return obj
  676. End Method
  677. Method SerializeReferencedObject:TxmlNode(obj:Object, node:TxmlNode)
  678. Local ref:String = GetObjRef(obj)
  679. If Contains(ref, obj)
  680. node.setAttribute("ref", ref)
  681. Else
  682. Return SerializeObject(obj, node)
  683. End If
  684. End Method
  685. End Type
  686. Type TPersistCollisionException Extends TPersistException
  687. Field ref:String
  688. Field obj1:Object
  689. Field obj2:Object
  690. Function CreateException:TPersistCollisionException(ref:String, obj1:Object, obj2:Object)
  691. Local e:TPersistCollisionException = New TPersistCollisionException
  692. e.ref = ref
  693. e.obj1 = obj1
  694. e.obj2 = obj2
  695. Return e
  696. End Function
  697. Method ToString:String()
  698. Return "Persist Collision. Matching ref '" + ref + "' for different objects"
  699. End Method
  700. End Type
  701. Type TPersistException Extends TRuntimeException
  702. End Type
  703. Rem
  704. bbdoc:
  705. End Rem
  706. Type TXMLPersistenceBuilder
  707. Global defaultSerializers:TMap = New TMap
  708. Field serializers:TMap = New TMap
  709. Method New()
  710. For Local s:TXMLSerializer = EachIn defaultSerializers.Values()
  711. Register(s.Clone())
  712. Next
  713. End Method
  714. Rem
  715. bbdoc:
  716. End Rem
  717. Method Build:TPersist()
  718. Local persist:TPersist = New TPersist
  719. persist._inited = True
  720. For Local s:TXMLSerializer = EachIn serializers.Values()
  721. persist.AddSerializer(s)
  722. Next
  723. Return persist
  724. End Method
  725. Rem
  726. bbdoc:
  727. End Rem
  728. Method Register:TXMLPersistenceBuilder(serializer:TXMLSerializer)
  729. serializers.Insert(serializer.TypeName(), serializer)
  730. Return Self
  731. End Method
  732. Rem
  733. bbdoc:
  734. End Rem
  735. Function RegisterDefault(serializer:TXMLSerializer)
  736. defaultSerializers.Insert(serializer.TypeName(), serializer)
  737. End Function
  738. End Type
  739. Rem
  740. bbdoc:
  741. End Rem
  742. Type TXMLSerializer
  743. Field persist:TPersist
  744. Rem
  745. bbdoc: Returns the typeid name that the serializer handles - For example, "TMap"
  746. End Rem
  747. Method TypeName:String() Abstract
  748. Rem
  749. bbdoc: Serializes the object.
  750. End Rem
  751. Method Serialize(tid:TTypeId, obj:Object, node:TxmlNode) Abstract
  752. Rem
  753. bbdoc: Deserializes the object.
  754. End Rem
  755. Method Deserialize:Object(objType:TTypeId, node:TxmlNode) Abstract
  756. Rem
  757. bbdoc: Returns a new instance.
  758. End Rem
  759. Method Clone:TXMLSerializer() Abstract
  760. Rem
  761. bbdoc:
  762. End Rem
  763. Method SerializeObject:TxmlNode(obj:Object, node:TxmlNode)
  764. Return persist.SerializeObject(obj, node)
  765. End Method
  766. Rem
  767. bbdoc: Iterates over all of the object fields, serializing them.
  768. End Rem
  769. Method SerializeFields(tid:TTypeId, obj:Object, node:TxmlNode)
  770. persist.SerializeFields(tid, obj, node)
  771. End Method
  772. Rem
  773. bbdoc:
  774. End Rem
  775. Method GetFileVersion:Int()
  776. Return persist.fileVersion
  777. End Method
  778. Method DeserializeObject:Object(node:TxmlNode, direct:Int = False)
  779. Return persist.DeserializeObject("", node, direct)
  780. End Method
  781. Rem
  782. bbdoc: Returns True if the reference has already been processed.
  783. End Rem
  784. Method Contains:Int(ref:String, obj:Object)
  785. Return persist.Contains(ref, obj)
  786. End Method
  787. Rem
  788. bbdoc: Adds the object reference to the object map, in order to track what object instances have been processed.
  789. End Rem
  790. Method AddObjectRef(ref:String, obj:Object)
  791. persist.objectMap.Insert(ref, obj)
  792. End Method
  793. Rem
  794. bbdoc: Convenience method for checking and adding an object reference.
  795. returns: True if the object has already been processed.
  796. End Rem
  797. Method AddObjectRefAsRequired:Int(ref:String, obj:Object)
  798. If Contains(ref, obj) Then
  799. Return True
  800. End If
  801. AddObjectRef(ref, obj)
  802. End Method
  803. Rem
  804. bbdoc: Adds the xml reference to the object map, in order to track what object instances have been processed.
  805. End Rem
  806. Method AddObjectRefNode(node:TxmlNode, obj:Object)
  807. persist.AddObjectRef(obj, node)
  808. End Method
  809. Rem
  810. bbdoc: Returns a String representation of an object reference, suitable for serializing.
  811. End Rem
  812. Method GetObjRef:String(obj:Object)
  813. Return TPersist.GetObjRef(obj)
  814. End Method
  815. Method GetReferencedObj:Object(ref:String)
  816. Return persist.objectMap.ValueForKey(ref)
  817. End Method
  818. Rem
  819. bbdoc: Serializes a single field.
  820. End Rem
  821. Method SerializeField(f:TField, obj:Object, node:TxmlNode)
  822. persist.SerializeField(f, obj, node)
  823. End Method
  824. Rem
  825. bbdoc:
  826. End Rem
  827. Method CreateObjectInstance:Object(objType:TTypeId, node:TxmlNode)
  828. Return persist.CreateObjectInstance(objType, node)
  829. End Method
  830. Method DeserializeFields(objType:TTypeId, obj:Object, node:TxmlNode)
  831. persist.DeserializeFields(objType, obj, node)
  832. End Method
  833. Method DeserializeReferencedObject:Object(node:TxmlNode, direct:Int = False)
  834. Return persist.DeserializeReferencedObject(node, direct)
  835. End Method
  836. Method SerializeReferencedObject:TxmlNode(obj:Object, node:TxmlNode)
  837. Return persist.SerializeReferencedObject(obj, node)
  838. End Method
  839. End Type
  840. Type TMapXMLSerializer Extends TXMLSerializer
  841. Global nil:TNode = New TMap._root
  842. Method TypeName:String()
  843. Return "TMap"
  844. End Method
  845. Method Serialize(tid:TTypeId, obj:Object, node:TxmlNode)
  846. Local map:TMap = TMap(obj)
  847. If map Then
  848. For Local mapNode:TNode = EachIn map
  849. Local n:TxmlNode = node.addChild("n")
  850. SerializeReferencedObject(mapNode.Key(), n.addChild("k"))
  851. SerializeReferencedObject(mapNode.Value(), n.addChild("v"))
  852. Next
  853. End If
  854. End Method
  855. Method Deserialize:Object(objType:TTypeId, node:TxmlNode)
  856. Local map:TMap = TMap(CreateObjectInstance(objType, node))
  857. If node.getChildren() Then
  858. For Local mapNode:TxmlNode = EachIn node.getChildren()
  859. Local key:Object = DeserializeReferencedObject(TxmlNode(mapNode.getFirstChild()))
  860. Local value:Object = DeserializeReferencedObject(TxmlNode(mapNode.getLastChild()))
  861. map.Insert(key, value)
  862. Next
  863. End If
  864. Return map
  865. End Method
  866. Method Clone:TXMLSerializer()
  867. Return New TMapXMLSerializer
  868. End Method
  869. End Type
  870. Type TListXMLSerializer Extends TXMLSerializer
  871. Method TypeName:String()
  872. Return "TList"
  873. End Method
  874. Method Serialize(tid:TTypeId, obj:Object, node:TxmlNode)
  875. Local list:TList = TList(obj)
  876. If list Then
  877. For Local item:Object = EachIn list
  878. SerializeReferencedObject(item, node.addChild("e"))
  879. Next
  880. End If
  881. End Method
  882. Method Deserialize:Object(objType:TTypeId, node:TxmlNode)
  883. Local list:TList = TList(CreateObjectInstance(objType, node))
  884. If node.getChildren() Then
  885. For Local listNode:TxmlNode = EachIn node.getChildren()
  886. list.AddLast(DeserializeReferencedObject(listNode))
  887. Next
  888. End If
  889. Return list
  890. End Method
  891. Method Clone:TXMLSerializer()
  892. Return New TListXMLSerializer
  893. End Method
  894. End Type
  895. Type TIntMapXMLSerializer Extends TXMLSerializer
  896. Method TypeName:String()
  897. Return "TIntMap"
  898. End Method
  899. Method Serialize(tid:TTypeId, obj:Object, node:TxmlNode)
  900. Local map:TIntMap = TIntMap(obj)
  901. If map Then
  902. For Local mapNode:TIntNode = EachIn map
  903. Local v:TxmlNode = node.addChild("e")
  904. If mapNode.Value() Then
  905. SerializeReferencedObject(mapNode.Value(), v)
  906. End If
  907. v.setAttribute("index", mapNode.Key())
  908. Next
  909. End If
  910. End Method
  911. Method Deserialize:Object(objType:TTypeId, node:TxmlNode)
  912. Local map:TIntMap = TIntMap(CreateObjectInstance(objType, node))
  913. If node.getChildren() Then
  914. For Local mapNode:TxmlNode = EachIn node.getChildren()
  915. Local index:Int = Int(mapNode.getAttribute("index"))
  916. Local obj:Object = DeserializeReferencedObject(mapNode)
  917. map.Insert(index, obj)
  918. Next
  919. End If
  920. Return map
  921. End Method
  922. Method Clone:TXMLSerializer()
  923. Return New TIntMapXMLSerializer
  924. End Method
  925. End Type
  926. Type TStringMapXMLSerializer Extends TXMLSerializer
  927. Method TypeName:String()
  928. Return "TStringMap"
  929. End Method
  930. Method Serialize(tid:TTypeId, obj:Object, node:TxmlNode)
  931. Local map:TStringMap = TStringMap(obj)
  932. If map Then
  933. For Local mapNode:TStringNode = EachIn map
  934. Local n:TxmlNode = node.addChild("n")
  935. SerializeReferencedObject(mapNode.Key(), n.addChild("k"))
  936. SerializeReferencedObject(mapNode.Value(), n.addChild("v"))
  937. Next
  938. End If
  939. End Method
  940. Method Deserialize:Object(objType:TTypeId, node:TxmlNode)
  941. Local map:TStringMap = TStringMap(CreateObjectInstance(objType, node))
  942. If node.getChildren() Then
  943. For Local mapNode:TxmlNode = EachIn node.getChildren()
  944. Local keyNode:TxmlNode = TxmlNode(mapNode.getFirstChild())
  945. Local valueNode:TxmlNode = TxmlNode(mapNode.getLastChild())
  946. Local k:String = String(DeserializeReferencedObject(keyNode))
  947. Local v:Object = DeserializeReferencedObject(valueNode)
  948. map.Insert(k, v)
  949. Next
  950. End If
  951. Return map
  952. End Method
  953. Method Clone:TXMLSerializer()
  954. Return New TStringMapXMLSerializer
  955. End Method
  956. End Type
  957. TXMLPersistenceBuilder.RegisterDefault(New TMapXMLSerializer)
  958. TXMLPersistenceBuilder.RegisterDefault(New TListXMLSerializer)
  959. TXMLPersistenceBuilder.RegisterDefault(New TIntMapXMLSerializer)
  960. TXMLPersistenceBuilder.RegisterDefault(New TStringMapXMLSerializer)
  961. Extern
  962. Function bbEmptyStringPtr:Byte Ptr()
  963. Function bbNullObjectPtr:Byte Ptr()
  964. Function bbEmptyArrayPtr:Byte Ptr()
  965. Function bbObjectRef:Byte Ptr(obj:Object)
  966. End Extern