Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate to UUIDs #47

Merged
merged 32 commits into from
Apr 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
b85718a
implement UuidResolver
Oct 28, 2022
9527f46
adapt tests to compile against uuid resolver
Oct 28, 2022
53d3ce7
fix atomic change tests for uuid resolver
Oct 28, 2022
61206f8
hide id resolver
Oct 28, 2022
ebd6129
fix change recorder tests for uuid resolver
Oct 28, 2022
ab4601a
preserve UUIDs in EChangeResolverAndApplicator
Nov 3, 2022
f5bf42b
cleanup documentation
Nov 3, 2022
9db0776
cleanup ChangeRecorder
Nov 3, 2022
2db317b
minor naming improvements
Nov 4, 2022
af3f214
refactor UUID resource set resolving
Nov 9, 2022
f7a1cf2
temporarily expose tools.vitruv.change.atomic.id as it is needed by l…
Nov 9, 2022
bfcf157
cleanup and document UUID resolver interface
Nov 9, 2022
468ae6a
correct remove indexes in atomic change tests
Nov 10, 2022
cb2c9f3
enforce no deviating registration for UUIDs or EObjects
Nov 10, 2022
6aed330
fix order of tag comparison in correspondences to avoid nullpointer e…
Nov 14, 2022
cba9925
adapt `ChangePublishingTestView` to resolve resource UUIDs before ret…
Nov 15, 2022
446adfe
allow null resources in change publishing test view
Nov 15, 2022
9d2a4c5
separate change recorder and id management
Nov 25, 2022
a59ca07
assert correct resource set when adding elements to uuid resolver
Nov 25, 2022
af7c82c
fix bug in EChangeResolverAndApplicator leading to some UUIDs not bei…
Nov 25, 2022
aacb3c3
temporarily expose UuidResolver of `NonTransactionalTestView` as need…
Nov 25, 2022
6a61360
correct test: previously the same element was deleted twice
Dec 27, 2022
06bab01
restore hierarchical id manager functionality
Feb 5, 2023
8196322
minor cleanups regarding UUIDs
Feb 6, 2023
3580572
correct registration of UUIDs when applying changes
Feb 6, 2023
bd19ce9
implement code suggestions
JanWittler Mar 6, 2023
72eef5f
added tests for UUID resolver
JanWittler Mar 6, 2023
2dc363b
Add missing documentation
TomWerm Mar 16, 2023
aa3f4ed
Inline method parts
TomWerm Mar 16, 2023
b62a81c
Update UuidResolverImpl.java
TomWerm Mar 16, 2023
4a32950
Remove misleading comment
TomWerm Mar 16, 2023
efca656
Implement code suggestions
TomWerm Mar 16, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion bundles/tools.vitruv.change.atomic/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ Export-Package: tools.vitruv.change.atomic,
tools.vitruv.change.atomic.root.impl,
tools.vitruv.change.atomic.root.util,
tools.vitruv.change.atomic.resolve,
tools.vitruv.change.atomic.id
tools.vitruv.change.atomic.id,
tools.vitruv.change.atomic.uuid
Require-Bundle: org.eclipse.xtend.lib,
org.apache.log4j,
org.eclipse.emf.ecore.edit,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package tools.vitruv.change.atomic

import org.eclipse.emf.ecore.EObject
import tools.vitruv.change.atomic.eobject.EObjectAddedEChange
import tools.vitruv.change.atomic.eobject.EObjectSubtractedEChange
import tools.vitruv.change.atomic.eobject.EObjectExistenceEChange
import tools.vitruv.change.atomic.eobject.EObjectSubtractedEChange
import tools.vitruv.change.atomic.feature.FeatureEChange
import org.eclipse.emf.ecore.EObject
import tools.vitruv.change.atomic.id.IdResolver

import static com.google.common.base.Preconditions.checkArgument
import static com.google.common.base.Preconditions.checkState
import tools.vitruv.change.atomic.id.IdResolver

