Skip to content

Commit 9d89737

Browse files
authored
Merge pull request #65 from modelix/role-ids2
MODELIX-361 models can store role UIDs instead of names
2 parents 76781e2 + f8e72aa commit 9d89737

File tree

20 files changed

+199
-62
lines changed

20 files changed

+199
-62
lines changed

light-model-client/src/commonMain/kotlin/org/modelix/client/light/LightModelClient.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ class LightModelClient(val connection: IConnection, val debugName: String = "")
2424
private var lastMergedVersionHash: String? = null
2525
private val pendingOperations: MutableList<OperationData> = ArrayList()
2626
private var rootNodeId: NodeId? = null
27+
private var usesRoleIds: Boolean = true
2728
private var temporaryIdsSequence: Long = 0
2829
private var changeSetIdSequence: Int = 0
2930
private val nodesReferencingTemporaryIds = HashSet<NodeId>()
@@ -269,6 +270,7 @@ class LightModelClient(val connection: IConnection, val debugName: String = "")
269270
if (version.rootNodeId != null && version.rootNodeId != rootNodeId) {
270271
rootNodeId = version.rootNodeId
271272
}
273+
usesRoleIds = version.usesRoleIds
272274
lastMergedVersionHash = version.versionHash
273275

274276
val oldChildNodes: Set<NodeId> = version.nodes.mapNotNull { nodes[it.nodeId] }.flatMap { it.children.values.flatten() }.toSet()
@@ -350,9 +352,11 @@ class LightModelClient(val connection: IConnection, val debugName: String = "")
350352
}
351353
}
352354

