Trapped-Backend.st 5.0 KB

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