Trapped-Backend.st 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  1. Smalltalk current createPackage: 'Trapped-Backend' properties: #{}!
  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. Object subclass: #Isolator
  29. instanceVariableNames: 'root'
  30. package: 'Trapped-Backend'!
  31. !Isolator methodsFor: 'accessing'!
  32. root
  33. ^root
  34. !
  35. root: anObject
  36. root := anObject
  37. ! !
  38. !Isolator methodsFor: 'action'!
  39. model: anEavModel modify: aBlock
  40. | newValue |
  41. newValue := aBlock value: (anEavModel on: self).
  42. anEavModel on: self put: newValue deepCopy
  43. !
  44. model: anEavModel read: aBlock
  45. aBlock value: (anEavModel on: self) deepCopy
  46. ! !
  47. !Isolator class methodsFor: 'instance creation'!
  48. on: anObject
  49. ^self new root: anObject
  50. ! !
  51. Object subclass: #KeyedPubSubBase
  52. instanceVariableNames: ''
  53. package: 'Trapped-Backend'!
  54. !KeyedPubSubBase commentStamp!
  55. I represent a pub-sub based on a key.
  56. I manage key-block subscriptions as well as running blocks that are dirty.
  57. The subscription objects are reponsible of decision if the change is relevant for them.
  58. Subscription object must be subclasses of KeyedSubscriptionBase.
  59. My subclasses must provide implementation for:
  60. subscriptionKey:block: (factory method for creating appropriate subscription object)
  61. as well as bookkeeping of subscriptions:
  62. add:
  63. do:
  64. clean
  65. (optionally) run!
  66. !KeyedPubSubBase methodsFor: 'action'!
  67. changed: key
  68. | needsToRun |
  69. needsToRun := false.
  70. self do: [ :each |
  71. (each accepts: key) ifTrue: [
  72. each flag.
  73. needsToRun := true.
  74. ]
  75. ].
  76. self dirty: needsToRun
  77. !
  78. dirty: aBoolean
  79. aBoolean ifTrue: [[ self run ] fork]
  80. !
  81. on: key hook: aBlock
  82. self add: (self subscriptionKey: key block: aBlock) flag.
  83. self dirty: true
  84. !
  85. run
  86. | needsClean |
  87. needsClean := false.
  88. self do: [ :each |
  89. each isFlagged ifTrue: [
  90. each run.
  91. each isEnabled ifFalse: [ needsClean := true ]
  92. ]
  93. ].
  94. needsClean ifTrue: [ self clean ]
  95. !
  96. subscriptionKey: key block: aBlock
  97. "Should return subclass of KeyedSubscriptionBase"
  98. self subclassReponsibility
  99. ! !
  100. KeyedPubSubBase subclass: #ListKeyedPubSubBase
  101. instanceVariableNames: ''
  102. package: 'Trapped-Backend'!
  103. !ListKeyedPubSubBase commentStamp!
  104. I am base class list-keyed pub-sub.
  105. My subclasses need to provide implementation for:
  106. add:
  107. do:
  108. clean
  109. (optionally) run!
  110. !ListKeyedPubSubBase methodsFor: 'action'!
  111. subscriptionKey: key block: aBlock
  112. ^ListKeyedSubscription new key: key block: aBlock; yourself
  113. ! !
  114. ListKeyedPubSubBase subclass: #SimpleListKeyedPubSub
  115. instanceVariableNames: 'queue'
  116. package: 'Trapped-Backend'!
  117. !SimpleListKeyedPubSub methodsFor: 'accessing'!
  118. add: aSubscription
  119. queue add: aSubscription.
  120. ! !
  121. !SimpleListKeyedPubSub methodsFor: 'bookkeeping'!
  122. clean
  123. queue := queue select: [ :each | each isEnabled ]
  124. ! !
  125. !SimpleListKeyedPubSub methodsFor: 'enumeration'!
  126. do: aBlock
  127. queue do: aBlock
  128. ! !
  129. !SimpleListKeyedPubSub methodsFor: 'initialization'!
  130. initialize
  131. super initialize.
  132. queue := OrderedCollection new
  133. ! !
  134. Error subclass: #KeyedPubSubUnsubscribe
  135. instanceVariableNames: ''
  136. package: 'Trapped-Backend'!
  137. !KeyedPubSubUnsubscribe commentStamp!
  138. SIgnal me from the subscription block to unsubscribe it.!
  139. Object subclass: #KeyedSubscriptionBase
  140. instanceVariableNames: 'key actionBlock flagged'
  141. package: 'Trapped-Backend'!
  142. !KeyedSubscriptionBase methodsFor: 'accessing'!
  143. flag
  144. flagged := true
  145. !
  146. key: anObject block: aBlock
  147. key := anObject.
  148. actionBlock := aBlock
  149. ! !
  150. !KeyedSubscriptionBase methodsFor: 'action'!
  151. run
  152. [[ actionBlock value ] ensure: [ flagged := false ]]
  153. on: KeyedPubSubUnsubscribe do: [ actionBlock := nil ]
  154. ! !
  155. !KeyedSubscriptionBase methodsFor: 'initialization'!
  156. initialize
  157. super initialize.
  158. key := nil.
  159. actionBlock := nil.
  160. flagged := false.
  161. ! !
  162. !KeyedSubscriptionBase methodsFor: 'testing'!
  163. accepts: aKey
  164. "Should return true if change for aKey is relevant for this subscription"
  165. self subclassResponsibility
  166. !
  167. isEnabled
  168. ^actionBlock notNil
  169. !
  170. isFlagged
  171. ^flagged
  172. ! !
  173. KeyedSubscriptionBase subclass: #ListKeyedSubscription
  174. instanceVariableNames: ''
  175. package: 'Trapped-Backend'!
  176. !ListKeyedSubscription methodsFor: 'testing'!
  177. accepts: aKey
  178. ^aKey size <= key size and: [aKey = (key copyFrom: 1 to: aKey size)]
  179. ! !
  180. Object subclass: #ListKeyedEntity
  181. instanceVariableNames: 'dispatcher payload'
  182. package: 'Trapped-Backend'!
  183. !ListKeyedEntity commentStamp!
  184. I am base class for #('string-at-index' #selector numeric-at-index)-array-path-keyed entities,
  185. that moderate access to the wrapped model object via read;do and modify:do:
  186. and allow pub-sub via watch:do:.
  187. This wrapped model can be any smalltalk object.
  188. My subclasses need to provide implementation for:
  189. read:do:
  190. modify:do:
  191. and must issue these calls when initializing:
  192. model: (with a wrapped object)
  193. dispatcher: (with a subclass of ListKeyedPubSubBase)!
  194. !ListKeyedEntity methodsFor: 'accessing'!
  195. dispatcher
  196. ^dispatcher
  197. !
  198. dispatcher: aDispatcher
  199. dispatcher := aDispatcher
  200. !
  201. model: anObject
  202. payload := anObject.
  203. self dispatcher changed: #()
  204. ! !
  205. !ListKeyedEntity methodsFor: 'action'!
  206. watch: path do: aBlock
  207. self dispatcher on: path hook: [ self read: path do: aBlock ]
  208. ! !
  209. ListKeyedEntity subclass: #ListKeyedDirectEntity
  210. instanceVariableNames: ''
  211. package: 'Trapped-Backend'!
  212. !ListKeyedDirectEntity commentStamp!
  213. I am ListKeyedEntity that directly manipulate
  214. the wrapped model object.!
  215. !ListKeyedDirectEntity methodsFor: 'action'!
  216. modify: path do: aBlock
  217. | newValue eavModel |
  218. eavModel := path asEavModel.
  219. newValue := aBlock value: (eavModel on: payload).
  220. [ eavModel on: payload put: newValue ] ensure: [ self dispatcher changed: path ]
  221. !
  222. read: path do: aBlock
  223. | eavModel |
  224. eavModel := path asEavModel.
  225. aBlock value: (eavModel on: payload)
  226. ! !
  227. ListKeyedEntity subclass: #ListKeyedIsolatedEntity
  228. instanceVariableNames: ''
  229. package: 'Trapped-Backend'!
  230. !ListKeyedIsolatedEntity commentStamp!
  231. I am ListKeyedEntity that guards access
  232. to the wrapped model object via Isolator.!
  233. !ListKeyedIsolatedEntity methodsFor: 'accessing'!
  234. model: anObject
  235. super model: (Isolator on: anObject)
  236. ! !
  237. !ListKeyedIsolatedEntity methodsFor: 'action'!
  238. modify: path do: aBlock
  239. | eavModel |
  240. eavModel := ({#root},path) asEavModel.
  241. [ payload model: eavModel modify: aBlock ] ensure: [ self dispatcher changed: path ]
  242. !
  243. read: path do: aBlock
  244. | eavModel |
  245. eavModel := ({#root},path) asEavModel.
  246. payload model: eavModel read: aBlock
  247. ! !
  248. !Object methodsFor: '*Trapped-Backend'!
  249. reverseTrapAt: anObject
  250. ^nil
  251. !
  252. reverseTrapAt: anObject put: value
  253. self error: 'Trapped cannot put at ', self class name, ' type key.'
  254. ! !
  255. !SequenceableCollection methodsFor: '*Trapped-Backend'!
  256. asEavModel
  257. | model |
  258. model := EavModel new.
  259. model getBlock: [ :anObject |
  260. self inject: anObject into: [ :soFar :segment |
  261. soFar ifNotNil: [ segment reverseTrapAt: soFar ]]].
  262. self isEmpty ifFalse: [
  263. model putBlock: [ :anObject :value | | penultimate |
  264. penultimate := self allButLast inject: anObject into: [ :soFar :segment |
  265. soFar ifNotNil: [ segment reverseTrapAt: soFar ]].
  266. self last reverseTrapAt: penultimate put: value ]].
  267. ^model
  268. ! !
  269. !String methodsFor: '*Trapped-Backend'!
  270. reverseTrapAt: anObject
  271. ^anObject at: self ifAbsent: [nil]
  272. !
  273. reverseTrapAt: anObject put: value
  274. ^anObject at: self put: value
  275. ! !
  276. !Symbol methodsFor: '*Trapped-Backend'!
  277. reverseTrapAt: anObject
  278. ^[anObject perform: self] on: MessageNotUnderstood do: [^nil]
  279. !
  280. reverseTrapAt: anObject put: value
  281. ^anObject perform: (self, ':') asSymbol withArguments: { value }
  282. ! !
  283. !Number methodsFor: '*Trapped-Backend'!
  284. reverseTrapAt: anObject
  285. ^anObject at: self ifAbsent: [nil]
  286. !
  287. reverseTrapAt: anObject put: value
  288. ^anObject at: self put: value
  289. ! !