package gov.nasa.jpl.mbee.mdk.systems_reasoner.validation;
import com.nomagic.uml2.ext.jmi.helpers.ModelHelper;
import com.nomagic.uml2.ext.jmi.helpers.StereotypesHelper;
import com.nomagic.uml2.ext.magicdraw.actions.mdbasicactions.CallBehaviorAction;
import com.nomagic.uml2.ext.magicdraw.classes.mddependencies.Dependency;
import com.nomagic.uml2.ext.magicdraw.classes.mdkernel.*;
import com.nomagic.uml2.ext.magicdraw.commonbehaviors.mdbasicbehaviors.Behavior;
import com.nomagic.uml2.ext.magicdraw.mdprofiles.Stereotype;
import gov.nasa.jpl.mbee.mdk.systems_reasoner.actions.*;
import gov.nasa.jpl.mbee.mdk.validation.IndeterminateProgressMonitorProxy;
import gov.nasa.jpl.mbee.mdk.validation.ValidationRule;
import gov.nasa.jpl.mbee.mdk.validation.ValidationRuleViolation;
import gov.nasa.jpl.mbee.mdk.validation.ValidationSuite;
import gov.nasa.jpl.mbee.mdk.validation.ViolationSeverity;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
public class SRValidationSuite extends ValidationSuite implements Runnable {
private static final String NAME = "SR Validate";
private List<Element> elements;
private static final ValidationRule generalMissingRule = new ValidationRule("Missing General", "General is missing in generalization", ViolationSeverity.ERROR),
generalNotClassRule = new ValidationRule("General Not Class", "General is not of type class", ViolationSeverity.ERROR),
attributeMissingRule = new ValidationRule("Missing Owned Redefinable Element", "Owned RedefinableElement is missing", ViolationSeverity.ERROR),
aspectMissingRule = new ValidationRule("Missing Defined Aspect", "An aspect is defined but not realized", ViolationSeverity.ERROR),
nameRule = new ValidationRule("Naming Inconsistency", "Names are inconsistent", ViolationSeverity.WARNING),
subsetsRule = new ValidationRule("Redefined Property Subset Missing.", "Subset missing.", ViolationSeverity.WARNING),
attributeTypeRule = new ValidationRule("Attribute Type Inconsistency", "Attribute types are inconsistent", ViolationSeverity.WARNING),
generalSpecificNameRule = new ValidationRule("General Specific Name Inconsistency", "General and specific names are inconsistent", ViolationSeverity.INFO),
// orphanAttributeRule = new ValidationRule("Potential Orphan", "First degree attribute is never redefined", ViolationSeverity.WARNING);
instanceClassifierExistenceRule = new ValidationRule("Instance Classifier Unspecified", "Instance classifier is not specified", ViolationSeverity.ERROR),
missingSlotsRule = new ValidationRule("Missing Slot(s) Detected", "Missing slot(s) detected", ViolationSeverity.ERROR),
associationInheritanceRule = new ValidationRule("Association inheritance missing.", "The association of the specialized element does not inherit from its general counterpart.", ViolationSeverity.ERROR);
{
this.addValidationRule(generalMissingRule);
this.addValidationRule(generalNotClassRule);
this.addValidationRule(attributeMissingRule);
this.addValidationRule(aspectMissingRule);
this.addValidationRule(nameRule);
this.addValidationRule(attributeTypeRule);
// this.addValidationRule(orphanAttributeRule);
this.addValidationRule(generalSpecificNameRule);
this.addValidationRule(instanceClassifierExistenceRule);
this.addValidationRule(missingSlotsRule);
this.addValidationRule(associationInheritanceRule);
this.addValidationRule(subsetsRule);
}
public SRValidationSuite(final List<Element> elements) {
super(NAME);
this.elements = elements;
}
@Override
public void run() {
for (final ValidationRule vr : this.getValidationRules()) {
vr.getViolations().clear();
}
final ListIterator<Element> iterator = elements.listIterator();
while (iterator.hasNext()) {
final Element element = iterator.next();
if (element instanceof Classifier) {
final Classifier classifier = (Classifier) element;
// traverse the hierarchy down
for (final Generalization generalization : classifier.get_generalizationOfGeneral()) {
if (!elements.contains(generalization.getSpecific())) {
iterator.add(generalization.getSpecific());
iterator.previous();
}
}
for (final InstanceSpecification instance : classifier.get_instanceSpecificationOfClassifier()) {
if (!elements.contains(instance)) {
iterator.add(instance);
iterator.previous();
}
}
for (Property property : classifier.getAttribute()) {
if (!elements.contains(property.getType())) {
iterator.add(property.getType());
iterator.previous();
}
}
checkForAspects(classifier, classifier);
for (final Classifier general : classifier.getGeneral()) {
// Inheritance on Associations Rule
checkAssociationsForInheritance(classifier, general);
checkForAspects(classifier, general);
}
for (final NamedElement ne : classifier.getInheritedMember()) { // Exclude Classifiers for now -> Should Aspect Blocks be Redefined?
if (ne instanceof RedefinableElement && !((RedefinableElement) ne).isLeaf() && !(ne instanceof Classifier)) {
final RedefinableElement redefEl = (RedefinableElement) ne;
RedefinableElement redefingEl = null;
for (Element p : classifier.getOwnedElement()) {
if (p instanceof RedefinableElement) {
if (doesEventuallyRedefine((RedefinableElement) p, redefEl)) {
// if (p instanceof RedefinableElement && ((RedefinableElement) p).getRedefinedElement().contains(redefinableElement)) {
redefingEl = (RedefinableElement) p;
if(redefingEl instanceof Property && redefEl instanceof Property) {
if(!((Property) redefEl).getSubsettedProperty().isEmpty()) {
final ValidationRuleViolation v = new ValidationRuleViolation(classifier, subsetsRule.getDescription() + ": " + redefEl.getQualifiedName());
v.addAction(new SubsetRedefinedProperty((Property) redefEl, (Property) redefingEl));
attributeTypeRule.addViolation(v);
}
break;
}
}
}
}
if (redefingEl == null) {
boolean redefinedInContext = false;
for (final NamedElement ne2 : classifier.getInheritedMember()) {
if (ne2 instanceof RedefinableElement && ne2 instanceof RedefinableElement && doesEventuallyRedefine((RedefinableElement) ne2, redefEl)) {
redefinedInContext = true;
break;
}
}
if (!redefinedInContext) {
// Composite tag added, so user can make an educated decision on whether to specialize or not. Non-composite properties are typically not specialized in the context of the Block Specific Type,
// but there could be a number of valid reasons to do so.
final ValidationRuleViolation v = new ValidationRuleViolation(classifier, (ne instanceof Property && ((Property) ne).isComposite() ? "[COMPOSITE] " : "") +
(redefEl instanceof TypedElement && ((TypedElement) redefEl).getType() != null ? "[TYPED] " : "") + attributeMissingRule.getDescription() + ": " + redefEl.getQualifiedName());
for (final Property p : classifier.getAttribute()) {
if (p.getName().equals(redefEl.getName()) && !p.hasRedefinedElement()) {
v.addAction(new SetRedefinitionAction(p, redefEl, "Redefine by Name Collision"));
}
}
if (ne instanceof RedefinableElement) {
// why was this a requirement? One should be able to redefine any property regardless of aggregation, by my understanding.
//if (!((Property) ne).isComposite()) {
v.addAction(new SetOrCreateRedefinableElementAction(classifier, redefEl, false));
if (redefEl instanceof TypedElement) { // && ((TypedElement) redefEl).getType() != null
// intentionally showing this option even if the type isn't specializable so the user doesn't have to go through
// grouping them separately to validate. It will just ignore and log if a type isn't specializable.
v.addAction(new SetOrCreateRedefinableElementAction(classifier, redefEl, true, "Redefine Attribute & Specialize Types Recursively & Individually", true));
}
}
attributeMissingRule.addViolation(v);
}
}
else {
if ((redefingEl.getName() == null && redefEl.getName() != null) || (redefingEl.getName() != null && !redefingEl.getName().equals(redefEl.getName()))) {
final ValidationRuleViolation v = new ValidationRuleViolation(redefingEl, nameRule.getDescription() + ": [GENERAL] " + redefEl.getName() + " - [SPECIFIC] " + redefingEl.getName());
v.addAction(new RenameElementAction(redefEl, redefingEl, "Update Specific"));
v.addAction(new RenameElementAction(redefingEl, redefEl, "Update General"));
nameRule.addViolation(v);
}
if (redefingEl instanceof TypedElement && redefEl instanceof TypedElement) {
final TypedElement redefingTypdEl = (TypedElement) redefingEl;
final TypedElement redefableTypdEl = (TypedElement) redefEl;
if ((redefingTypdEl.getType() == null && redefableTypdEl.getType() != null) || (redefingTypdEl.getType() != null && redefingTypdEl.getType() instanceof Classifier && redefableTypdEl.getType() instanceof Classifier
&& !doesEventuallyGeneralizeTo((Classifier) redefingTypdEl.getType(), (Classifier) redefableTypdEl.getType()))) {
if (redefingTypdEl.getType() instanceof Classifier && redefableTypdEl.getType() instanceof Classifier && ((Classifier) redefingTypdEl.getType()).getGeneral().contains(redefableTypdEl.getType())) {
if(!elements.contains(redefableTypdEl.getType())) {
iterator.add(redefingTypdEl.getType());
iterator.previous();
}
}
else {
final ValidationRuleViolation v = new ValidationRuleViolation(redefingTypdEl,
attributeTypeRule.getDescription() + ": [GENERAL] " + (redefableTypdEl.getType() != null ? redefableTypdEl.getType().getQualifiedName() : "null") + " - [SPECIFIC] "
+ (redefingTypdEl.getType() != null ? redefingTypdEl.getType().getQualifiedName() : "null"));
v.addAction(new RetypeElementAction(redefableTypdEl, redefingTypdEl, "Update Specific"));
v.addAction(new RetypeElementAction(redefingTypdEl, redefableTypdEl, "Update General"));
attributeTypeRule.addViolation(v);
}
}
}
}
}
}
}
else if (element instanceof InstanceSpecification) {
final InstanceSpecification instance = (InstanceSpecification) element;
for (final Slot slot : instance.getSlot()) {
for (final ValueSpecification vs : slot.getValue()) {
final InstanceSpecification i;
if (vs instanceof InstanceValue && (i = ((InstanceValue) vs).getInstance()) != null && !elements.contains(i)) {
iterator.add(i);
iterator.previous();
}
}
}
if (!instance.hasClassifier()) {
final ValidationRuleViolation v = new ValidationRuleViolation(instance, instanceClassifierExistenceRule.getDescription() + ": " + instance.getQualifiedName());
v.addAction(new OpenSpecificationAction(instance));
v.addAction(new SelectInContainmentTreeAction(instance));
instanceClassifierExistenceRule.addViolation(v);
continue;
}
// boolean needsReslotting = false;
final List<Property> missingProperties = new ArrayList<>();
for (final Classifier classifier : instance.getClassifier()) {
for (final Property property : CreateSlotsAction.collectSlottableProperties(classifier)) {
boolean isDefined = false;
for (final Slot slot : instance.getSlot()) {
if (slot.getDefiningFeature().equals(property)) {
isDefined = true;
break;
}
}
if (!isDefined) {
missingProperties.add(property);
}
}
}
if (!missingProperties.isEmpty()) {
String suffix = "";
if (instance.hasSlot()) {
suffix += ": ";
for (int i = 0; i < missingProperties.size(); i++) {
final Property property = missingProperties.get(i);
suffix += property.getName() != null && !property.getName().isEmpty() ? property.getName() : "<>";
if (i != missingProperties.size() - 1) {
suffix += ", ";
}
}
}
final ValidationRuleViolation v = new ValidationRuleViolation(instance, (!instance.hasSlot() ? missingSlotsRule.getDescription().replaceFirst("Missing", "No") : missingSlotsRule.getDescription()) + suffix);
v.addAction(IndeterminateProgressMonitorProxy.doubleWrap(new CreateSlotsAction(instance, false, false, false, "Create Missing Slots"), "Systems Reasoner"));
v.addAction(IndeterminateProgressMonitorProxy.doubleWrap(new CreateSlotsAction(instance, false, false, true, "Recreate Slots"), "Systems Reasoner"));
v.addAction(IndeterminateProgressMonitorProxy.doubleWrap(new CreateSlotsAction(instance, false, true, true, "Delete Child Instances & Recreate Slots"), "Systems Reasoner"));
v.addAction(IndeterminateProgressMonitorProxy.doubleWrap(new CreateSlotsAction(instance, true, false, false, "[R] Create Missing Slots"), "Systems Reasoner"));
v.addAction(IndeterminateProgressMonitorProxy.doubleWrap(new CreateSlotsAction(instance, true, false, true, "[R] Recreate Slots"), "Systems Reasoner"));
v.addAction(IndeterminateProgressMonitorProxy.doubleWrap(new CreateSlotsAction(instance, true, true, true, "[R] Delete Child Instances & Recreate Slots"), "Systems Reasoner"));
missingSlotsRule.addViolation(v);
}
}
}
}
private void checkAssociationsForInheritance(Classifier classifier, Classifier general) {
assocRule:
for (Element child : classifier.getOwnedElement()) {
if (child instanceof Property) {
Type partType = ((Property) child).getType();
for (Element superChild : general.getOwnedElement()) {
if (superChild instanceof Property) {
Type superPartType = ((Property) superChild).getType();
final ValidationRuleViolation v = new ValidationRuleViolation(classifier, associationInheritanceRule.getDescription() + ": [GENERAL] " + general.getName() + " - [SPECIFIC] " + classifier.getName());
if (partType != null) {
if (partType.equals(superPartType)) {
if (hasAnAssociation(superChild)) {
if (hasInheritanceFromTo(((Property) child).getAssociation(), ((Property) superChild).getAssociation())) {
break assocRule;
}
else {
v.addAction(new AddInheritanceToAssociationAction(((Property) child).getAssociation(), ((Property) superChild).getAssociation()));
associationInheritanceRule.addViolation(v);
}
}
}
else if (partType instanceof Classifier) {
if (((Classifier) partType).getGeneral().contains(superPartType)) {
if (hasInheritanceFromTo(((Property) child).getAssociation(), ((Property) superChild).getAssociation())) {
break assocRule;
}
else {
v.addAction(new AddInheritanceToAssociationAction(((Property) child).getAssociation(), ((Property) superChild).getAssociation()));
associationInheritanceRule.addViolation(v);
}
}
}
}
}
}
}
}
}
private void checkForAspects(final Classifier classifier, final Classifier general) {
/**
* Check for aspect structures:
*/
for (Dependency d : general.getClientDependency()) {
boolean aspectFound = false;
Classifier aspect = null;
Stereotype s = StereotypesHelper.getAppliedStereotypeByString(d, "aspect");
if (s != null) {
for (Element el : d.getTarget()) {
if (el instanceof Classifier) {
aspect = (Classifier) el;
for (Element ownedElement : classifier.getOwnedElement()) {
if (ownedElement instanceof Property) {
Type type = ((TypedElement) ownedElement).getType();
if (type instanceof Classifier) {
if ((hasInheritanceFromTo((Classifier) type, aspect))) {
aspectFound = true;
}
}
}
else if (ownedElement instanceof CallBehaviorAction) {
Behavior b = ((CallBehaviorAction) ownedElement).getBehavior();
if (b.getGeneral().contains(el)) {
aspectFound = true;
}
}
}
}
}
if (!aspectFound) {
if (aspect != null) {
final ValidationRuleViolation v = new ValidationRuleViolation(classifier, aspectMissingRule.getDescription() + ": [CLASS WITH ASPECT] " + classifier.getName() + " - [ASPECT] " + aspect.getName());
v.addAction(new AspectRemedyAction(classifier, aspect));
aspectMissingRule.addViolation(v);
}
}
}
}
}
private boolean hasAnAssociation(Element superChild) {
return ((Property) superChild).getAssociation() != null;
}
private boolean hasInheritanceFromTo(Classifier classifier, Classifier general) {
if (classifier != null) {
return ModelHelper.getGeneralClassifiersRecursivelly(classifier).contains(general);
}
else {
return false;
}
}
public static boolean doesEventuallyRedefine(final RedefinableElement source, final RedefinableElement target) {
if (source.getRedefinedElement().contains(target)) {
return true;
}
for (final RedefinableElement p : source.getRedefinedElement()) {
if (doesEventuallyRedefine(p, target)) {
return true;
}
}
return false;
}
public static boolean doesEventuallyGeneralizeTo(final Classifier source, final Classifier target) {
if (source.getGeneral().contains(target)) {
return true;
}
if (source.equals(target)) {
return true;
}
for (final Classifier classifier : source.getGeneral()) {
if (doesEventuallyGeneralizeTo(classifier, target)) {
return true;
}
}
return false;
}
}