![JAR search and dependency download from the Maven repository](/logo.png)
edelta.refactorings.lib.EdeltaBadSmellsFinder.edelta Maven / Gradle / Ivy
import edelta.refactorings.lib.helper.EdeltaFeatureEqualityHelper
import java.util.Collection
import java.util.List
import java.util.Map
import java.util.function.BiPredicate
import org.eclipse.emf.ecore.EClass
import org.eclipse.emf.ecore.EClassifier
import org.eclipse.emf.ecore.EPackage
import org.eclipse.emf.ecore.EStructuralFeature
import org.eclipse.emf.ecore.util.EcoreUtil
import org.eclipse.emf.ecore.util.EcoreUtil.CrossReferencer
import org.eclipse.emf.ecore.util.EcoreUtil.UsageCrossReferencer
package edelta.refactorings.lib
metamodel "ecore"
/**
* Finds all the features that are structurally equal
* in the given {@link EPackage}.
*
* Note that this takes into consideration the name and type,
* but also other properties like lowerBound, unique, etc.
*
* For example, given these EClasses
*
*
* C1 {
* A1 : EString
* }
*
* C2 {
* A1 : EString
* }
*
*
* It returns the map with this entry
*
*
* (A1 : EString) -> [ C1:A1, C2:A1 ]
*
*
* @param ePackage
*/
def findDuplicatedFeatures(EPackage ePackage) {
return findDuplicatedFeaturesCustom(
ePackage,
[existing, current|
new EdeltaFeatureEqualityHelper().equals(existing, current)
]
)
}
/**
* Allows you to specify the lambda checking for equality of features.
*
* @param ePackage
* @param matcher
*/
def findDuplicatedFeaturesCustom(EPackage ePackage, BiPredicate matcher) {
val allFeatures = ePackage.allEStructuralFeatures
val result = findDuplicatedFeaturesInCollection(allFeatures, matcher)
result.entrySet.forEach[
logInfo[
"Duplicate features: " +
value.map[getEObjectRepr(it)].join(", ")
]
]
return result
}
/**
* Finds all the features that are structurally equal
* in the given collection.
*
* Note that this takes into consideration the name and type,
* but also other properties like lowerBound, unique, etc.
*
* For example, given these EClasses
*
*
* C1 {
* A1 : EString
* }
*
* C2 {
* A1 : EString
* }
*
*
* And the collection of features [ C1:A1, C2:A1 ]
* it returns the map with this entry
*
*
* (A1 : EString) -> [ C1:A1, C2:A1 ]
*
*
* It allows you to specify the lambda checking for equality of features.
*
* @param features
* @param matcher
*/
def findDuplicatedFeaturesInCollection(Collection features, BiPredicate matcher) {
val map = >newLinkedHashMap
for (f : features) {
val existing = map.entrySet.findFirst[matcher.test(it.key, f)]
if (existing !== null) {
existing.value += f
} else {
map.put(f, newArrayList(f))
}
}
// only entries with a mapped list of size > 1 are duplicates
return map.filter[key, values| values.size > 1]
}
/**
* If a class has more than one direct subclass, it finds duplicate features in all
* of its direct subclasses.
*
* Returns a map where the key is the class with more than one direct subclass
* with duplicate features; the value is another map as returned by
* {@link #findDuplicatedFeaturesInCollection(Collection, BiPredicate)}
*/
def findDuplicatedFeaturesInSubclasses(EPackage ePackage) {
val map = newLinkedHashMap
for (c : ePackage.allEClasses) {
val directSubclasses = c.directSubclasses
val numOfSubclasses = directSubclasses.size
if (numOfSubclasses > 1) {
val candidates =
findDuplicatedFeaturesInCollection(
directSubclasses.map[EStructuralFeatures].flatten.toList,
[existing, current|
new EdeltaFeatureEqualityHelper().equals(existing, current)
]
)
// only entries with a mapped list of size = num of subclasses are duplicates
val duplicates = candidates
.filter[key, values | values.size == numOfSubclasses]
if (!duplicates.empty) {
map.put(c, duplicates)
duplicates.entrySet.forEach[
logInfo[
"In subclasses of " + getEObjectRepr(c) + ", duplicate features: " +
value.map[getEObjectRepr(it)].join(", ")
]
]
}
}
}
return map
}
/**
* Finds all the features corresponding to a redundant container,
* that is, a missed opposite reference to the container.
*
* The result consists of an iterable of pairs where the key
* is the reference corresponding to the redundant container
* and the value is the reference that should correspond to the
* opposite reference.
*
* For example, if "Bank" has a containment feature "clients",
* and the "Client" has a non-containment feature "bank", which
* is not set as the opposite of "clients", then the detected
* redundant container will be the pair "Client:bank" -> "Bank:clients".
*
* This form should make the corresponding refactoring trivial to
* implement, since all the information are in the pair.
*/
def findRedundantContainers(EPackage ePackage) {
ePackage.allEClasses
.map[findRedundantContainers]
.flatten
}
/**
* see {@link #findRedundantContainers(EPackage)}
*/
def findRedundantContainers(EClass cl) {
val redundantContainers = newArrayList
val containmentReferences = cl.EReferences
.filter[containment]
for (containmentReference : containmentReferences) {
val redundant = containmentReference.EReferenceType.EReferences
.filter[
!containment &&
required &&
EOpposite === null &&
EReferenceType === cl
]
.head
if (redundant !== null){
redundantContainers += redundant -> containmentReference
logInfo[
"Redundant container: "
+ getEObjectRepr(containmentReference)
+ " -> "
+ getEObjectRepr(redundant)
]
}}
return redundantContainers
}
/**
* see {@link #isDeadClassifier(EClassifier)}
*/
def findDeadClassifiers(EPackage ePackage) {
ePackage.EClassifiers
.filter[isDeadClassifier]
.toList
}
/**
* Whether {@link #doesNotReferToClasses(EClassifier)} and
* {@link #isNotReferredByClassifiers(EClassifier)}
*/
def isDeadClassifier(EClassifier cl) {
val result = (cl.doesNotReferToClasses && cl.isNotReferredByClassifiers)
if (result)
logInfo["Dead classifier: " + getEObjectRepr(cl)]
return result;
}
/**
* Whether the passed EClassifier does not refer to any EClass
*/
def doesNotReferToClasses(EClassifier c) {
CrossReferencer.find(newArrayList(c))
.keySet
.filter(EClass)
.empty
}
/**
* Whether the passed EClassifier is not referred by EClassifiers.
*/
def isNotReferredByClassifiers(EClassifier cl) {
UsageCrossReferencer.find(cl, cl.packagesToInspect)
.empty
}
/**
* Returns a map where the key is an EClass (superclass)
* and the associated value is a list of subclasses that are
* considered matching the "classification by hierarchy" bad smell.
*/
def findClassificationByHierarchy(EPackage ePackage)
: Map> {
val classification= ePackage.allEClasses
.filter[
ESuperTypes.size == 1 &&
EStructuralFeatures.empty &&
isNotReferredByClassifiers
]
.groupBy[ESuperTypes.head]
.filter[base, subclasses|subclasses.size > 1]
// only if there are at least 2 subclasses
classification.entrySet.forEach[
logInfo["Classification by hierarchy: "
+ getEObjectRepr(key) + " - "
+ "subclasses["
+ value.map[getEObjectRepr(it)].join(",")
+ "]"
]
]
return classification
}
/**
* Finds base classes that should be set as abstract,
* since they have subclasses.
*/
def findConcreteAbstractMetaclasses(EPackage ePackage) {
val classes= ePackage.allEClasses
.filter[
cl |
!cl.abstract &&
cl.hasSubclasses
]
classes.forEach[
logInfo["Concrete abstract class: " + getEObjectRepr(it)]
]
return classes;
}
def hasSubclasses(EClass cl) {
!cl.directSubclasses.empty
}
def directSubclasses(EClass cl) {
EcoreUtil.UsageCrossReferencer
.find(cl, cl.packagesToInspect)
.filter[EStructuralFeature === ecoreref(eSuperTypes)]
.map[EObject as EClass]
}
/**
* Finds abstract classes that should be concrete,
* since they have no subclasses.
*/
def findAbstractConcreteMetaclasses(EPackage ePackage) {
val classes= ePackage.allEClasses
.filter[
cl |
cl.abstract &&
!cl.hasSubclasses
]
classes.forEach[
logInfo["Abstract concrete class: " + getEObjectRepr(it)]
]
return classes;
}
/**
* Finds classes that are abstract though they have only concrete superclasses.
*/
def findAbstractSubclassesOfConcreteSuperclasses(EPackage ePackage) {
val classes= ePackage.allEClasses
.filter[
cl |
cl.abstract &&
!cl.ESuperTypes.empty &&
cl.ESuperTypes.forall[!abstract]
]
classes.forEach[
logInfo["Abstract class with concrete superclasses: " + getEObjectRepr(it)]
]
return classes;
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy