Skip to content

Commit

Permalink
Merge pull request #47 from vitruv-tools/uuid
Browse files Browse the repository at this point in the history
  • Loading branch information
TomWerm authored Apr 5, 2023
2 parents c515a73 + efca656 commit b9cb0d1
Show file tree
Hide file tree
Showing 51 changed files with 1,785 additions and 397 deletions.
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
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) {
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) {
// 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

0 comments on commit b9cb0d1

Please sign in to comment.