Trapped-Backend.st 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. Smalltalk createPackage: 'Trapped-Backend'!
  2. (Smalltalk packageAt: 'Trapped-Backend') imports: {'lyst/Lyst'}!
  3. Object subclass: #EavModel
  4. instanceVariableNames: 'getBlock putBlock'
  5. package: 'Trapped-Backend'!
  6. !EavModel commentStamp!
  7. External actor value model.!
  8. !EavModel methodsFor: 'accessing'!
  9. getBlock: aBlock
  10. getBlock := aBlock
  11. !
  12. on: anObject
  13. "Returns value of model applied on object"
  14. ^getBlock value: anObject
  15. !
  16. on: anObject put: anObject2
  17. "Puts a value via model applied on object"
  18. ^putBlock value: anObject value: anObject2
  19. !
  20. putBlock: aBlock
  21. putBlock := aBlock
  22. ! !
  23. !EavModel methodsFor: 'initialization'!
  24. initialize
  25. super initialize.
  26. getBlock := [ self error: 'No getter block.' ].
  27. putBlock := [ self error: 'No putter block.' ].
  28. ! !
  29. AxonInterest subclass: #InterestedInTrapPath
  30. instanceVariableNames: ''
  31. package: 'Trapped-Backend'!
  32. !InterestedInTrapPath methodsFor: 'testing'!
  33. accepts: anAspect
  34. ^anAspect size <= aspect size and: [anAspect = (aspect copyFrom: 1 to: anAspect size)]
  35. ! !
  36. AxonInterest subclass: #InterestedInTrapPathSubtree
  37. instanceVariableNames: ''
  38. package: 'Trapped-Backend'!
  39. !InterestedInTrapPathSubtree methodsFor: 'testing'!
  40. accepts: anAspect
  41. ^anAspect size <= aspect size
  42. ifTrue: [anAspect = (aspect copyFrom: 1 to: anAspect size)]
  43. ifFalse: [aspect = (anAspect copyFrom: 1 to: aspect size)]
  44. ! !
  45. Object subclass: #Isolator
  46. instanceVariableNames: 'root'
  47. package: 'Trapped-Backend'!
  48. !Isolator methodsFor: 'accessing'!
  49. root
  50. ^root
  51. !
  52. root: anObject
  53. root := anObject
  54. ! !
  55. !Isolator methodsFor: 'action'!
  56. model: anEavModel modify: aBlock
  57. | newValue |
  58. newValue := aBlock value: (anEavModel on: self).
  59. anEavModel on: self put: newValue deepCopy
  60. !
  61. model: anEavModel read: aBlock
  62. aBlock value: (anEavModel on: self) deepCopy
  63. ! !
  64. !Isolator class methodsFor: 'instance creation'!
  65. on: anObject
  66. ^self new root: anObject
  67. ! !
  68. Object subclass: #TrappedPosition
  69. instanceVariableNames: 'path model'
  70. package: 'Trapped-Backend'!
  71. !TrappedPosition methodsFor: 'accessing'!
  72. model
  73. ^model
  74. !
  75. path
  76. ^path
  77. !
  78. path: anArray model: aTrappedMW
  79. path := anArray.
  80. model := aTrappedMW
  81. ! !
  82. !TrappedPosition methodsFor: 'action'!
  83. modify: aBlock
  84. self model modify: self path do: aBlock
  85. !
  86. read: aBlock
  87. self model read: self path do: aBlock
  88. !
  89. watch: aBlock
  90. self model axon addInterest: (self
  91. interestOn: self path
  92. block: [ self read: aBlock ])
  93. ! !
  94. !TrappedPosition methodsFor: 'private'!
  95. interestOn: anAspect block: aBlock
  96. (anAspect notEmpty and: [ anAspect last isNil ])
  97. ifTrue: [ ^ InterestedInTrapPathSubtree new aspect: anAspect allButLast block: aBlock ]
  98. ifFalse: [ ^ InterestedInTrapPath new aspect: anAspect block: aBlock ]
  99. ! !
  100. AxonizedObject subclass: #Trapper
  101. instanceVariableNames: 'payload'
  102. package: 'Trapped-Backend'!
  103. !Trapper commentStamp!
  104. A portmanteau of 'Trapped wrapper', I am base class for model objects wrapped by Trapped.
  105. Wrapped object is indexed by #('string-at-index' #selector numeric-at-index) array paths. Operations using this indexing are:
  106. - `read:do` to get the indexed content
  107. - `modify:do:` to get and modify the indexed content, and
  108. - `watch:do:` to subscribe to changes of the indexed content.
  109. The wrapped model can be any smalltalk object.
  110. My subclasses need to provide implementation for:
  111. - read:do:
  112. - modify:do:
  113. and must issue these calls when initializing:
  114. - axon: (with a subclass of `Axon`)
  115. - model: (with a wrapped object, after `axon:`)!
  116. !Trapper methodsFor: 'accessing'!
  117. model: anObject
  118. payload := anObject.
  119. self axon changedAll
  120. ! !
  121. !Trapper methodsFor: 'action'!
  122. modify: path do: aBlock
  123. self subclassResponsibility
  124. !
  125. read: path do: aBlock
  126. self subclassResponsibility
  127. ! !
  128. Trapper subclass: #DirectTrapper
  129. instanceVariableNames: ''
  130. package: 'Trapped-Backend'!
  131. !DirectTrapper commentStamp!
  132. I am Trapper that directly manipulate
  133. the wrapped model object.!
  134. !DirectTrapper methodsFor: 'action'!
  135. modify: path do: aBlock
  136. | newValue eavModel |
  137. eavModel := path asEavModel.
  138. newValue := aBlock value: (eavModel on: payload).
  139. [ eavModel on: payload put: newValue ] ensure: [ self changed: path ]
  140. !
  141. read: path do: aBlock
  142. | eavModel |
  143. eavModel := path asEavModel.
  144. aBlock value: (eavModel on: payload)
  145. ! !
  146. Trapper subclass: #IsolatingTrapper
  147. instanceVariableNames: ''
  148. package: 'Trapped-Backend'!
  149. !IsolatingTrapper commentStamp!
  150. I am Trapper that guards access
  151. to the wrapped model object via Isolator.
  152. IOW, read:do: gets always its own deep copy,
  153. modify:do: is not reentrant
  154. and upon writing the written part is deep-copied as well
  155. (so modifier does not hold the source of truth
  156. and can change it later).
  157. This also means, a wrapped object and all its parts
  158. must understand `#deepCopy`.!
  159. !IsolatingTrapper methodsFor: 'accessing'!
  160. model: anObject
  161. super model: (Isolator on: anObject)
  162. ! !
  163. !IsolatingTrapper methodsFor: 'action'!
  164. modify: path do: aBlock
  165. | eavModel |
  166. eavModel := ({{#root}},path) asEavModel.
  167. [ payload model: eavModel modify: aBlock ] ensure: [ self changed: path ]
  168. !
  169. read: path do: aBlock
  170. | eavModel |
  171. eavModel := ({{#root}},path) asEavModel.
  172. payload model: eavModel read: aBlock
  173. ! !
  174. !SequenceableCollection methodsFor: '*Trapped-Backend'!
  175. asEavModel
  176. | model |
  177. model := EavModel new.
  178. model getBlock: [ :anObject | anObject atLyst: self ifAbsent: [ nil ] ].
  179. self isEmpty ifFalse: [
  180. model putBlock: [ :anObject :value | anObject atLyst: self ifAbsent: [ nil ] put: value ]].
  181. ^model
  182. ! !