-
Notifications
You must be signed in to change notification settings - Fork 3
/
Scheme.kt
314 lines (260 loc) · 9.85 KB
/
Scheme.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
package space.kscience.dataforge.meta
import space.kscience.dataforge.meta.descriptors.Described
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.get
import space.kscience.dataforge.meta.descriptors.validate
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.misc.ThreadSafe
import space.kscience.dataforge.names.*
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
import kotlin.reflect.KProperty1
/**
* A base for delegate-based or descriptor-based scheme. [Scheme] has an empty constructor to simplify usage from [MetaReader].
*
* @param prototype default values provided by this scheme
*/
public open class Scheme(
private var prototype: Meta? = null,
descriptor: MetaDescriptor? = null,
) : Described, MetaRepr, MutableMetaProvider, Configurable {
/**
* Meta to be mutated by this scheme
*/
internal var target: MutableMeta = MutableMeta()
/**
* A descriptor of this scheme
*/
final override var descriptor: MetaDescriptor? = descriptor
private set
final override val meta: ObservableMutableMeta = SchemeMeta(Name.EMPTY)
/**
* This method must be called before the scheme could be used
*/
internal fun initialize(
target: MutableMeta,
prototype: Meta,
descriptor: MetaDescriptor?,
) {
this.target = target
this.prototype = prototype
this.descriptor = descriptor
}
/**
* Check if property with given [name] could be assigned to [meta]
*/
public open fun validate(name: Name, meta: Meta?): Boolean {
val descriptor = descriptor?.get(name)
return descriptor?.validate(meta) ?: true
}
override fun get(name: Name): MutableMeta? = meta[name]
override fun set(name: Name, node: Meta?) {
if (validate(name, meta)) {
meta[name] = node
} else {
error("Validation failed for node $node at $name")
}
}
override fun setValue(name: Name, value: Value?) {
val valueDescriptor = descriptor?.get(name)
if (valueDescriptor?.validate(value) != false) {
meta.setValue(name, value)
} else error("Value $value is not validated by $valueDescriptor")
}
override fun toMeta(): Laminate = Laminate(meta, descriptor?.defaultNode)
private val listeners: MutableList<MetaListener> = mutableListOf()
override fun toString(): String = meta.toString()
private inner class SchemeMeta(val pathName: Name) : ObservableMutableMeta {
override val self get() = this
override var value: Value?
get() = target[pathName]?.value
?: prototype?.get(pathName)?.value
?: descriptor?.get(pathName)?.defaultValue
set(value) {
val oldValue = target[pathName]?.value
target[pathName] = value
if (oldValue != value) {
invalidate(Name.EMPTY)
}
}
override val items: Map<NameToken, ObservableMutableMeta>
get() {
val targetKeys = target[pathName]?.items?.keys ?: emptySet()
val defaultKeys = prototype?.get(pathName)?.items?.keys ?: emptySet()
return (targetKeys + defaultKeys).associateWith { SchemeMeta(pathName + it) }
}
override fun invalidate(name: Name) {
listeners.forEach { it.callback(this@Scheme.meta, pathName + name) }
}
@ThreadSafe
override fun onChange(owner: Any?, callback: Meta.(name: Name) -> Unit) {
listeners.add(MetaListener(owner) { changedName ->
if (changedName.startsWith(pathName)) {
this@Scheme.meta.callback(changedName.removeFirstOrNull(pathName)!!)
}
})
}
@ThreadSafe
override fun removeListener(owner: Any?) {
listeners.removeAll { it.owner === owner }
}
override fun toString(): String = Meta.toString(this)
override fun equals(other: Any?): Boolean = Meta.equals(this, other as? Meta)
override fun hashCode(): Int = Meta.hashCode(this)
override fun set(name: Name, node: Meta?) {
target[pathName + name] = node
invalidate(name)
}
override fun getOrCreate(name: Name): ObservableMutableMeta = SchemeMeta(pathName + name)
@DFExperimental
override fun attach(name: Name, node: ObservableMutableMeta) {
set(name, node)
node.onChange(this) { changeName ->
set(name + changeName, this[changeName])
}
}
}
}
/**
* Relocate scheme target onto given [MutableMeta]. Old provider does not get updates anymore.
* The Current state of the scheme that os used as a default.
*/
@DFExperimental
public fun <T : Scheme> T.retarget(provider: MutableMeta): T = apply {
initialize(provider, meta.seal(), descriptor)
}
/**
* A shortcut to edit a [Scheme] object in-place
*/
public inline operator fun <T : Scheme> T.invoke(block: T.() -> Unit): T = apply(block)
/**
* Create a copy of given [Scheme]
*/
public inline fun <T : Scheme> T.copy(spec: SchemeSpec<T>, block: T.() -> Unit = {}): T =
spec.read(meta.copy()).apply(block)
/**
* A specification for simplified generation of wrappers
*/
public open class SchemeSpec<T : Scheme>(
private val builder: () -> T,
) : MetaConverter<T> {
override val descriptor: MetaDescriptor? get() = null
override fun readOrNull(source: Meta): T = builder().also {
it.initialize(MutableMeta(), source, descriptor)
}
/**
* Write changes made to the [Scheme] to target [MutableMeta]. If the empty [Scheme] contains any data it is copied to the target.
*/
public fun write(target: MutableMeta): T = empty().also {
target.update(it.meta)
it.initialize(target, Meta.EMPTY, descriptor)
}
/**
* Generate a blank object. The object could contain some elements if they are defined in a constructor
*/
public fun empty(): T = builder().also {
it.initialize(MutableMeta(), it.target, descriptor)
}
override fun convert(obj: T): Meta = obj.meta
/**
* A convenience method to use specifications in builders
*/
public inline operator fun invoke(action: T.() -> Unit): T = empty().apply(action)
}
/**
* Update a [MutableMeta] using given specification
*/
public fun <T : Scheme> MutableMeta.updateWith(
spec: SchemeSpec<T>,
action: T.() -> Unit,
): T = spec.write(this).apply(action)
/**
* Update configuration using given specification
*/
public fun <T : Scheme> Configurable.updateWith(
spec: SchemeSpec<T>,
action: T.() -> Unit,
): T = spec.write(meta).apply(action)
/**
* A delegate that uses a [MetaReader] to wrap a child of this provider
*/
public fun <T : Scheme> MutableMeta.scheme(
spec: SchemeSpec<T>,
key: Name? = null,
): ReadWriteProperty<Any?, T> = object : ReadWriteProperty<Any?, T> {
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
val name = key ?: property.name.asName()
return spec.write(getOrCreate(name))
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
val name = key ?: property.name.asName()
set(name, value.toMeta())
}
}
public fun <T : Scheme> Scheme.scheme(
spec: SchemeSpec<T>,
key: Name? = null,
): ReadWriteProperty<Any?, T> = meta.scheme(spec, key)
/**
* A delegate that uses a [MetaReader] to wrap a child of this provider.
* Returns null if meta with given name does not exist.
*/
public fun <T : Scheme> MutableMeta.schemeOrNull(
spec: SchemeSpec<T>,
key: Name? = null,
): ReadWriteProperty<Any?, T?> = object : ReadWriteProperty<Any?, T?> {
override fun getValue(thisRef: Any?, property: KProperty<*>): T? {
val name = key ?: property.name.asName()
return if (get(name) == null) null else spec.write(getOrCreate(name))
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
val name = key ?: property.name.asName()
if (value == null) remove(name)
else set(name, value.toMeta())
}
}
public fun <T : Scheme> Scheme.schemeOrNull(
spec: SchemeSpec<T>,
key: Name? = null,
): ReadWriteProperty<Any?, T?> = meta.schemeOrNull(spec, key)
/**
* A delegate that uses a [MetaReader] to wrap a list of child providers.
* If children are mutable, the changes in list elements are reflected on them.
* The list is a snapshot of children state, so change in structure is not reflected on its composition.
*/
public fun <T : Scheme> MutableMeta.listOfScheme(
spec: SchemeSpec<T>,
key: Name? = null,
): ReadWriteProperty<Any?, List<T>> = object : ReadWriteProperty<Any?, List<T>> {
override fun getValue(thisRef: Any?, property: KProperty<*>): List<T> {
val name = key ?: property.name.asName()
return getIndexedList(name).map { spec.write(it as MutableMeta) }
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: List<T>) {
val name = key ?: property.name.asName()
setIndexed(name, value.map { it.toMeta() })
}
}
public fun <T : Scheme> Scheme.listOfScheme(
spec: SchemeSpec<T>,
key: Name? = null,
): ReadWriteProperty<Any?, List<T>> = meta.listOfScheme(spec, key)
/**
* Use the value of the property in a [callBack].
* The callback is called once immediately after subscription to pass the initial value.
*
* Optional [owner] property is used for
*/
public fun <S : Scheme, T> S.useProperty(
property: KProperty1<S, T>,
owner: Any? = null,
callBack: S.(T) -> Unit,
) {
//Pass initial value.
callBack(property.get(this))
meta.onChange(owner) { name ->
if (name.startsWith(property.name.asName())) {
callBack(property.get(this@useProperty))
}
}
}