Skip to content

Commit 563d73b

Browse files
committed
fix(model-api): cleanup conversion between IRoleReference and strings
1 parent 9c25978 commit 563d73b

File tree

30 files changed

+125
-140
lines changed

30 files changed

+125
-140
lines changed
Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
package org.modelix.model.sync.bulk
22

3+
import org.modelix.kotlin.utils.DelicateModelixApi
34
import org.modelix.model.api.INode
5+
import org.modelix.model.api.IReadableNode
6+
import org.modelix.model.api.getOriginalOrCurrentReference
7+
import org.modelix.model.api.getOriginalReference
8+
import org.modelix.model.api.meta.NullConcept
49
import org.modelix.model.data.NodeData
5-
import org.modelix.model.data.associateWithNotNull
610

711
/**
812
* A ModelExporter exports a node and its subtree in bulk.
@@ -14,15 +18,21 @@ expect class ModelExporter(root: INode)
1418
* This function is recursively called on the node's children.
1519
*/
1620
fun INode.asExported(): NodeData {
17-
val idKey = NodeData.idPropertyKey
21+
return asReadableNode().asExported()
22+
}
23+
24+
fun IReadableNode.asExported(): NodeData {
25+
@OptIn(DelicateModelixApi::class) // json file should contain role IDs
1826
return NodeData(
19-
id = getPropertyValue(idKey) ?: reference.serialize(),
20-
concept = getConceptReference()?.getUID(),
21-
role = roleInParent,
22-
properties = getPropertyRoles().associateWithNotNull { getPropertyValue(it) }.filterKeys { it != idKey },
23-
references = getReferenceRoles().associateWithNotNull {
24-
getReferenceTarget(it)?.getPropertyValue(idKey) ?: getReferenceTargetRef(it)?.serialize()
27+
id = getOriginalOrCurrentReference(),
28+
concept = getConceptReference().takeIf { it != NullConcept.getReference() }?.getUID(),
29+
role = getContainmentLink().getIdOrNameOrNull(),
30+
properties = getAllProperties()
31+
.filterNot { it.first.matches(NodeData.ID_PROPERTY_REF) }
32+
.associate { it.first.getIdOrName() to it.second },
33+
references = getAllReferenceTargetRefs().associate {
34+
it.first.getIdOrName() to (getReferenceTarget(it.first)?.getOriginalReference() ?: it.second.serialize())
2535
},
26-
children = allChildren.map { it.asExported() },
36+
children = getAllChildren().map { it.asExported() },
2737
)
2838
}

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import org.modelix.model.api.IProperty
1010
import org.modelix.model.api.IPropertyReference
1111
import org.modelix.model.api.IReferenceLink
1212
import org.modelix.model.api.IReferenceLinkReference
13-
import org.modelix.model.api.RoleAccessContext
1413
import org.modelix.model.api.getAllConcepts
1514
import kotlin.reflect.KClass
1615

@@ -112,17 +111,17 @@ abstract class GeneratedConcept<NodeT : ITypedNode, ConceptT : ITypedConcept>(
112111
}
113112

114113
override fun getChildLink(roleKey: String): IChildLink {
115-
return getAllChildLinks().find { RoleAccessContext.getKey(it) == roleKey }
114+
return getAllChildLinks().find { it.toReference().matches(roleKey) }
116115
?: throw IllegalArgumentException("Concept ${getLongName()} doesn't contain child link $roleKey")
117116
}
118117

119118
override fun getProperty(roleKey: String): IProperty {
120-
return getAllProperties().find { RoleAccessContext.getKey(it) == roleKey }
119+
return getAllProperties().find { it.toReference().matches(roleKey) }
121120
?: throw IllegalArgumentException("Concept ${getLongName()} doesn't contain property $roleKey")
122121
}
123122

124123
override fun getReferenceLink(roleKey: String): IReferenceLink {
125-
return getAllReferenceLinks().find { RoleAccessContext.getKey(it) == roleKey }
124+
return getAllReferenceLinks().find { it.toReference().matches(roleKey) }
126125
?: throw IllegalArgumentException("Concept ${getLongName()} doesn't contain reference link $roleKey")
127126
}
128127

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

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.modelix.model.api
22

33
import kotlinx.serialization.Serializable
4+
import org.modelix.kotlin.utils.DelicateModelixApi
45

56
/**
67
* Representation of a parent-child relationship between [IConcept]s.
@@ -51,8 +52,17 @@ sealed interface IChildLinkReference : ILinkReference {
5152

5253
override fun toLegacy(): IChildLink
5354

55+
/**
56+
* @see getIdOrName
57+
*/
58+
@DelicateModelixApi
5459
fun getIdOrNameOrNull(): String? = getIdOrName()
5560

61+
/**
62+
* @see getNameOrId
63+
*/
64+
@DelicateModelixApi
65+
@Deprecated("Name based persistence is legacy and IDs should be used")
5666
fun getNameOrIdOrNull(): String? = getNameOrId()
5767

5868
fun matches(other: IChildLinkReference): Boolean
@@ -100,7 +110,7 @@ sealed class AbstractChildLinkReference : AbstractRoleReference(), IChildLinkRef
100110

101111
@Serializable
102112
object NullChildLinkReference : AbstractChildLinkReference() {
103-
override fun toString(): String = "null"
113+
override fun stringForLegacyApi() = null
104114

105115
override fun getIdOrName(): String = "null"
106116

@@ -126,7 +136,7 @@ data class UnclassifiedChildLinkReference(val value: String) : AbstractChildLink
126136
init {
127137
require(value != "null") { "Use NullChildLinkReference" }
128138
}
129-
override fun toString(): String = value
139+
override fun stringForLegacyApi() = value
130140
override fun getStringValue(): String = value
131141
override fun getIdOrName(): String = value
132142
override fun getNameOrId(): String = value
@@ -148,7 +158,7 @@ data class ChildLinkReferenceByName(override val name: String) : AbstractChildLi
148158
init {
149159
require(name != "null") { "Use NullChildLinkReference" }
150160
}
151-
override fun toString(): String = name
161+
override fun stringForLegacyApi() = IRoleReference.encodeStringForLegacyApi(null, name)
152162
override fun getSimpleName(): String = name
153163
override fun getIdOrName(): String = name
154164
override fun getNameOrId(): String = name
@@ -168,7 +178,7 @@ data class ChildLinkReferenceByUID(val uid: String) : AbstractChildLinkReference
168178
init {
169179
require(uid != "null") { "Use NullChildLinkReference" }
170180
}
171-
override fun toString(): String = uid
181+
override fun stringForLegacyApi() = IRoleReference.encodeStringForLegacyApi(uid, null)
172182
override fun getUID(): String = uid
173183
override fun getIdOrName(): String = uid
174184
override fun getNameOrId(): String = uid
@@ -189,7 +199,7 @@ data class ChildLinkReferenceByIdAndName(val uid: String, override val name: Str
189199
require(uid != "null") { "Use NullChildLinkReference" }
190200
require(name != "null") { "Use NullChildLinkReference" }
191201
}
192-
override fun toString(): String = stringForLegacyApi()
202+
override fun stringForLegacyApi() = IRoleReference.encodeStringForLegacyApi(uid, name)
193203
override fun getUID(): String = uid
194204
override fun getSimpleName(): String = name
195205
override fun getIdOrName(): String = uid

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -315,10 +315,9 @@ interface IReplaceableNode : INode {
315315
fun replaceNode(concept: ConceptReference?): INode
316316
}
317317

318-
@Deprecated("Use .key(INode), .key(IBranch), .key(ITransaction) or .key(ITree)")
319-
fun IRole.key(): String = RoleAccessContext.getKey(this)
320-
fun IRole.key(node: INode): String = toReference().key(node)
321-
fun IRoleReference.key(node: INode): String = if (node.usesRoleIds()) getIdOrName() else getNameOrId()
318+
fun IProperty.key(node: INode): String = toReference().stringForLegacyApi()
319+
fun IReferenceLink.key(node: INode): String = toReference().stringForLegacyApi()
320+
fun IRoleReference.key(node: INode): String? = stringForLegacyApi()
322321
fun IChildLinkReference.key(node: INode): String? = when (this) {
323322
is NullChildLinkReference -> null
324323
else -> (this as IRoleReference).key(node)

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ sealed interface IPropertyReference : IRoleReference {
3232

3333
override fun matches(unclassified: String?) = unclassified != null && matches(fromString(unclassified))
3434

35+
override fun stringForLegacyApi(): String
36+
3537
companion object : IRoleReferenceFactory<IPropertyReference> {
3638
/**
3739
* Can be a name or UID or anything else. INode will decide how to resolve it.
@@ -75,6 +77,7 @@ sealed class AbstractPropertyReference : AbstractRoleReference(), IPropertyRefer
7577

7678
@Serializable
7779
data class UnclassifiedPropertyReference(val value: String) : AbstractPropertyReference(), IUnclassifiedRoleReference {
80+
override fun stringForLegacyApi() = value
7881
override fun getStringValue(): String = value
7982
override fun getIdOrName(): String = value
8083
override fun getNameOrId(): String = value
@@ -92,6 +95,7 @@ data class UnclassifiedPropertyReference(val value: String) : AbstractPropertyRe
9295

9396
@Serializable
9497
data class PropertyReferenceByName(override val name: String) : AbstractPropertyReference(), IRoleReferenceByName {
98+
override fun stringForLegacyApi() = IRoleReference.encodeStringForLegacyApi(null, name)
9599
override fun getSimpleName(): String = name
96100
override fun getIdOrName(): String = name
97101
override fun getNameOrId(): String = name
@@ -107,6 +111,7 @@ data class PropertyReferenceByName(override val name: String) : AbstractProperty
107111

108112
@Serializable
109113
data class PropertyReferenceByUID(val uid: String) : AbstractPropertyReference(), IRoleReferenceByUID {
114+
override fun stringForLegacyApi() = IRoleReference.encodeStringForLegacyApi(uid, null)
110115
override fun getUID(): String = uid
111116
override fun getIdOrName(): String = uid
112117
override fun getNameOrId(): String = uid
@@ -122,6 +127,7 @@ data class PropertyReferenceByUID(val uid: String) : AbstractPropertyReference()
122127

123128
@Serializable
124129
data class PropertyReferenceByIdAndName(val uid: String, override val name: String) : AbstractPropertyReference(), IRoleReferenceByUID, IRoleReferenceByName {
130+
override fun stringForLegacyApi() = IRoleReference.encodeStringForLegacyApi(uid, name)
125131
override fun getUID(): String = uid
126132
override fun getSimpleName(): String = name
127133
override fun getIdOrName(): String = uid

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ sealed interface IReferenceLinkReference : ILinkReference {
3333

3434
override fun matches(unclassified: String?) = unclassified != null && matches(fromString(unclassified))
3535

36+
override fun stringForLegacyApi(): String
37+
3638
companion object : IRoleReferenceFactory<IReferenceLinkReference> {
3739
/**
3840
* Can be a name or UID or anything else. INode will decide how to resolve it.
@@ -74,6 +76,7 @@ sealed class AbstractReferenceLinkReference : AbstractRoleReference(), IReferenc
7476

7577
@Serializable
7678
data class UnclassifiedReferenceLinkReference(val value: String) : AbstractReferenceLinkReference(), IUnclassifiedRoleReference {
79+
override fun stringForLegacyApi() = value
7780
override fun getStringValue(): String = value
7881
override fun getIdOrName(): String = value
7982
override fun getNameOrId(): String = value
@@ -91,6 +94,7 @@ data class UnclassifiedReferenceLinkReference(val value: String) : AbstractRefer
9194

9295
@Serializable
9396
data class ReferenceLinkReferenceByName(override val name: String) : AbstractReferenceLinkReference(), IRoleReferenceByName {
97+
override fun stringForLegacyApi() = IRoleReference.encodeStringForLegacyApi(null, name)
9498
override fun getSimpleName(): String = name
9599
override fun getIdOrName(): String = name
96100
override fun getNameOrId(): String = name
@@ -106,6 +110,7 @@ data class ReferenceLinkReferenceByName(override val name: String) : AbstractRef
106110

107111
@Serializable
108112
data class ReferenceLinkReferenceByUID(val uid: String) : AbstractReferenceLinkReference(), IRoleReferenceByUID {
113+
override fun stringForLegacyApi() = IRoleReference.encodeStringForLegacyApi(uid, null)
109114
override fun getUID(): String = uid
110115
override fun getIdOrName(): String = uid
111116
override fun getNameOrId(): String = uid
@@ -121,6 +126,7 @@ data class ReferenceLinkReferenceByUID(val uid: String) : AbstractReferenceLinkR
121126

122127
@Serializable
123128
data class ReferenceLinkReferenceByIdAndName(val uid: String, override val name: String) : AbstractReferenceLinkReference(), IRoleReferenceByUID, IRoleReferenceByName {
129+
override fun stringForLegacyApi() = IRoleReference.encodeStringForLegacyApi(uid, name)
124130
override fun getUID(): String = uid
125131
override fun getSimpleName(): String = name
126132
override fun getIdOrName(): String = uid

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

Lines changed: 16 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
package org.modelix.model.api
22

33
import kotlinx.serialization.Serializable
4-
import org.modelix.kotlin.utils.ContextValue
54
import org.modelix.kotlin.utils.DelicateModelixApi
6-
import org.modelix.kotlin.utils.base64UrlDecoded
7-
import org.modelix.kotlin.utils.base64UrlEncoded
5+
import org.modelix.kotlin.utils.urlDecode
6+
import org.modelix.kotlin.utils.urlEncode
87

98
/**
109
* An [IRole] is a structural feature of a concept.
@@ -34,7 +33,7 @@ interface IRole : IRoleDefinition {
3433
override fun getSimpleName(): String
3534

3635
@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(...)")
37-
val name: String get() = RoleAccessContext.getKey(this)
36+
val name: String get() = toReference().stringForLegacyApi() ?: "null"
3837

3938
/**
4039
* Returns whether this role's value has to be set or not.
@@ -64,31 +63,26 @@ sealed interface IRoleReference {
6463

6564
/**
6665
* Get whichever is available, but prefer the name.
66+
*
67+
* Should only be use in the same cases as [getIdOrName], but when a legacy name based persistence is used.
6768
*/
69+
@DelicateModelixApi
70+
@Deprecated("Name based persistence is legacy and IDs should be used")
6871
fun getNameOrId(): String
6972

7073
/**
7174
* Get whichever is available, but prefer the UID.
75+
*
76+
* Use it only when persisting model data to produce a stable ObjectHash.
77+
* When passing a string value to some legacy API use [stringForLegacyApi] or [toString].
7278
*/
79+
@DelicateModelixApi
7380
fun getIdOrName(): String
7481

7582
/**
7683
* Use this for APIs that still work with strings, but have some implementation that uses [IRoleReference].
7784
*/
78-
fun stringForLegacyApi(): String = when (this) {
79-
is IRoleReferenceByUID -> {
80-
if (this is IRoleReferenceByName) {
81-
encodeStringForLegacyApi(getUID(), getSimpleName())
82-
} else {
83-
encodeStringForLegacyApi(getUID(), null)
84-
}
85-
}
86-
is IRoleReferenceByName -> {
87-
encodeStringForLegacyApi(null, getSimpleName())
88-
}
89-
is IUnclassifiedRoleReference -> getStringValue()
90-
NullChildLinkReference -> "null"
91-
}
85+
fun stringForLegacyApi(): String?
9286

9387
fun matches(unclassified: String?): Boolean
9488

@@ -100,8 +94,8 @@ sealed interface IRoleReference {
10094
if (value == null || value == "null") return factory.fromNull()
10195
val parts = value.split(":")
10296
if (parts.size != 3 || parts[0] != "") return factory.fromUnclassifiedString(value)
103-
val id = parts[1].takeIf { it.isNotEmpty() }?.base64UrlDecoded
104-
val name = parts[2].takeIf { it.isNotEmpty() }?.base64UrlDecoded
97+
val id = parts[1].takeIf { it.isNotEmpty() }?.urlDecode()
98+
val name = parts[2].takeIf { it.isNotEmpty() }?.urlDecode()
10599
return if (id != null) {
106100
if (name != null) {
107101
factory.fromIdAndName(id, name)
@@ -118,7 +112,7 @@ sealed interface IRoleReference {
118112
}
119113

120114
fun encodeStringForLegacyApi(id: String?, name: String?): String {
121-
return ":" + (id?.base64UrlEncoded ?: "") + ":" + (name?.base64UrlEncoded ?: "")
115+
return ":" + (id?.urlEncode() ?: "") + ":" + (name?.urlEncode() ?: "")
122116
}
123117

124118
fun requireNotForLegacyApi(value: String?) {
@@ -180,7 +174,7 @@ sealed interface IRoleReferenceByUID : IRoleReference {
180174

181175
@Serializable
182176
sealed class AbstractRoleReference : IRoleReference {
183-
override fun toString(): String = stringForLegacyApi()
177+
final override fun toString(): String = stringForLegacyApi() ?: "null"
184178
override fun getUID(): String = throw UnsupportedOperationException()
185179
override fun getSimpleName(): String = throw UnsupportedOperationException()
186180
final override fun equals(other: Any?): Boolean {
@@ -214,31 +208,6 @@ sealed class AbstractRoleReference : IRoleReference {
214208
}
215209
}
216210

217-
@Deprecated("Will be removed after all usages of IRole.name are migrated.")
218-
object RoleAccessContext {
219-
private val value = ContextValue<Boolean>(false)
220-
221-
fun <T> runWith(useRoleIds: Boolean, body: () -> T): T {
222-
return value.computeWith(useRoleIds, body)
223-
}
224-
225-
/**
226-
* Depending on the context returns IRole.getSimpleName() or IRole.getUID()
227-
*/
228-
fun getKey(role: IRole): String {
229-
return if (isUsingRoleIds()) {
230-
// Some implementations use the name to construct a UID. Avoid endless recursions.
231-
runWith(false) { role.toReference().getIdOrName() }
232-
} else {
233-
role.toReference().getNameOrId()
234-
}
235-
}
236-
237-
fun isUsingRoleIds(): Boolean {
238-
return value.getValueOrNull() ?: false
239-
}
240-
}
241-
242211
abstract class RoleFromName() : IRole {
243212
override fun getConcept(): IConcept {
244213
throw UnsupportedOperationException()

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

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -114,13 +114,3 @@ interface ITransaction {
114114
*/
115115
fun putUserObject(key: Any, value: Any?)
116116
}
117-
118-
/**
119-
* Returns the key of the receiver role for the given transaction.
120-
*
121-
* @param t the desired transaction
122-
* @return uid of the role, if the tree of the transaction uses role ids, or
123-
* the role name otherwise
124-
*/
125-
fun IRole.key(t: ITransaction): String = toReference().key(t)
126-
fun IRoleReference.key(t: ITransaction): String = if (t.tree.usesRoleIds()) getIdOrName() else getNameOrId()

0 commit comments

Comments
 (0)