Trapped-Backend.st 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  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. AxonInterestBase 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. AxonInterestBase 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. AxonizedObject subclass: #Trapper
  69. instanceVariableNames: 'payload'
  70. package: 'Trapped-Backend'!
  71. !Trapper commentStamp!
  72. A portmanteau of 'Trapped wrapper', I am base class for model objects wrapped by Trapped.
  73. Wrapped object is indexed by #('string-at-index' #selector numeric-at-index) array paths. Operations using this indexing are:
  74. - `read:do` to get the indexed content
  75. - `modify:do:` to get and modify the indexed content, and
  76. - `watch:do:` to subscribe to changes of the indexed content.
  77. The wrapped model can be any smalltalk object.
  78. My subclasses need to provide implementation for:
  79. - read:do:
  80. - modify:do:
  81. and must issue these calls when initializing:
  82. - axon: (with a subclass of `AxonBase`)
  83. - model: (with a wrapped object, after `axon:`)!
  84. !Trapper methodsFor: 'accessing'!
  85. model: anObject
  86. payload := anObject.
  87. self axon changedAll
  88. ! !
  89. !Trapper methodsFor: 'action'!
  90. interestOn: anAspect block: aBlock
  91. (anAspect notEmpty and: [ anAspect last isNil ])
  92. ifTrue: [ ^ InterestedInTrapPathSubtree new aspect: anAspect allButLast block: aBlock ]
  93. ifFalse: [ ^ InterestedInTrapPath new aspect: anAspect block: aBlock ]
  94. !
  95. modify: path do: aBlock
  96. self subclassResponsibility
  97. !
  98. read: path do: aBlock
  99. self subclassResponsibility
  100. !
  101. watch: path do: aBlock
  102. self axon addInterest: (self
  103. interestOn: path
  104. block: [ self read: path do: aBlock ])
  105. ! !
  106. Trapper subclass: #DirectTrapper
  107. instanceVariableNames: ''
  108. package: 'Trapped-Backend'!
  109. !DirectTrapper commentStamp!
  110. I am Trapper that directly manipulate
  111. the wrapped model object.!
  112. !DirectTrapper methodsFor: 'action'!
  113. modify: path do: aBlock
  114. | newValue eavModel |
  115. eavModel := path asEavModel.
  116. newValue := aBlock value: (eavModel on: payload).
  117. [ eavModel on: payload put: newValue ] ensure: [ self changed: path ]
  118. !
  119. read: path do: aBlock
  120. | eavModel |
  121. eavModel := path asEavModel.
  122. aBlock value: (eavModel on: payload)
  123. ! !
  124. Trapper subclass: #IsolatingTrapper
  125. instanceVariableNames: ''
  126. package: 'Trapped-Backend'!
  127. !IsolatingTrapper commentStamp!
  128. I am Trapper that guards access
  129. to the wrapped model object via Isolator.
  130. IOW, read:do: gets always its own deep copy,
  131. modify:do: is not reentrant
  132. and upon writing the written part is deep-copied as well
  133. (so modifier does not hold the source of truth
  134. and can change it later).
  135. This also means, a wrapped object and all its parts
  136. must understand `#deepCopy`.!
  137. !IsolatingTrapper methodsFor: 'accessing'!
  138. model: anObject
  139. super model: (Isolator on: anObject)
  140. ! !
  141. !IsolatingTrapper methodsFor: 'action'!
  142. modify: path do: aBlock
  143. | eavModel |
  144. eavModel := ({{#root}},path) asEavModel.
  145. [ payload model: eavModel modify: aBlock ] ensure: [ self changed: path ]
  146. !
  147. read: path do: aBlock
  148. | eavModel |
  149. eavModel := ({{#root}},path) asEavModel.
  150. payload model: eavModel read: aBlock
  151. ! !
  152. !SequenceableCollection methodsFor: '*Trapped-Backend'!
  153. asEavModel
  154. | model |
  155. model := EavModel new.
  156. model getBlock: [ :anObject | anObject atLyst: self ifAbsent: [ nil ] ].
  157. self isEmpty ifFalse: [
  158. model putBlock: [ :anObject :value | anObject atLyst: self ifAbsent: [ nil ] put: value ]].
  159. ^model
  160. ! !