Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
edelta.refactorings.lib.EdeltaRefactorings.edelta Maven / Gradle / Ivy
import edelta.lib.EdeltaModelMigrator.EObjectFunction
import edelta.refactorings.lib.helper.EdeltaFeatureDifferenceFinder
import java.util.Collection
import java.util.HashMap
import java.util.List
import java.util.function.Function
import java.util.function.Predicate
import java.util.stream.Collectors
import org.eclipse.emf.ecore.EAttribute
import org.eclipse.emf.ecore.EClass
import org.eclipse.emf.ecore.EClassifier
import org.eclipse.emf.ecore.EDataType
import org.eclipse.emf.ecore.EEnum
import org.eclipse.emf.ecore.ENamedElement
import org.eclipse.emf.ecore.EObject
import org.eclipse.emf.ecore.EReference
import org.eclipse.emf.ecore.EStructuralFeature
import org.eclipse.emf.ecore.ETypedElement
import org.eclipse.emf.ecore.util.EcoreUtil
import java.util.function.Consumer
import edelta.lib.EdeltaModelMigrator
package edelta.refactorings.lib
metamodel "ecore"
def addMandatoryAttribute(EClass eClass, String attributeName, EDataType dataType): EAttribute {
return eClass.addNewEAttribute(attributeName, dataType) [
makeSingleRequired
]
}
def addMandatoryReference(EClass eClass, String referenceName, EClass type): EReference {
return eClass.addNewEReference(referenceName, type) [
makeSingleRequired
]
}
/**
* Changes this feature to single (upper = 1); concerning model migration,
* it takes the first element of the previous model object's collection
* for this feature.
*
* @param feature
*/
def changeToSingle(EStructuralFeature feature) {
changeUpperBound(feature, 1)
}
/**
* Changes this feature to multiple (upper = -1); concerning model migration,
* it makes sure that a collection is created if the previous model object's value was set.
*
* @param feature
*/
def changeToMultiple(EStructuralFeature feature) {
changeUpperBound(feature, -1)
}
/**
* Changes this feature to multiple with the given upper bound; concerning model migration,
* it makes sure that a collection is created with at most the specified upper bound
* if the previous model object's value was set, discarding possible additional values in
* the original collection.
*
* @param feature
* @param upperBound
*/
def changeUpperBound(EStructuralFeature feature, int upperBound) {
feature.upperBound = upperBound
modelMigration[
copyRule(
isRelatedTo(feature),
multiplicityAwareCopy(feature)
)
]
}
/**
* Merges the given attributes into a single new attribute in the containing class.
* The attributes must be compatible (same containing class, same type, same cardinality, etc).
*
* @param newAttributeName
* @param attributes
* @param valueMerger is used to merge the values of the original
* features in the new model
* @return the new attribute added to the containing class of the attributes
*/
def mergeAttributes(String newAttributeName, Collection attributes,
Function, Object> valueMerger) : EAttribute {
return mergeFeatures(newAttributeName, attributes, valueMerger, null)
}
/**
* Merges the given references into a single new reference in the containing class.
* The references must be compatible (same containing class, same type, same cardinality, etc).
*
* @param newReferenceName
* @param references
* @param valueMerger is used to merge the values of the original
* features in the new model
* @param postCopy executed after the model migrations
* @return the new reference added to the containing class of the references
*/
def mergeReferences(String newReferenceName, Collection references,
Function, EObject> valueMerger, Runnable postCopy) : EReference {
return mergeFeatures(newReferenceName, references, valueMerger, postCopy)
}
/**
* Merges the given features into a single new feature in the containing class.
* The features must be compatible (same containing class, same type, same cardinality, etc).
*
* @param meant for both {@link EAttribute} and {@link EReference}
* @param the type for the function (either {@link Object} or {@link EObject})
* @param newFeatureName
* @param features
* @param valueMerger is used to merge the values of the original
* features in the new model
* @param postCopy if not null, it is executed after the model migrations
* @return the new feature added to the containing class of the features
*/
def mergeFeatures(String newFeatureName, Collection features,
Function, V> valueMerger, Runnable postCopy) : T {
checkNoDifferences(
features,
new EdeltaFeatureDifferenceFinder().ignoringName,
"The two features cannot be merged"
)
val firstFeature = features.head
val owner = firstFeature.EContainingClass
val mergedFeature = firstFeature.copyToAs(owner, newFeatureName)
removeAllElements(features)
modelMigration[
copyRule(
wasRelatedTo(firstFeature),
[_, oldObj, newObj |
var originalFeatures = features.stream()
.map[a | getOriginal(a)]
// for references we must get the copied EObject
var oldValues = originalFeatures
.map[f | oldObj.eGet(f)]
.collect(Collectors.toList())
// the explicit cast is required to avoid compilation error
// in the generated Java code:
// The method apply(Collection) in the type Function,V> is not applicable for the arguments (Object)
var merged = valueMerger.apply(getMigrated(oldValues) as Collection)
newObj.eSet(mergedFeature, merged)
],
postCopy
)
]
return mergedFeature
}
/**
* Merges the given features into a single new feature in the containing class.
* The features must be compatible (same containing class, same type, same cardinality, etc).
*
* TODO: this has to be removed once all the examples and tests have been ported
* to model migration as well.
*
* @param
* @param newFeatureName
* @param features
* @return the new feature added to the containing class of the features
*/
def mergeFeatures(String newFeatureName, Collection features) : T {
checkNoDifferences(
features,
new EdeltaFeatureDifferenceFinder().ignoringName,
"The two features cannot be merged"
)
val feature = features.head
val owner = feature.EContainingClass
val copy = feature.copyToAs(owner, newFeatureName)
removeAllElements(features)
return copy
}
/**
* Merges the given features into the single given existing feature in the containing class.
* The features must be compatible (same containing class, same type, same cardinality, etc)
* and their types must be subtypes of the specified feature.
*
* @param feature the features will be merged into this feature
* @param features
*/
def mergeFeatures(EStructuralFeature feature, Collection features) : EStructuralFeature {
checkCompliant(feature, features)
checkNoDifferences(#[feature] + features,
new EdeltaFeatureDifferenceFinder()
.ignoringName.ignoringType,
"The two features cannot be merged")
removeAllElements(features)
return feature
}
/**
* Merges the given features into a single new feature, with the given type, in the containing class.
* The features must be compatible (same containing class, same type, same cardinality, etc)
* and their types must be subtypes of the specified type.
*
* @param newFeatureName
* @param type
* @param features
*/
def mergeFeatures(String newFeatureName, EClassifier type, Collection features) : EStructuralFeature {
val feature = features.head
val owner = feature.EContainingClass
val copy = feature.copyToAs(owner, newFeatureName, type)
mergeFeatures(copy, features)
return copy
}
/**
* Split the given attribute into several attributes with the same type
* as the original one, using the specified names. The original attribute
* will be removed. The passed valueSplitter is used to migrate the
* original object into the corresponding split ones.
*
* @param attribute
* @param newNames
* @param valueSplitter
* @return the collection of features
*/
def splitAttribute(EAttribute attribute, Collection newNames,
Function> valueSplitter) :
Collection {
val splitAttributes = splitFeature(attribute, newNames)
modelMigration[
copyRule(
wasRelatedTo(attribute),
[feature, oldObj, newObj |
var oldValue = oldObj.eGet(feature)
var splittedValues = valueSplitter
.apply(oldValue).iterator()
for (splitFeature : splitAttributes) {
if (!splittedValues.hasNext())
return;
newObj.eSet(splitFeature, splittedValues.next())
}
]
)
]
return splitAttributes
}
/**
* Split the given reference into several references with the same type
* as the original one, using the specified names. The original reference
* will be removed. The passed valueSplitter is used to migrate the
* original object into the corresponding split ones.
*
* @param reference
* @param newNames
* @param valueSplitter
* @param postCopy executed after the model migrations
* @return the collection of features
*/
def splitReference(EReference reference,
Collection newNames,
Function> valueSplitter,
Runnable postCopy) :
Collection {
val splitReferences = splitFeature(reference, newNames)
modelMigration[
copyRule(
wasRelatedTo(reference),
[feature, oldObj, newObj |
// for references we must get the migrated EObject
var oldValue = getMigrated(
getValueAsEObject(oldObj, feature))
var splittedValues = valueSplitter
.apply(oldValue).iterator()
for (splitFeature : splitReferences) {
if (!splittedValues.hasNext())
return;
newObj.eSet(splitFeature, splittedValues.next())
}
],
postCopy
)
]
return splitReferences
}
/**
* Split the given feature into several features with the same type
* as the original one, using the specified names. The original feature
* will be removed.
*
* @param
* @param featureToSplit
* @param newFeatureNames
* @return the collection of features
*/
def splitFeature(T featureToSplit,
Collection newFeatureNames) : Collection {
checkNotMany(featureToSplit,
"Cannot split 'many' feature")
checkNoBidirectionalReferences(#[featureToSplit],
"Cannot split a bidirectional reference")
val owner = featureToSplit.EContainingClass
var splitFeatures = newFeatureNames.stream()
.map[newName |
copyToAs(featureToSplit, owner, newName)
]
.collect(Collectors.toList())
removeElement(featureToSplit)
return splitFeatures;
}
/**
* Given an EAttribute, expected to have an EEnum type, creates a subclass of
* the containing class for each value of the referred EEnum
* (each subclass is given a name corresponding to the the EEnumLiteral,
* all lowercase but the first letter, for example, given the literal
* "LITERAL1", the subclass is given the name "Literal1").
* The attribute will then be removed and so will the EEnum.
* The original containing EClass is made abstract.
*
* @param attr
* @return the collection of created subclasses
*/
def enumToSubclasses(EAttribute attr) : Collection {
val type = attr.EAttributeType
if (type instanceof EEnum) {
val owner = attr.EContainingClass
val createdSubclasses = introduceSubclasses(owner,
type.ELiterals.map[toString.toLowerCase.toFirstUpper].toList,
[oldObj |
val literalValue =
oldObj.getValueFromFeatureName(attr.name).toString()
val correspondingSubclass =
owner.findSiblingByName(literalValue.toLowerCase.toFirstUpper)
return createInstance(correspondingSubclass)
]
)
removeElement(type) // will also remove the attribute
return createdSubclasses
} else {
showError(attr,
"Not an EEnum: " + getEObjectRepr(type)
)
return null
}
}
/**
* Creates the classes with the given names as subclasses of the passed
* superClass, which will then be made abstract; the objectMigrator is applied
* for migrating objects that were originally instances of the superClass.
*
* @param superClass
* @param name
* @param objectMigration
*/
def introduceSubclasses(EClass superClass, Collection names,
EObjectFunction objectMigrator
) : Collection {
superClass.makeAbstract
val subclasses = names.map[name|
superClass.addNewSubclass(name)
].toList
modelMigration[
createInstanceRule(
isRelatedTo(superClass),
objectMigrator
)
]
return subclasses
}
/**
* Given a collection of subclasses, which are expected to be direct subclasses of
* an EClass, say superclass, generates an EEnum (in the superclass' package)
* with the specified name, representing the inheritance relation,
* with an EEnumLiteral for each subclass (the name is the name
* of the subclass in uppercase); the subclasses are removed, and
* an attributed is added to the superclass with the created EEnum as type
* (the name is the name of the EEnum, first letter lowercase).
*
* For example, given the name "BaseType" and the collection of classes
* {"Derived1", "Derived2"} subclasses of the superclass "Base",
* it creates the EEnum "BaseType" with literals "DERIVED1", "DERIVED2",
* (the values will be incremental numbers starting from 0,
* according to the order of the subclasses in the collection)
* it adds to "Base" the EAttribute "baseType" of type "BaseType".
* The EClasses "Derived1" and "Derived2" are removed from the package.
*
* @param name the name for the created EEnum
* @param subclasses
* @return the created EAttribute
*/
def subclassesToEnum(String name, Collection subclasses) : EAttribute {
checkNoFeatures(subclasses)
val superclass = getSingleDirectSuperclass(subclasses)
val enum = superclass.addNewEEnumAsSibling(name) [
subclasses.forEach[subClass, index |
val enumLiteralName = subClass.name.toUpperCase
addNewEEnumLiteral(enumLiteralName) => [
value = index
]
]
]
val attribute = superclass.addNewEAttribute(enum.fromTypeToFeatureName, enum)
superclass.makeConcrete
removeAllElements(subclasses)
modelMigration[
createInstanceRule(
wasRelatedToAtLeastOneOf(subclasses),
[oldObj |
val enumLiteralName = enum.getEEnumLiteral(
oldObj.eClass.name.toUpperCase)
return createInstance(
superclass,
[newObj | newObj.eSet(attribute, enumLiteralName)]
)
]
)
]
return attribute
}
/**
* Extracts the specified features into a new class with the given name.
* The features must belong to the same class.
* In the containing class a containment required reference to
* the extracted class will be created (its name will be the name
* of the extracted class with the first letter lowercase).
*
* @param name the name for the extracted class
* @param features the features to extract
* @return the added EReference to the extracted metaclass
*/
def extractClass(String name, Collection features) {
if (features.empty)
return null // TODO: error?
checkNoBidirectionalReferences(features,
"Cannot extract bidirectional references")
val owner = findSingleOwner(features)
val extracted = owner.addNewEClassAsSibling(name)
val reference = owner.addMandatoryReference(name.toFirstLower, extracted)
makeContainmentBidirectional(reference)
features.moveAllTo(extracted)
modelMigration[
copyRule(
wasRelatedToAtLeastOneOf(features),
[origFeature, origObj, migratedObj |
var extractedObj = migratedObj.getOrSetEObject(reference,
[extracted.createInstance])
extractedObj.eSet(
getMigrated(origFeature),
getMigrated(origObj.eGet(origFeature))
)
]
)
]
return reference
}
/**
* Inlines the features of the specified class into the single class
* that has a containment reference to the specified class.
* The specified class will then be removed.
*
* @param cl
* @return the features of the original class
*/
def inlineClass(EClass cl) {
inlineClass(cl, "")
}
/**
* Inlines the features of the specified class into the single class
* that has a containment reference to the specified class.
* The specified class will then be removed.
*
* @param cl
* @param prefix the prefix for the names of the inlined features
* @return the features of the original class
*/
def inlineClass(EClass cl, String prefix) {
val reference = findSingleContainmentReferenceToThisClass(cl)
checkNotMany(reference,
"Cannot inline in a 'many' reference")
val featuresToInline = cl.EStructuralFeatures
.filter[it !== reference.EOpposite] // skip the possible back reference
.toList
featuresToInline.forEach[name = prefix + name]
featuresToInline.moveAllTo(reference.EContainingClass)
removeElement(cl)
modelMigration[
copyRule(
wasRelatedTo(reference),
[origFeature, origObj, migratedObj |
val origReferredObj = origObj.getValueAsEObject(origFeature)
for (feature : featuresToInline) {
migratedObj.eSet(
feature,
getMigrated(origReferredObj.eGet(getOriginal(feature)))
)
}
]
)
]
return featuresToInline
}
/**
* Makes the EReference, which is assumed to be already part of an EClass,
* a single required containment reference, adds to the referred
* type, which is assumed to be set, an opposite required single reference.
* @param reference
*/
def makeContainmentBidirectional(EReference reference) {
reference.makeContainment
val owner = reference.EContainingClass
val referredType = reference.EReferenceType
referredType.addMandatoryReference(owner.fromTypeToFeatureName, owner) => [
makeBidirectional(reference)
]
}
/**
* Replaces an EReference with an EClass (with the given name, the same package
* as the package of the reference's containing class),
* updating possible opposite reference,
* so that a relation can be extended with additional features.
* The original reference will be made a containment reference,
* (its other properties will not be changed)
* to the added EClass (and made bidirectional).
*
* For example, given
*
* b2 b1
* A <-------> C
*
*
* (where the opposite "b2" might not be present)
* if we pass "b1" and the name "B", then the result will be
*
*
* a b1 b2 c
* A <-------> B <------> C
*
*
* where "b1" will be a containment reference.
* Note the names inferred for the new additional opposite references.
*
* @param name the name for the extracted class
* @param reference the reference to turn into a reference to the extracted class
* @return the extracted class
*/
def referenceToClass(String name, EReference reference) {
checkNotContainment(reference,
"Cannot apply referenceToClass on containment reference")
val ePackage = reference.EContainingClass.EPackage
val extracted = ePackage.addNewEClass(name)
val extractedRef = extracted.addMandatoryReference(
reference.EType.fromTypeToFeatureName, reference.EReferenceType)
val eOpposite = reference.EOpposite
if (eOpposite !== null) {
eOpposite.makeBidirectional(extractedRef)
}
reference.EType = extracted
reference.makeContainmentBidirectional
// handle the migration of the reference that now has to refer
// to a new object (of the extracted class), or, transparently
// to a list of new objects in case of a multi reference
modelMigration[
copyRule(
[feature |
isRelatedTo(feature, reference) ||
isRelatedTo(feature, eOpposite)],
[feature, oldObj, newObj |
// feature: the feature of the original metamodel
// oldObj: the object of the original model
// newObj: the object of the new model, already created
// the opposite reference now changed its type
// so we have to skip the copy or we'll have a ClassCastException
// the bidirectionality will be implied in the next migrator
if (isRelatedTo(feature, eOpposite))
return
// retrieve the original value, wrapped in a list
// so this works (transparently) for both single and multi feature
// discard possible extra values, in case the multiplicity has changed
var oldValueOrValues =
getValueForFeature(oldObj, feature,
reference.getUpperBound())
// for each old value create a new object for the
// extracted class, by setting the reference's value
// with the copied value of that reference
var copies = oldValueOrValues.stream()
.map[oldValue |
// since this is NOT a containment reference
// the referred oldValue has already been copied
val copy = getMigrated(oldValue as EObject)
return createInstance(extracted) [
o | o.eSet(extractedRef, copy)
]
]
.collect(Collectors.toList())
// in the new object set the value or values (transparently)
// with the created object (or objects, again, transparently)
setValueForFeature(
newObj, reference, copies)
]
);
]
return extracted
}
/**
* Given an EClass, which is meant to represent a relation,
* removes such a class, transforming the relation into an EReference.
*
* For example, given
*
* a b1 b2 c
* A <-------> B <------> C
*
*
* (where the opposites "a" and "b2" might not be present)
* if we pass "B", then the result will be
*
* b2 b1
* A <-------> C
*
*
* @param cl
* @return the EReference that now represents the relation, that is,
* the EReference originally of type cl ("b1" above)
*/
def classToReference(EClass cl) : EReference {
// search for a single EReference that has type cl ("b1" above)
val reference = findSingleContainmentAmongReferencesToThisClass(cl)
// "A" above
val owner = reference.EContainingClass
// search for a single EReference ("c" above) in cl that has not type owner
// (the one with type owner, if exists, would be the EOpposite
// of reference, which we are not interested in, "a" above)
val referenceToTarget = cl.findSingleReferenceNotOfType(owner)
// reference will now refer to referenceToTarget's type ("C" above)
reference.EType = referenceToTarget.EType
reference.dropContainment
val opposite = referenceToTarget.EOpposite
if (opposite !== null) { // "b2" above
makeBidirectional(reference, opposite)
}
removeElement(cl)
return reference
}
/**
* Given a non empty list of {@link EStructuralFeature}, which are known to
* appear in several classes as duplicates, extracts a new common superclass,
* with the duplicate feature,
* adds the extracted class as the superclass of the classes with the duplicate
* feature and removes the duplicate feature from such each class.
*
* The name of the extracted class is the name of the feature, with the first
* letter capitalized and the "Element" suffix (example, if the feature is
* "name" the extracted class will be called "NameElement").
* An additional number can be
* added as a suffix to avoid name clashes with existing classes.
*
* @param duplicates
*/
def extractSuperclass(List duplicates) {
val feature = duplicates.head;
val superClassName =
ensureEClassifierNameIsUnique(feature,
feature.name.toFirstUpper + "Element")
return extractSuperclass(superClassName, duplicates)
}
/**
* Given a non empty list of {@link EStructuralFeature}, which are known to
* appear in several classes as duplicates, extracts a new common superclass,
* with the given name, with the duplicate feature,
* adds the extracted class as the superclass of the classes with the duplicate
* feature and removes the duplicate feature from such each class.
*
* @param name
* @param duplicates
*/
def extractSuperclass(String name, List duplicates) {
val feature = duplicates.head;
return feature.EContainingClass.addNewEClassAsSibling(name) [
makeAbstract
duplicates
.map[EContainingClass]
.forEach[c | c.addESuperType(it)]
pullUpFeatures(duplicates)
]
}
/**
* Given a non empty list of {@link EStructuralFeature}, which are known to
* appear in several subclasses as duplicates, pulls them up in
* the given common superclass
* (and removes the duplicate feature from each subclass).
*
* @param dest
* @param duplicates
*/
def pullUpFeatures(EClass dest, Collection duplicates) : EStructuralFeature {
checkNoDifferences(
duplicates,
new EdeltaFeatureDifferenceFinder().ignoringContainingClass,
"The two features are not equal"
)
checkAllDirectSubclasses(dest, duplicates.map[EContainingClass].toList)
val pulledUp = duplicates.head.copyTo(dest)
removeAllElements(duplicates)
modelMigration[
mapFeaturesRule(duplicates, pulledUp)
]
return pulledUp
}
/**
* Given a feature and a non empty list of {@link EClass}, which are known to
* be direct subclasses of the containing class of the feature, pushes the feature down in
* the given common subclasses
* (and removes the feature from the original containing class).
*
* @param featureToPush
* @param subClasses
*/
def pushDownFeature(EStructuralFeature featureToPush, List subClasses) : Collection {
checkAllDirectSubclasses(featureToPush.EContainingClass, subClasses)
val pushedDownFeatures = new HashMap()
for (subClass : subClasses) {
var pushedDown = EcoreUtil.copy(featureToPush)
pushedDownFeatures.put(subClass, pushedDown)
subClass.getEStructuralFeatures().add(0, pushedDown)
}
removeElement(featureToPush)
modelMigration[
featureMigratorRule(
wasRelatedTo(featureToPush),
[feature, oldObj, newObj | // the object of the original model
// the result depends on the EClass of the original
// object being copied, but the map was built
// using evolved classes
pushedDownFeatures.get(newObj.eClass())
]
);
]
return pushedDownFeatures.values
}
/**
* Create a new target class with the given name that merges all
* the passed classes.
*
* The classes are expected to have the same single direct superclass
* and to define the same features.
*
* @param mergedClassName
* @param toMerge
*/
def mergeClasses(String mergedClassName, Collection toMerge) : EClass {
val superClass = getSingleDirectSuperclass(toMerge)
checkSameFeatures(toMerge)
checkNoBidirectionalReferences(toMerge.map[EStructuralFeatures].flatten.toList,
"Cannot merge in the presence of bidirectional references"
)
val merged = copyToAs(toMerge.head, superClass.EPackage, mergedClassName)
removeAllElements(toMerge)
modelMigration[
createInstanceRule(
wasRelatedToAtLeastOneOf(toMerge),
[origObj |
val origFeatures = origObj.eClass.EAllStructuralFeatures
createInstance(merged) [
newObj |
for (origFeature : origFeatures)
copyFrom(
newObj, merged.getEStructuralFeature(origFeature.name),
origObj, origFeature
)
]
]
)
]
return merged
}
/**
* Splits the passed class into several classes with the given names;
* all classes will be copies of the original class (which will be removed).
* Concerning model migration, it creates an object of the first split class.
*
* @param toSplit
* @param names
*/
def splitClass(EClass toSplit, Collection names) : Collection {
val containingPackage = toSplit.EPackage
return splitClass(toSplit, names,
[origObj |
createInstance(containingPackage.getEClass(names.head))
]
)
}
/**
* Splits the passed class into several classes with the given names;
* all classes will be copies of the original class (which will be removed).
* The objectMigrator is used when migrating the model objects of the original class:
* it is used to create an instance of the proper class and then all the features
* of the original object are copied into the created instance automatically
*
* @param toSplit
* @param names
* @param objectMigration
*/
def splitClass(EClass toSplit, Collection names, EObjectFunction objectMigrator)
: Collection {
return splitClass(toSplit, names)
[EdeltaModelMigrator it |
createInstanceRule(
wasRelatedTo(toSplit),
[origObj |
val origFeatures = origObj.eClass.EAllStructuralFeatures
val newObj = objectMigrator.apply(origObj)
val newClass = newObj.eClass
for (origFeature : origFeatures)
copyFrom(
newObj, newClass.getEStructuralFeature(origFeature.name),
origObj, origFeature
)
return newObj
]
)
]
}
/**
* Splits the passed class into several classes with the given names;
* all classes will be copies of the original class (which will be removed).
* The migratorConsumer is used when migrating the model and the developer ahs
* the full control on such a migration by using the migratorConsumer appropriately
* to define migration rules.
*
* @param toSplit
* @param names
* @param objectMigration
*/
def splitClass(EClass toSplit, Collection names, Consumer migratorConsumer)
: Collection {
val containingPackage = toSplit.EPackage
val split = names.map[ n |
copyToAs(toSplit, containingPackage, n)
].toList
removeElement(toSplit)
modelMigration(migratorConsumer)
return split
}
/**
* Ensures that the proposed classifier name is unique within the containing package of
* the passed context; if not, it appends an incremental index until the name
* is actually unique
*/
def ensureEClassifierNameIsUnique(ENamedElement context, String proposedName) {
var className = proposedName
var ePackage = context.EContainingPackage
val currentEClassifiersNames =
ePackage.EClassifiers.map[name].sort
var counter = 1
// make sure the new class is unique by name in the package
while (currentEClassifiersNames.contains(className)) {
className += (counter++)
}
return className
}
def fromTypeToFeatureName(EClassifier type) {
type.name.toFirstLower
}
/**
* Makes sure that this is not a containment reference,
* otherwise it shows an error message
* and throws an IllegalArgumentException.
*
* @param reference the reference that must not be a containment reference
* @param errorMessage the message to show in case the reference
* is a containment reference
*/
def checkNotContainment(EReference reference, String errorMessage) {
if (reference.containment) {
val message = errorMessage + ": " + getEObjectRepr(reference)
showError(reference, message)
throw new IllegalArgumentException(message)
}
}
/**
* Makes sure that this is not a multi element (upperBound > 1),
* otherwise it shows an error message
* and throws an IllegalArgumentException.
*
* @param element the element that must not be multi
* @param errorMessage the message to show in case the check fails
*/
def checkNotMany(ETypedElement element, String errorMessage) {
if (element.isMany) {
val message = errorMessage + ": " + getEObjectRepr(element)
showError(element, message)
throw new IllegalArgumentException(message)
}
}
/**
* Makes sure that the passed collection does not have EReferences
* with an EOpposite. Otherwise shows an error (using
* also the passed errorMessage) with the details of the bidirectional references and
* throws an IllegalArgumentException
*/
def checkNoBidirectionalReferences(Collection features,
String errorMessage
) {
val bidirectionalReferences = features
.filter(EReference)
.filter[EOpposite !== null]
if (!bidirectionalReferences.empty) {
val message = errorMessage + ":\n" +
bidirectionalReferences.map[" " + EObjectRepr].join("\n")
showError(bidirectionalReferences.head, message)
throw new IllegalArgumentException(message)
}
}
/**
* Makes sure that there are no differences in the passed features,
* using the specified differenceFinder, otherwise it shows an error message
* with the details of the differences and throws an IllegalArgumentException.
*
* @param features
* @param differenceFinder
* @param errorMessage
* @return true if there are no differences
*/
def checkNoDifferences(Iterable extends EStructuralFeature> features,
EdeltaFeatureDifferenceFinder differenceFinder,
String errorMessage) {
val feature = features.head
val different = features
.findFirst[feature !== it && !differenceFinder.equals(feature, it)]
if (different !== null) {
val message = errorMessage + ":\n" +
differenceFinder.differenceDetails
showError(different, message)
throw new IllegalArgumentException(message)
}
}
/**
* Makes sure that all the passed classes are direct subclasses of
* the passed class.
*
* @param superClass
* @param classes
*/
def checkAllDirectSubclasses(EClass superClass, Collection classes) {
val nonDirectSubclasses = classes
.filter[!ESuperTypes.contains(superClass)]
if (!nonDirectSubclasses.empty) {
nonDirectSubclasses.forEach[
showError(it,
"Not a direct subclass of: " +
getEObjectRepr(superClass)
)
]
throw new IllegalArgumentException("Not all direct subclasses")
}
}
/**
* Makes sure that the features have types that are subtypes of the
* specified feature, if not, shows
* error information and throws an IllegalArgumentException.
*
* @param feature
* @param features
*/
def checkCompliant(EStructuralFeature feature, Collection extends EStructuralFeature> features) {
val Predicate compliance = if (feature instanceof EReference) {
[other |
if (other instanceof EReference)
feature.EReferenceType.isSuperTypeOf(other.EReferenceType)
else
false // attribute's type is surely not compliant
]
} else {
[other | feature.EType === other.EType]
}
val nonCompliant = features.filter[!compliance.test(it)]
if (!nonCompliant.empty) {
val message =
"features not compliant with type " + getEObjectRepr(feature.EType) + ":\n" +
nonCompliant
.map[" " + getEObjectRepr(it) + ": " + getEObjectRepr(EType)].join("\n")
showError(feature, message)
throw new IllegalArgumentException(message)
}
}
def checkType(EStructuralFeature feature, EClassifier expectedType) : void {
// don't rely on equality because in the interpreter the types
// might be loaded from different Ecore models
if (feature.EType.fullyQualifiedName != expectedType.fullyQualifiedName) {
val message =
"expecting " + getEObjectRepr(expectedType) + " but was " +
getEObjectRepr(feature.EType)
showError(feature, message)
throw new IllegalArgumentException(message)
}
}
/**
* Makes sure the passed EClasses have no features, if not, shows
* error information and throws an IllegalArgumentException.
*
* @param classes
*/
def checkNoFeatures(Collection classes) {
val classesWithFeatures = classes.filter[c |
val features = c.EStructuralFeatures
val empty = features.empty
if (!empty) {
showError(c,
"Not an empty class: " + getEObjectRepr(c) + ":\n" +
features.map[" " + EObjectRepr].join("\n")
)
}
return !empty
].toList
if (!classesWithFeatures.empty)
throw new IllegalArgumentException("Classes not empty")
}
/**
* Makes sure the passed EClasses have the same features,
* independently from the order, if not, shows
* error information and throws an IllegalArgumentException.
*
* @param classes
*/
def checkSameFeatures(Collection classes) {
// the order is not important
val firstClass = classes.head
val features = firstClass.EStructuralFeatures.sortBy[name]
val otherClasses = classes.tail
for (otherClass : otherClasses) {
val otherFeatures = otherClass.EStructuralFeatures.sortBy[name]
val expectedSize = features.size
val actualSize = otherFeatures.size
if (expectedSize !== actualSize) {
showError(otherClass, "Different features size: expected " +
expectedSize + " but was " + actualSize + "\n " +
"in classes " + getEObjectRepr(firstClass) + ", " +
getEObjectRepr(otherClass)
)
throw new IllegalArgumentException("Features don't match in size")
} else {
val finder = new EdeltaFeatureDifferenceFinder().ignoringContainingClass
val otherIt = otherFeatures.iterator
for (f : features) {
val other = otherIt.next
if (!finder.equals(f, other)) {
val message = finder.differenceDetails
showError(otherClass, message)
throw new IllegalArgumentException(message)
}
}
}
}
}
/**
* Finds, among all references to the given EClass, the single containment reference in the
* EClass' package's resource set, performing validation (that is,
* no reference is found, or more than one containment reference is found) checks and in case
* show errors and throws an IllegalArgumentException.
*
* Note that several references to the class are allowed: the important thing
* is that exactly one is a containment reference.
*
* @param cl
*/
def findSingleContainmentAmongReferencesToThisClass(EClass cl) {
val references = findReferencesToThisClass(cl)
if (references.filter[containment].size > 1) {
val message = "The EClass is referred by more than one container:\n" +
references.map[" " + getEObjectRepr(it)].join("\n")
showError(cl, message)
throw new IllegalArgumentException(message)
}
return references.head
}
/**
* Finds all the EReferences to the given EClass in the
* EClass' package's resource set. If no such references are
* found it throws an IllegalArgumentException.
*
* @param cl
*/
def findReferencesToThisClass(EClass cl) {
val references = allReferencesToThisClass(cl)
if (references.isEmpty) {
val message = "The EClass is not referred: " + getEObjectRepr(cl)
showError(cl, message)
throw new IllegalArgumentException(message)
}
return references
}
/**
* Returns all the EReferences to the given EClass in the
* EClass' package's resource set.
*
* @param cl
*/
def allReferencesToThisClass(EClass cl) {
allUsagesOfThisClass(cl)
.map[EObject]
.filter(EReference)
}
/**
* Finds the single usage of this class and it must be a
* containment reference. Otherwise it show errors and throws an IllegalArgumentException.
*
* Note that several references to the class are allowed: the important thing
* is that exactly one is a containment reference.
*
* @param cl
*/
def findSingleContainmentReferenceToThisClass(EClass cl) {
return getAsContainmentReference
(findSingleUsageOfThisClass(cl))
}
/**
* Finds the single usage the given EClass in the
* EClass' package's resource set, performing validation (that is,
* no usage is found, or more than one) checks and in case
* show errors and throws an IllegalArgumentException.
*
* @param cl
*/
def findSingleUsageOfThisClass(EClass cl) {
val usages = allUsagesOfThisClass(cl)
if (usages.isEmpty) {
val message = "The EClass is not used: " + getEObjectRepr(cl)
showError(cl, message)
throw new IllegalArgumentException(message)
}
if (usages.size > 1) {
val message = "The EClass is used by more than one element:\n" +
usages.map[
" " + getEObjectRepr(EObject) + "\n" +
" " + getEObjectRepr(EStructuralFeature)
].join("\n")
showError(cl, message)
throw new IllegalArgumentException(message)
}
return usages.head.EObject
}
/**
* Makes sure that the passed EObject represent a containment EReference
* otherwise shows an error and throws an IllegalArgumentException
*
* @param o
* @return the containment EReference if it is a containment reference
*/
def getAsContainmentReference(EObject o) {
if (o instanceof EReference) {
if (!o.containment) {
val message = "Not a containment reference: " + getEObjectRepr(o)
showError(o, message)
throw new IllegalArgumentException(message)
}
return o
}
val message = "Not a reference: " + getEObjectRepr(o)
showError(o as ENamedElement, message)
throw new IllegalArgumentException(message)
}
/**
* Returns all the usages of the given EClass in the
* EClass' package's resource set.
*
* @param cl
*/
def allUsagesOfThisClass(EClass cl) {
EcoreUtil.UsageCrossReferencer
.find(cl, cl.packagesToInspect)
.filter[EObject instanceof ENamedElement] // skip EGenericType
.filter[!EStructuralFeature.derived] // skip derived features
.toList
}
/**
* Finds the single EReference, in the EReferences of the given EClass,
* with a type different from the given type, performing validation (that is,
* no reference is found, or more than one) checks and in case
* show errors and throws an IllegalArgumentException
*
* @param cl
* @param target
*/
def findSingleReferenceNotOfType(EClass cl, EClass type) {
val otherReferences = cl.EReferences
.filter[EType !== type]
.toList
if (otherReferences.empty) {
val message = "No references not of type " + getEObjectRepr(type)
showError(cl, message)
throw new IllegalArgumentException(message)
}
if (otherReferences.size > 1) {
val message = "Too many references not of type " + getEObjectRepr(type) +
":\n" + otherReferences.map[" " + getEObjectRepr(it)].join("\n")
showError(cl, message)
throw new IllegalArgumentException(message)
}
return otherReferences.head
}
/**
* Finds and returns the single containing class of the passed features.
* If there's more than one containing class throws an IllegalArgumentException.
*/
def findSingleOwner(Collection features) {
val owners = features.groupBy[EContainingClass]
if (owners.size > 1) {
val message = "Multiple containing classes:\n" +
owners.entrySet.map[
val reprForClass = getEObjectRepr(key)
showError(key,
"Extracted features must belong to the same class: " +
reprForClass)
return " " + reprForClass + ":\n" +
value.map[" " + getEObjectRepr(it)].join("\n")
].join("\n")
throw new IllegalArgumentException(message)
}
return features.head.EContainingClass
}
/**
* Checks that the passed subclasses have all exactly one superclass
* and that it is the same and returns that as a result. It also checks
* that such a common superclass has no further subclasses.
*
* In case of failure, besides reporting errors, it throws an
* IllegalArgumentException.
*/
def getSingleDirectSuperclass(Collection subclasses) : EClass {
val invalid = subclasses.filter[
ESuperTypes.size != 1
]
if (!invalid.empty) {
invalid.forEach[
val superclasses = ESuperTypes
showError(it,
"Expected one superclass: " + EObjectRepr + " instead of:\n" +
if (superclasses.empty)
" empty"
else
superclasses.map[" " + EObjectRepr].join("\n")
)
]
throw new IllegalArgumentException("Wrong superclasses")
}
// now all subclasses are known to have exactly one superclass
val superclass = subclasses.head.ESuperTypes.head
val differences = subclasses
.filter[ESuperTypes.head !== superclass]
if (!differences.empty) {
differences.forEach[
val message = "Wrong superclass of " + EObjectRepr + ":\n"+
" Expected: " + getEObjectRepr(superclass) + "\n" +
" Actual : " + getEObjectRepr(ESuperTypes.head)
showError(it, message)
]
throw new IllegalArgumentException("Wrong superclasses")
}
val additionalSubclasses = directSubclasses(superclass).toSet
additionalSubclasses.removeAll(subclasses.toSet)
if (!additionalSubclasses.empty) {
val message = "The class " + getEObjectRepr(superclass) + " has additional subclasses:\n" +
additionalSubclasses.map[" " + EObjectRepr].join("\n")
showError(superclass, message)
throw new IllegalArgumentException(message)
}
return superclass
}
def directSubclasses(EClass cl) {
allUsagesOfThisClass(cl)
.filter[EStructuralFeature == ecoreref(eSuperTypes)]
.map[EObject as EClass]
}