353-
inner class NodeAdapter(var nodeId: NodeId) : INode {
355+
inner class NodeAdapter(var nodeId: NodeId) : INodeEx {
354356
fun getData() = synchronizedRead { getNodeData(nodeId) }
355357

358+
override fun usesRoleIds(): Boolean = usesRoleIds
359+
356360
override fun getArea(): IArea = area
357361

358362
override val isValid: Boolean

light-model-server/src/main/kotlin/org/modelix/model/server/light/LightModelServer.kt

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,10 @@ import org.modelix.model.api.INode
3535
import org.modelix.model.api.INodeReference
3636
import org.modelix.model.api.INodeReferenceSerializer
3737
import org.modelix.model.api.IRole
38+
import org.modelix.model.api.key
3839
import org.modelix.model.api.remove
3940
import org.modelix.model.api.serialize
41+
import org.modelix.model.api.usesRoleIds
4042
import org.modelix.model.server.api.AddNewChildNodeOpData
4143
import org.modelix.model.server.api.ChangeSetId
4244
import org.modelix.model.server.api.DeleteNodeOpData
@@ -191,6 +193,7 @@ class LightModelServer(val port: Int, val rootNode: INode, val ignoredRoles: Set
191193
repositoryId = null,
192194
versionHash = null,
193195
rootNodeId = rootNode.nodeId(),
196+
usesRoleIds = rootNode.usesRoleIds(),
194197
nodes = nodeDataList
195198
)
196199
}
@@ -285,9 +288,9 @@ class LightModelServer(val port: Int, val rootNode: INode, val ignoredRoles: Set
285288
private fun INode.toData(): NodeData {
286289
val conceptRef = concept?.getReference()
287290
val ignored = if (conceptRef == null) IgnoredRoles.EMPTY else ignoredRolesCache.getOrPut(conceptRef) {
288-
val ignoredChildRoles = concept?.getAllChildLinks()?.intersect(ignoredRoles)?.map { it.name }?.toSet() ?: emptySet()
289-
val ignoredPropertyRoles = concept?.getAllProperties()?.intersect(ignoredRoles)?.map { it.name }?.toSet() ?: emptySet()
290-
val ignoredReferenceRoles = concept?.getAllReferenceLinks()?.intersect(ignoredRoles)?.map { it.name }?.toSet() ?: emptySet()
291+
val ignoredChildRoles = concept?.getAllChildLinks()?.intersect(ignoredRoles)?.map { it.key(this) }?.toSet() ?: emptySet()
292+
val ignoredPropertyRoles = concept?.getAllProperties()?.intersect(ignoredRoles)?.map { it.key(this) }?.toSet() ?: emptySet()
293+
val ignoredReferenceRoles = concept?.getAllReferenceLinks()?.intersect(ignoredRoles)?.map { it.key(this) }?.toSet() ?: emptySet()
291294
IgnoredRoles(ignoredChildRoles, ignoredPropertyRoles, ignoredReferenceRoles).optimizeEmpty()
292295
}
293296

metamodel-runtime/src/commonMain/kotlin/org/modelix/metamodel/GeneratedConcept.kt

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -84,19 +84,19 @@ abstract class GeneratedConcept<NodeT : ITypedNode, ConceptT : ITypedConcept>(
8484
}
8585
}
8686

87-
override fun getChildLink(name: String): IChildLink {
88-
return getAllChildLinks().find { it.name == name }
89-
?: throw IllegalArgumentException("Concept ${getLongName()} doesn't contain child link $name")
87+
override fun getChildLink(roleKey: String): IChildLink {
88+
return getAllChildLinks().find { RoleAccessContext.getKey(it) == roleKey }
89+
?: throw IllegalArgumentException("Concept ${getLongName()} doesn't contain child link $roleKey")
9090
}
9191

92-
override fun getProperty(name: String): IProperty {
93-
return getAllProperties().find { it.name == name }
94-
?: throw IllegalArgumentException("Concept ${getLongName()} doesn't contain property $name")
92+
override fun getProperty(roleKey: String): IProperty {
93+
return getAllProperties().find { RoleAccessContext.getKey(it) == roleKey }
94+
?: throw IllegalArgumentException("Concept ${getLongName()} doesn't contain property $roleKey")
9595
}
9696

97-
override fun getReferenceLink(name: String): IReferenceLink {
98-
return getAllReferenceLinks().find { it.name == name }
99-
?: throw IllegalArgumentException("Concept ${getLongName()} doesn't contain reference link $name")
97+
override fun getReferenceLink(roleKey: String): IReferenceLink {
98+
return getAllReferenceLinks().find { RoleAccessContext.getKey(it) == roleKey }
99+
?: throw IllegalArgumentException("Concept ${getLongName()} doesn't contain reference link $roleKey")
100100
}
101101

102102
override fun getReference(): IConceptReference {
@@ -162,24 +162,26 @@ abstract class GeneratedConcept<NodeT : ITypedNode, ConceptT : ITypedConcept>(
162162

163163
class GeneratedProperty<ValueT>(
164164
private val owner: IConcept,
165-
override val name: String,
165+
private val simpleName: String,
166166
private val uid: String?,
167167
override val isOptional: Boolean,
168168
private val serializer: IPropertyValueSerializer<ValueT>
169169
) : ITypedProperty<ValueT>, IProperty {
170170
override fun getConcept(): IConcept = owner
171-
override fun getUID(): String = uid ?: (getConcept().getUID() + "." + name)
171+
override fun getUID(): String = uid ?: (getConcept().getUID() + "." + simpleName)
172172
override fun untyped(): IProperty = this
173173

174174
override fun serializeValue(value: ValueT): String? = serializer.serialize(value)
175175

176176
override fun deserializeValue(serialized: String?): ValueT = serializer.deserialize(serialized)
177+
178+
override fun getSimpleName(): String = simpleName
177179
}
178180
fun IProperty.typed() = this as? ITypedProperty<*>
179181

180182
abstract class GeneratedChildLink<ChildNodeT : ITypedNode, ChildConceptT : ITypedConcept>(
181183
private val owner: IConcept,
182-
override val name: String,
184+
private val simpleName: String,
183185
private val uid: String?,
184186
override val isMultiple: Boolean,
185187
override val isOptional: Boolean,
@@ -191,7 +193,7 @@ abstract class GeneratedChildLink<ChildNodeT : ITypedNode, ChildConceptT : IType
191193

192194
override fun getConcept(): IConcept = owner
193195

194-
override fun getUID(): String = uid ?: (getConcept().getUID() + "." + name)
196+
override fun getUID(): String = uid ?: (getConcept().getUID() + "." + simpleName)
195197

196198
override fun untyped(): IChildLink {
197199
return this
@@ -200,6 +202,8 @@ abstract class GeneratedChildLink<ChildNodeT : ITypedNode, ChildConceptT : IType
200202
override fun castChild(childNode: INode): ChildNodeT {
201203
return childNode.typed(childNodeInterface)
202204
}
205+
206+
override fun getSimpleName(): String = simpleName
203207
}
204208
fun IChildLink.typed() = this as? ITypedChildLink<ITypedNode>
205209

@@ -227,7 +231,7 @@ class GeneratedChildListLink<ChildNodeT : ITypedNode, ChildConceptT : ITypedConc
227231

228232
class GeneratedReferenceLink<TargetNodeT : ITypedNode, TargetConceptT : ITypedConcept>(
229233
private val owner: IConcept,
230-
override val name: String,
234+
private val simpleName: String,
231235
private val uid: String?,
232236
override val isOptional: Boolean,
233237
override val targetConcept: IConcept,
@@ -236,13 +240,15 @@ class GeneratedReferenceLink<TargetNodeT : ITypedNode, TargetConceptT : ITypedCo
236240

237241
override fun getConcept(): IConcept = owner
238242

239-
override fun getUID(): String = uid ?: (getConcept().getUID() + "." + name)
243+
override fun getUID(): String = uid ?: (getConcept().getUID() + "." + simpleName)
240244

241245
override fun untyped(): IReferenceLink = this
242246

243247
override fun castTarget(target: INode): TargetNodeT {
244248
return target.typed(targetNodeInterface)
245249
}
250+
251+
override fun getSimpleName(): String = simpleName
246252
}
247253
fun IReferenceLink.typed() = this as? ITypedReferenceLink<ITypedNode>
248254

model-api/src/commonMain/kotlin/org/modelix/model/api/INode.kt

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,37 @@ interface INode {
5151
fun getReferenceRoles(): List<String>
5252
}
5353

54-
private fun IRole.key(): String = name // TODO use .getUID(), but this is a breaking change
55-
fun INode.getChildren(link: IChildLink): Iterable<INode> = getChildren(link.key())
56-
fun INode.moveChild(role: IChildLink, index: Int, child: INode): Unit = moveChild(role.key(), index, child)
57-
fun INode.addNewChild(role: IChildLink, index: Int, concept: IConcept?) = addNewChild(role.key(), index, concept)
58-
fun INode.getReferenceTarget(link: IReferenceLink): INode? = getReferenceTarget(link.key())
59-
fun INode.setReferenceTarget(link: IReferenceLink, target: INode?): Unit = setReferenceTarget(link.key(), target)
60-
fun INode.getPropertyValue(property: IProperty): String? = getPropertyValue(property.key())
61-
fun INode.setPropertyValue(property: IProperty, value: String?): Unit = setPropertyValue(property.key(), value)
54+
interface INodeEx : INode {
55+
fun usesRoleIds(): Boolean
56+
fun getContainmentLink(): IChildLink? = roleInParent?.let { role ->
57+
parent?.concept?.getAllChildLinks()?.find { (if (usesRoleIds()) it.getUID() else it.getSimpleName()) == role }
58+
}
59+
fun getChildren(link: IChildLink): Iterable<INode> = getChildren(link.key(this))
60+
fun moveChild(role: IChildLink, index: Int, child: INode) = moveChild(role.key(this), index, child)
61+
fun addNewChild(role: IChildLink, index: Int, concept: IConcept?): INode = addNewChild(role.key(this), index, concept)
62+
fun addNewChild(role: IChildLink, index: Int, concept: IConceptReference?): INode = addNewChild(role.key(this), index, concept)
63+
fun getReferenceTarget(link: IReferenceLink): INode? = getReferenceTarget(link.key(this))
64+
fun setReferenceTarget(link: IReferenceLink, target: INode?) = setReferenceTarget(link.key(this), target)
65+
fun getReferenceTargetRef(role: IReferenceLink): INodeReference? = getReferenceTargetRef(role.key(this))
66+
fun setReferenceTarget(role: IReferenceLink, target: INodeReference?) = setReferenceTarget(role.key(this), target)
67+
fun getPropertyValue(property: IProperty): String? = getPropertyValue(property.key(this))
68+
fun setPropertyValue(property: IProperty, value: String?) = setPropertyValue(property.key(this), value)
69+
}
70+
71+
@Deprecated("Use .key(INode), .key(IBranch), .key(ITransaction) or .key(ITree)")
72+
fun IRole.key(): String = RoleAccessContext.getKey(this)
73+
fun IRole.key(node: INode): String = if (node.usesRoleIds()) getUID() else getSimpleName()
74+
fun INode.usesRoleIds(): Boolean = if (this is INodeEx) this.usesRoleIds() else false
75+
fun INode.getChildren(link: IChildLink): Iterable<INode> = if (this is INodeEx) getChildren(link) else getChildren(link.key(this))
76+
fun INode.moveChild(role: IChildLink, index: Int, child: INode): Unit = if (this is INodeEx) moveChild(role, index, child) else moveChild(role.key(this), index, child)
77+
fun INode.addNewChild(role: IChildLink, index: Int, concept: IConcept?) = if (this is INodeEx) addNewChild(role, index, concept) else addNewChild(role.key(this), index, concept)
78+
fun INode.getReferenceTarget(link: IReferenceLink): INode? = if (this is INodeEx) getReferenceTarget(link) else getReferenceTarget(link.key(this))
79+
fun INode.getReferenceTargetRef(link: IReferenceLink): INodeReference? = if (this is INodeEx) getReferenceTargetRef(link) else getReferenceTargetRef(link.key(this))
80+
fun INode.setReferenceTarget(link: IReferenceLink, target: INode?): Unit = if (this is INodeEx) setReferenceTarget(link, target) else setReferenceTarget(link.key(this), target)
81+
fun INode.setReferenceTarget(link: IReferenceLink, target: INodeReference?): Unit = if (this is INodeEx) setReferenceTarget(link, target) else setReferenceTarget(link.key(this), target)
82+
fun INode.getPropertyValue(property: IProperty): String? = if (this is INodeEx) getPropertyValue(property) else getPropertyValue(property.key(this))
83+
fun INode.setPropertyValue(property: IProperty, value: String?): Unit = if (this is INodeEx) setPropertyValue(property, value) else setPropertyValue(property.key(this), value)
84+
6285
fun INode.getConcept(): IConcept? = getConceptReference()?.resolve()
6386
fun INode.getResolvedReferenceTarget(role: String): INode? = getReferenceTargetRef(role)?.resolveNode(getArea())
6487
fun INode.getResolvedConcept(): IConcept? = getConceptReference()?.resolve()
@@ -68,6 +91,22 @@ fun INode.addNewChild(role: String?): INode = addNewChild(role, -1, null as ICon
6891
fun INode.addNewChild(role: String?, concept: IConceptReference?): INode = addNewChild(role, -1, concept)
6992
fun INode.addNewChild(role: String?, concept: IConcept?): INode = addNewChild(role, -1, concept)
7093

94+
fun INode.resolveChildLink(role: String): IChildLink {
95+
val c = this.concept ?: throw RuntimeException("Node has no concept")
96+
return c.getAllChildLinks().find { it.key(this) == role }
97+
?: throw RuntimeException("Child link '$role' not found in concept ${c.getLongName()}")
98+
}
99+
fun INode.resolveReferenceLink(role: String): IReferenceLink {
100+
val c = this.concept ?: throw RuntimeException("Node has no concept")
101+
return c.getAllReferenceLinks().find { it.key(this) == role }
102+
?: throw RuntimeException("Reference link '$role' not found in concept ${c.getLongName()}")
103+
}
104+
fun INode.resolveProperty(role: String): IProperty {
105+
val c = this.concept ?: throw RuntimeException("Node has no concept")
106+
return c.getAllProperties().find { it.key(this) == role }
107+
?: throw RuntimeException("Property '$role' not found in concept ${c.getLongName()}")
108+
}
109+
71110
fun INode.remove() {
72111
parent?.removeChild(this)
73112
}
@@ -76,7 +115,13 @@ fun INode.index(): Int {
76115
return (parent ?: return 0).getChildren(roleInParent).indexOf(this)
77116
}
78117

79-
fun INode.getContainmentLink() = roleInParent?.let { parent?.concept?.getChildLink(it) }
118+
fun INode.getContainmentLink() = if (this is INodeEx) {
119+
getContainmentLink()
120+
} else {
121+
roleInParent?.let { role ->
122+
parent?.concept?.getAllChildLinks()?.find { (if (usesRoleIds()) it.getUID() else it.getSimpleName()) == role }
123+
}
124+
}
80125
fun INode.getRoot(): INode = parent?.getRoot() ?: this
81126
fun INode.isInstanceOf(superConcept: IConcept?): Boolean = concept.let { it != null && it.isSubConceptOf(superConcept) }
82127
fun INode.isInstanceOfSafe(superConcept: IConcept): Boolean = tryGetConcept()?.isSubConceptOf(superConcept) ?: false

model-api/src/commonMain/kotlin/org/modelix/model/api/IRole.kt

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,33 @@ package org.modelix.model.api
33
interface IRole {
44
fun getConcept(): IConcept
55
fun getUID(): String
6-
val name: String
6+
fun getSimpleName(): String
7+
@Deprecated("Use getSimpleName() when showing it to the user or when accessing the model use the INode functions that accept an IRole or use IRole.key(...)")
8+
val name: String get() = RoleAccessContext.getKey(this)
79
val isOptional: Boolean
810
}
11+
12+
@Deprecated("Will be removed after all usages of IRole.name are migrated.")
13+
object RoleAccessContext {
14+
private val value = ContextValue<Boolean>(false)
15+
16+
fun <T> runWith(useRoleIds: Boolean, body: () -> T): T {
17+
return value.computeWith(useRoleIds, body)
18+
}
19+
20+
/**
21+
* Depending on the context returns IRole.getSimpleName() or IRole.getUID()
22+
*/
23+
fun getKey(role: IRole): String {
24+
return if (isUsingRoleIds()) {
25+
// Some implementations use the name to construct a UID. Avoid endless recursions.
26+
runWith(false) { role.getUID() }
27+
} else {
28+
role.getSimpleName()
29+
}
30+
}
31+
32+
fun isUsingRoleIds(): Boolean {
33+
return value.getValue() ?: false
34+
}
35+
}

model-api/src/commonMain/kotlin/org/modelix/model/api/ITransaction.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,5 @@ interface ITransaction {
3232
fun getUserObject(key: Any): Any?
3333
fun putUserObject(key: Any, value: Any?)
3434
}
35+
36+
fun IRole.key(t: ITransaction): String = if (t.tree.usesRoleIds()) getUID() else getSimpleName()

model-api/src/commonMain/kotlin/org/modelix/model/api/ITree.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.modelix.model.api
1717

1818
interface ITree {
19+
fun usesRoleIds(): Boolean
1920
fun getId(): String?
2021
fun visitChanges(oldVersion: ITree, visitor: ITreeChangeVisitor)
2122
fun containsNode(nodeId: Long): Boolean
@@ -45,3 +46,5 @@ interface ITree {
4546
const val DETACHED_NODES_ROLE = "detached"
4647
}
4748
}
49+
50+
fun IRole.key(tree: ITree): String = if (tree.usesRoleIds()) getUID() else getSimpleName()

model-api/src/commonMain/kotlin/org/modelix/model/api/PBranch.kt

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@ class PBranch constructor(@field:Volatile private var tree: ITree, private val i
2828
return branchId
2929
}
3030

31-
fun runWithTransaction(transaction: ITransaction, runnable: () -> Unit) {
32-
contextTransactions.computeWith(transaction as Transaction, runnable)
31+
private fun <T> runWithTransaction(transaction: ITransaction, runnable: () -> T): T {
32+
return RoleAccessContext.runWith(transaction.tree.usesRoleIds()) {
33+
contextTransactions.computeWith(transaction as Transaction, runnable)
34+
}
3335
}
3436

3537
override fun runRead(runnable: () -> Unit) {
@@ -39,7 +41,7 @@ class PBranch constructor(@field:Volatile private var tree: ITree, private val i
3941
} else {
4042
val currentTree = prevTransaction?.tree ?: tree
4143
val t = ReadTransaction(currentTree, this)
42-
contextTransactions.computeWith(t, runnable)
44+
runWithTransaction(t, runnable)
4345
}
4446
}
4547

@@ -51,7 +53,7 @@ class PBranch constructor(@field:Volatile private var tree: ITree, private val i
5153
val oldTree: ITree = prevWrite?.tree ?: tree
5254
val newWrite = WriteTransaction(oldTree, this, idGenerator)
5355
try {
54-
contextTransactions.computeWith(newWrite, runnable)
56+
runWithTransaction(newWrite, runnable)
5557
newWrite.close()
5658
val newTree: ITree = newWrite.tree
5759
if (prevWrite == null) {

model-api/src/commonMain/kotlin/org/modelix/model/api/PNodeAdapter.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import org.modelix.model.area.ContextArea
1919
import org.modelix.model.area.IArea
2020
import org.modelix.model.area.PArea
2121

22-
open class PNodeAdapter(val nodeId: Long, val branch: IBranch) : INode {
22+
open class PNodeAdapter(val nodeId: Long, val branch: IBranch) : INode, INodeEx {
2323

2424
init {
2525
require(nodeId != 0L, { "Invalid node 0" })
@@ -45,6 +45,8 @@ open class PNodeAdapter(val nodeId: Long, val branch: IBranch) : INode {
4545
// DependencyBroadcaster.INSTANCE.dependencyAccessed(new PNodeDependency(branch, nodeId));
4646
}
4747

48+
override fun usesRoleIds() = branch.transaction.tree.usesRoleIds()
49+
4850
override fun moveChild(role: String?, index: Int, child: INode) {
4951
if (child !is PNodeAdapter)
5052
throw RuntimeException(child::class.simpleName + " cannot be moved to " + this::class.simpleName)

model-api/src/commonMain/kotlin/org/modelix/model/api/SimpleChildLink.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
package org.modelix.model.api
1515

1616
class SimpleChildLink(
17-
override val name: String,
17+
private val simpleName: String,
1818
override val isMultiple: Boolean,
1919
override val isOptional: Boolean,
2020
override val targetConcept: IConcept
@@ -26,6 +26,8 @@ class SimpleChildLink(
2626

2727
override fun getUID(): String {
2828
val o = owner
29-
return (if (o == null) name else o.getUID() + "." + name)
29+
return (if (o == null) simpleName else o.getUID() + "." + simpleName)
3030
}
31+
32+
override fun getSimpleName(): String = simpleName
3133
}

0 commit comments

Comments
 (0)