/**
* Provides logic for initializing the IDs within changes.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package tools.vitruv.change.atomic

import org.eclipse.emf.ecore.EObject
import tools.vitruv.change.atomic.eobject.CreateEObject
import tools.vitruv.change.atomic.eobject.EObjectAddedEChange
import tools.vitruv.change.atomic.eobject.EObjectExistenceEChange
import tools.vitruv.change.atomic.eobject.EObjectSubtractedEChange
import tools.vitruv.change.atomic.feature.FeatureEChange
import tools.vitruv.change.atomic.uuid.UuidResolver

import static com.google.common.base.Preconditions.checkArgument
import static com.google.common.base.Preconditions.checkState

/**
* Provides logic for initializing the UUIDs within changes.
*/
class EChangeUuidManager {
val UuidResolver uuidResolver

/**
* Initializes the manager with a {@link UuidResolver}.
*
* @param uuidResolver -
* the {@link UuidResolver} to use for UUID management
*/
new(UuidResolver uuidResolver) {
checkArgument(uuidResolver !== null, "uuid resolver must not be null")
this.uuidResolver = uuidResolver
}

def static void setOrGenerateIds(Iterable<EChange> eChanges, UuidResolver uuidResolver) {
setOrGenerateIds(eChanges, uuidResolver, true)
}

def static void setOrGenerateIds(Iterable<EChange> eChanges, UuidResolver uuidResolver, boolean endTransaction) {
val manager = new EChangeUuidManager(uuidResolver)
eChanges.forEach [ eChange |
manager.setOrGenerateIds(eChange)
]
if (endTransaction) {
uuidResolver.endTransaction
}
}

def void setOrGenerateIds(EChange eChange) {
switch eChange {
CreateEObject<?>:
setOrGenerateCreatedEObjectUuid(eChange)
EObjectExistenceEChange<?>:
setAffectedEObjectUuid(eChange)
FeatureEChange<?,?>:
setAffectedEObjectUuid(eChange)
}
switch eChange {
EObjectSubtractedEChange<?>:
setOldValueUuid(eChange)
}
switch eChange {
EObjectAddedEChange<?>:
setOrGenerateNewValueUuid(eChange)
}
}

private def String getUuid(EObject object) {
val id = uuidResolver.getUuid(object)
checkState(id !== null, "uuid must not be null")
return id
}

private def String getOrGenerateUuid(EObject object) {
if (uuidResolver.hasUuid(object)) {
return getUuid(object)
}
uuidResolver.registerEObject(object)
}

private def void setOrGenerateNewValueUuid(EObjectAddedEChange<?> addedEChange) {
if (addedEChange.newValue === null) {
return;
}
addedEChange.newValueID = addedEChange.newValue.getOrGenerateUuid
}

private def void setOldValueUuid(EObjectSubtractedEChange<?> subtractedEChange) {
if (subtractedEChange.oldValue === null) {
return;
}
subtractedEChange.oldValueID = subtractedEChange.oldValue.uuid
}

private def void setAffectedEObjectUuid(EObjectExistenceEChange<?> existenceChange) {
val affectedEObject = existenceChange.affectedEObject
checkArgument(affectedEObject !== null, "existence change must have an affected EObject: %s", existenceChange)
existenceChange.affectedEObjectID = affectedEObject.uuid
}

private def void setOrGenerateCreatedEObjectUuid(CreateEObject<?> existenceChange) {
val affectedEObject = existenceChange.affectedEObject
checkArgument(affectedEObject !== null, "existence change must have an affected EObject: %s", existenceChange)
existenceChange.affectedEObjectID = affectedEObject.getOrGenerateUuid
}

private def void setAffectedEObjectUuid(FeatureEChange<?, ?> featureChange) {
val affectedEObject = featureChange.affectedEObject
checkArgument(affectedEObject !== null, "feature change must have an affected EObject: %s", featureChange)
featureChange.affectedEObjectID = affectedEObject.uuid
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import tools.vitruv.change.atomic.id.IdResolver
/**
* Static class for resolving EChanges internally.
*/
package class AtomicEChangeResolver {
package class AtomicEChangeIdResolver {
val IdResolver idResolver

new (IdResolver idResolver) {
Expand Down
TomWerm marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
package tools.vitruv.change.atomic.resolve

import org.eclipse.emf.common.util.URI
import org.eclipse.emf.ecore.EObject
import org.eclipse.emf.ecore.EStructuralFeature
import org.eclipse.emf.ecore.util.EcoreUtil
import tools.vitruv.change.atomic.EChange
import tools.vitruv.change.atomic.eobject.CreateEObject
import tools.vitruv.change.atomic.eobject.DeleteEObject
import tools.vitruv.change.atomic.eobject.EObjectExistenceEChange
import tools.vitruv.change.atomic.feature.FeatureEChange
import tools.vitruv.change.atomic.feature.reference.InsertEReference
import tools.vitruv.change.atomic.feature.reference.RemoveEReference
import tools.vitruv.change.atomic.feature.reference.ReplaceSingleValuedEReference
import tools.vitruv.change.atomic.root.InsertRootEObject
import tools.vitruv.change.atomic.root.RemoveRootEObject
import tools.vitruv.change.atomic.root.RootEChange
import tools.vitruv.change.atomic.uuid.UuidResolver

import static com.google.common.base.Preconditions.checkArgument
import static com.google.common.base.Preconditions.checkState

/**
* Static class for resolving EChanges internally.
*/
package class AtomicEChangeUuidResolver {
val UuidResolver uuidResolver

new (UuidResolver uuidResolver) {
checkArgument(uuidResolver !== null, "UUID resolver must not be null")
this.uuidResolver = uuidResolver
}

/**
* Resolves {@link FeatureEChange} attributes {@code affectedEObject} and {@code affectedFeature}.
* @param change The change which should be resolved.
*/
def private <A extends EObject, F extends EStructuralFeature> void resolveFeatureEChange(FeatureEChange<A, F> change) {
checkArgument(change.affectedEObjectID !== null, "change %s must have an affected EObject ID", change)
checkArgument(change.affectedFeature !== null, "change %s must have an affected feature", change)
if (uuidResolver.hasEObject(change.affectedEObjectID)) {
change.affectedEObject = uuidResolver.getEObject(change.affectedEObjectID) as A
}
change.affectedEObject.checkNotNullAndNotProxy(change, "affected object")
}

def private EObject resolveObject(String valueId) {
if (valueId === null) {
return null
}
return uuidResolver.getEObject(valueId)
}

/**
* Resolves {@link EObjectExistenceEChange} attribute {@code affectedEObject}.
* @param change The change which should be resolved.
* @param isNewObject true if the given change creates the object, false if it deletes the object
*/
def private <A extends EObject> void resolveEObjectExistenceEChange(EObjectExistenceEChange<A> change, boolean isNewObject) {
TomWerm marked this conversation as resolved.
Show resolved Hide resolved
checkArgument(change.affectedEObjectID !== null, "change %s must have an affected EObject ID", change)

// Resolve the affected object
if (isNewObject) {
// Check if ID resolver may still contain the removed object
if (uuidResolver.hasEObject(change.affectedEObjectID)) {
val stillExistingObject = uuidResolver.getEObject(change.affectedEObjectID) as A
change.affectedEObject = stillExistingObject
change.affectedEObject.checkNotNullAndNotProxy(change, "affected object")
} else {
// Create new one
val newObject = EcoreUtil.create(change.affectedEObjectType) as A
change.affectedEObject = newObject
uuidResolver.registerEObject(change.affectedEObjectID, newObject)
}
} else {
// Object still exists
change.affectedEObject = uuidResolver.getEObject(change.affectedEObjectID) as A
change.affectedEObject.checkNotNullAndNotProxy(change, "affected object")
}

if (change.idAttributeValue !== null) {
EcoreUtil.setID(change.affectedEObject, change.idAttributeValue)
}
}

/**
* Resolves {@link RootEChange} attribute {@code resource}.
* @param change The change which should be resolved.
*/
def private void resolveRootEChange(RootEChange change) {
// Get resource where the root object will be inserted / removed.
change.resource = uuidResolver.getResource(URI.createURI(change.uri))
}

/**
* Resolves the value of an {@link RootEChange}.
* @param change The change whose value shall be resolved.
* @param value The value that should be used if no value can be resolved for an id linked in the given change
* @param isInserted {@code true} if the concrete value is already inserted into the resource.
* Depends on the kind of the change and the model state.
* @returns The resolved value.
*/
def private <T extends EObject> resolveRootValue(RootEChange change, T value, boolean isInserted) {
TomWerm marked this conversation as resolved.
Show resolved Hide resolved
// Resolve the root object
val result = if (change instanceof InsertRootEObject<?>) {
uuidResolver.getEObject(change.newValueID)
} else if (change instanceof RemoveRootEObject<?>) {
uuidResolver.getEObject(change.oldValueID)
} else {
value
}

if (isInserted) {
checkState(0 <= change.index && change.index < change.resource.contents.size &&
change.resource.contents.get(change.index) === result,
"invalid index in change %s for resolved object %s", change, result)
}
return result
}

/**
* Dispatch method for resolving the {@link EChange}.
* @param change The change which should be resolved.
*/
def package dispatch void resolve(EChange change) {
// If an EChange reaches this point, there is a dispatch method missing for the concrete type.
throw new UnsupportedOperationException("change of type " + change?.eClass + " is not supported")
}

/**
* Dispatch method for resolving the {@link FeatureEChange} EChange.
* @param change The change which should be resolved.
*/
def package dispatch void resolve(FeatureEChange<EObject, EStructuralFeature> change) {
change.resolveFeatureEChange()
}

/**
* Dispatch method for resolving the {@link InsertEReference} EChange.
* @param change The change which should be resolved.
*/
def package dispatch void resolve(InsertEReference<EObject, EObject> change) {
change.resolveFeatureEChange()
change.newValue = change.newValueID.resolveObject()
change.newValue.checkNotNullAndNotProxy(change, "new value")
}

/**
* Dispatch method for resolving the {@link RemoveEReference} EChange.
* @param change The change which should be resolved.
*/
def package dispatch void resolve(RemoveEReference<EObject, EObject> change) {
change.resolveFeatureEChange()
change.oldValue = change.oldValueID.resolveObject()
change.oldValue.checkNotNullAndNotProxy(change, "old value")
}

/**
* Dispatch method for resolving the {@link ReplaceSingleValuedEReferenceEReference} EChange.
* @param change The change which should be resolved.
*/
def package dispatch void resolve(ReplaceSingleValuedEReference<EObject, EObject> change) {
change.resolveFeatureEChange()
change.newValue = change.newValueID.resolveObject()
change.oldValue = change.oldValueID.resolveObject()
change.oldValue.checkEitherNullOrNotProxy(change, "old value")
change.newValue.checkEitherNullOrNotProxy(change, "new value")
}

/**
* Dispatch method for resolving the {@link InsertRootEObject} EChange.
* @param change The change which should be resolved.
*/
def package dispatch void resolve(InsertRootEObject<EObject> change) {
change.resolveRootEChange()
change.newValue = change.resolveRootValue(change.newValue, false)
change.newValue.checkNotNullAndNotProxy(change, "new value")
}

/**
* Dispatch method for resolving the {@link RemoveRootEObject} EChange.
* @param change The change which should be resolved.
*/
def package dispatch void resolve(RemoveRootEObject<EObject> change) {
change.resolveRootEChange()
change.oldValue = change.resolveRootValue(change.oldValue, true)
change.oldValue.checkNotNullAndNotProxy(change, "old value")
}

/**
* Dispatch method for resolving the {@link CreateEObject} EChange.
* @param change The change which should be resolved.
*/
def package dispatch void resolve(CreateEObject<EObject> change) {
change.resolveEObjectExistenceEChange(true)
}

/**
* Dispatch method for resolving the {@link DeleteEObject} EChange.
* @param change The change which should be resolved.
*/
def package dispatch void resolve(DeleteEObject<EObject> change) {
change.resolveEObjectExistenceEChange(false)
}

def private static void checkNotNullAndNotProxy(EObject object, EChange change, String nameOfElementInChange) {
checkState(object !== null, "%s of change %s was resolved to null", nameOfElementInChange, change)
checkState(!object.eIsProxy, "%s of change %s was resolved to a proxy", nameOfElementInChange, object)
}

def private static void checkEitherNullOrNotProxy(EObject object, EChange change, String nameOfElementInChange) {
checkState(object === null || !object.eIsProxy, "%s of change %s was resolved to a proxy", nameOfElementInChange, object)
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,7 @@ import tools.vitruv.change.atomic.id.IdResolver
* Utility class for applying and resolving a given EChange.
*/
@Utility
class EChangeResolverAndApplicator {
static def <C extends EChange> C unresolve(C eChange) {
val copy = EcoreUtil.copy(eChange)
EChangeUnresolver.unresolve(copy)
return copy
}

class EChangeIdResolverAndApplicator {
static def EChange resolveBefore(EChange eChange, IdResolver idResolver) {
return resolveCopy(eChange, idResolver)
}
Expand Down Expand Up @@ -94,7 +88,7 @@ class EChangeResolverAndApplicator {
def private static EChange resolveCopy(EChange change, IdResolver idResolver) {
checkArgument(!change.isResolved, "change must not be resolved when trying to resolve")
var EChange copy = EcoreUtil.copy(change)
new AtomicEChangeResolver(idResolver).resolve(copy)
new AtomicEChangeIdResolver(idResolver).resolve(copy)
return copy
}

Expand Down
Loading