/** * Copyright (c) 2012-2016 Marsha Chechik, Alessio Di Sandro, Michalis Famelis, * Rick Salay. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Alessio Di Sandro - Implementation. */ package edu.toronto.cs.se.mmint.mid.reasoning; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import org.eclipse.core.runtime.IStatus; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EObject; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import edu.toronto.cs.se.mmint.MMINT; import edu.toronto.cs.se.mmint.MMINTConstants; import edu.toronto.cs.se.mmint.MMINTException; import edu.toronto.cs.se.mmint.MIDTypeHierarchy; import edu.toronto.cs.se.mmint.MIDTypeRegistry; import edu.toronto.cs.se.mmint.mid.EMFInfo; import edu.toronto.cs.se.mmint.mid.ExtendibleElement; import edu.toronto.cs.se.mmint.mid.ExtendibleElementConstraint; import edu.toronto.cs.se.mmint.mid.MID; import edu.toronto.cs.se.mmint.mid.MIDLevel; import edu.toronto.cs.se.mmint.mid.Model; import edu.toronto.cs.se.mmint.mid.ModelElement; import edu.toronto.cs.se.mmint.mid.ModelEndpoint; import edu.toronto.cs.se.mmint.mid.operator.Operator; import edu.toronto.cs.se.mmint.mid.operator.OperatorConstraint; import edu.toronto.cs.se.mmint.mid.relationship.BinaryMappingReference; import edu.toronto.cs.se.mmint.mid.relationship.BinaryModelRel; import edu.toronto.cs.se.mmint.mid.relationship.Mapping; import edu.toronto.cs.se.mmint.mid.relationship.MappingReference; import edu.toronto.cs.se.mmint.mid.relationship.ModelElementEndpoint; import edu.toronto.cs.se.mmint.mid.relationship.ModelElementEndpointReference; import edu.toronto.cs.se.mmint.mid.relationship.ModelElementReference; import edu.toronto.cs.se.mmint.mid.relationship.ModelEndpointReference; import edu.toronto.cs.se.mmint.mid.relationship.ModelRel; import edu.toronto.cs.se.mmint.mid.utils.MIDRegistry; import edu.toronto.cs.se.mmint.mid.utils.PrimitiveEObjectWrapper; /** * The constraint checker for multimodels. * * @author Alessio Di Sandro * */ public class MIDConstraintChecker { public static boolean isAllowedModelType(ModelRel modelRelType) { MID typeMID = modelRelType.getMIDContainer(); List<ModelRel> modelRelSubtypes = MIDTypeHierarchy.getSubtypes(modelRelType, typeMID); return (modelRelSubtypes.isEmpty()) ? true : false; } public static boolean isAllowedModelTypeEndpoint(BinaryModelRel modelRelType, Model newSrcModelType, Model newTgtModelType) { if (newSrcModelType == null && newTgtModelType == null) { // model type not added yet return true; } ModelRel modelRelTypeSuper = (ModelRel) modelRelType.getSupertype(); // checks that the new model type is the same of the super model rel type or is overriding it if (!MIDTypeHierarchy.isRootType(modelRelTypeSuper)) { MID typeMID = modelRelType.getMIDContainer(); if (newSrcModelType != null) { String srcUri = modelRelTypeSuper.getModelEndpoints().get(0).getTargetUri(); String newSrcUri = newSrcModelType.getUri(); if (!newSrcUri.equals(srcUri) && !MIDTypeHierarchy.isSubtypeOf(newSrcUri, srcUri, typeMID)) { return false; } } if (newTgtModelType != null) { String tgtUri = modelRelTypeSuper.getModelEndpoints().get(1).getTargetUri(); String newTgtUri = newTgtModelType.getUri(); if (!newTgtUri.equals(tgtUri) && !MIDTypeHierarchy.isSubtypeOf(newTgtUri, tgtUri, typeMID)) { return false; } } } return true; } public static boolean isAllowedModelElementTypeEndpointReference(BinaryMappingReference mappingTypeRef, ModelElementReference newSrcModelElemTypeRef, ModelElementReference newTgtModelElemTypeRef) { if (newSrcModelElemTypeRef == null && newTgtModelElemTypeRef == null) { // model element type reference not added yet return true; } Mapping mappingTypeSuper = mappingTypeRef.getObject().getSupertype(); // checks that the new model element type is the same of the super link type or is overriding it if (!MIDTypeHierarchy.isRootType(mappingTypeSuper)) { MID typeMID = mappingTypeRef.getMIDContainer(); if (newSrcModelElemTypeRef != null) { String srcUri = mappingTypeSuper.getModelElemEndpoints().get(0).getTargetUri(); String newSrcUri = newSrcModelElemTypeRef.getUri(); if (!newSrcUri.equals(srcUri) && !MIDTypeHierarchy.isSubtypeOf(newSrcUri, srcUri, typeMID)) { return false; } } if (newTgtModelElemTypeRef != null) { String tgtUri = mappingTypeSuper.getModelElemEndpoints().get(1).getTargetUri(); String newTgtUri = newTgtModelElemTypeRef.getUri(); if (!newTgtUri.equals(tgtUri) && !MIDTypeHierarchy.isSubtypeOf(newTgtUri, tgtUri, typeMID)) { return false; } } } return true; } public static List<String> getAllowedModelRelTypes(Model targetSrcModel, Model targetTgtModel) { List<String> modelRelTypeUris = new ArrayList<String>(); for (ModelRel modelRelType : MIDTypeRegistry.getModelRelTypes()) { boolean isAllowed = true, isAllowedSrc = false, isAllowedTgt = false; HashMap<String, Integer> cardinalityTable = new HashMap<String, Integer>(); //TODO MMINT[INTROSPECTION] consider direction for binary? if (targetSrcModel != null) { for (ModelEndpointReference modelTypeEndpointRef : modelRelType.getModelEndpointRefs()) { if (MIDConstraintChecker.isAllowedModelEndpoint(modelTypeEndpointRef, targetSrcModel, cardinalityTable)) { MIDRegistry.addEndpointCardinality(modelTypeEndpointRef.getUri(), cardinalityTable); isAllowedSrc = true; break; } } isAllowed = isAllowed && isAllowedSrc; } if (targetTgtModel != null) { for (ModelEndpointReference modelTypeEndpointRef : modelRelType.getModelEndpointRefs()) { if (MIDConstraintChecker.isAllowedModelEndpoint(modelTypeEndpointRef, targetTgtModel, cardinalityTable)) { MIDRegistry.addEndpointCardinality(modelTypeEndpointRef.getUri(), cardinalityTable); isAllowedTgt = true; break; } } isAllowed = isAllowed && isAllowedTgt; } if (isAllowed) { modelRelTypeUris.add(modelRelType.getUri()); } } // check for overrides for (String modelRelTypeUri : modelRelTypeUris) { //TODO MMINT[OVERRIDE] if one model rel type points to another one in this list through its override pointer, then delete it } return modelRelTypeUris; } public static List<String> getAllowedMappingTypeReferences(ModelRel modelRelType, ModelElementReference targetSrcModelElemRef, ModelElementReference targetTgtModelElemRef) { List<String> mappingTypeUris = new ArrayList<String>(); for (MappingReference mappingTypeRef : modelRelType.getMappingRefs()) { boolean isAllowed = true, isAllowedSrc = false, isAllowedTgt = false; HashMap<String, Integer> cardinalityTable = new HashMap<String, Integer>(); //TODO MMINT[INTROSPECTION] consider direction for binary? if (targetSrcModelElemRef != null) { for (ModelElementEndpointReference modelElemTypeEndpointRef : mappingTypeRef.getObject().getModelElemEndpointRefs()) { if (MIDConstraintChecker.isAllowedModelElementEndpointReference(modelElemTypeEndpointRef.getObject(), targetSrcModelElemRef, cardinalityTable)) { MIDRegistry.addEndpointCardinality(modelElemTypeEndpointRef.getUri(), cardinalityTable); isAllowedSrc = true; break; } } isAllowed = isAllowed && isAllowedSrc; } if (targetTgtModelElemRef != null) { for (ModelElementEndpointReference modelElemTypeEndpointRef : mappingTypeRef.getObject().getModelElemEndpointRefs()) { if (MIDConstraintChecker.isAllowedModelElementEndpointReference(modelElemTypeEndpointRef.getObject(), targetTgtModelElemRef, cardinalityTable)) { MIDRegistry.addEndpointCardinality(modelElemTypeEndpointRef.getUri(), cardinalityTable); isAllowedTgt = true; break; } } isAllowed = isAllowed && isAllowedTgt; } if (isAllowed) { mappingTypeUris.add(mappingTypeRef.getUri()); } } // check for overrides for (String mappingTypeUri : mappingTypeUris) { //TODO MMINT[OVERRIDE] if one link type points to another one in this list through its override pointer, then delete it } return mappingTypeUris; } public static boolean isAllowedModelEndpoint(ModelEndpointReference modelTypeEndpointRef, Model targetModel, Map<String, Integer> cardinalityTable) { //TODO MMINT[INTROSPECTION] consider static (like now) or runtime types? String targetModelTypeUri = targetModel.getMetatypeUri(); // check if the type is allowed if (modelTypeEndpointRef.getTargetUri().equals(targetModelTypeUri) || MIDTypeHierarchy.isSubtypeOf(targetModelTypeUri, modelTypeEndpointRef.getTargetUri())) { // check if the cardinality is allowed if (MIDRegistry.checkNewEndpointUpperCardinality(modelTypeEndpointRef.getObject(), cardinalityTable)) { return true; } } return false; } public static @Nullable List<String> getAllowedModelEndpoints(@NonNull ModelRel modelRel, @Nullable ModelEndpoint oldModelEndpoint, @Nullable Model targetModel) { if (targetModel == null) { // model not added yet return new ArrayList<String>(); } List<String> modelTypeEndpointUris = null; // count existing instances HashMap<String, Integer> cardinalityTable = new HashMap<String, Integer>(); for (ModelEndpoint modelEndpoint : modelRel.getModelEndpoints()) { MIDRegistry.addEndpointCardinality(modelEndpoint.getMetatypeUri(), cardinalityTable); } // possibly subtract model endpoint to be replaced if (oldModelEndpoint != null) { try { MIDRegistry.subtractEndpointCardinality(oldModelEndpoint.getMetatypeUri(), cardinalityTable); } catch (MMINTException e) { MMINTException.print(IStatus.WARNING, "The model endpoint to be replaced can't be found in the model relationship, skipping it", e); } } // check allowance for (ModelEndpointReference modelTypeEndpointRef : modelRel.getMetatype().getModelEndpointRefs()) { if (isAllowedModelEndpoint(modelTypeEndpointRef, targetModel, cardinalityTable)) { if (modelTypeEndpointUris == null) { modelTypeEndpointUris = new ArrayList<String>(); } modelTypeEndpointUris.add(modelTypeEndpointRef.getUri()); } } return modelTypeEndpointUris; } public static boolean areAllowedModelEndpoints(ModelRel modelRel, ModelRel newModelRelType) { HashMap<String, Integer> cardinalityTable = new HashMap<String, Integer>(); for (ModelEndpoint modelEndpoint : modelRel.getModelEndpoints()) { boolean isAllowed = false; //TODO MMINT[INTROSPECTION] order of visit might affect the result, should be from the most specific to the less for (ModelEndpointReference modelTypeEndpointRef : newModelRelType.getModelEndpointRefs()) { if (isAllowed = isAllowedModelEndpoint(modelTypeEndpointRef, modelEndpoint.getTarget(), cardinalityTable)) { MIDRegistry.addEndpointCardinality(modelTypeEndpointRef.getUri(), cardinalityTable); break; } } if (!isAllowed) { return false; } } return true; } public static boolean isAllowedModelElementEndpointReference(ModelElementEndpoint modelElemTypeEndpoint, ModelElementReference newModelElemRef, HashMap<String, Integer> cardinalityTable) { //TODO MMINT[INTROSPECTION] consider static (like now) or runtime types? String newModelElemTypeUri = newModelElemRef.getObject().getMetatypeUri(); // check if the type is allowed if (modelElemTypeEndpoint.getTargetUri().equals(newModelElemTypeUri) || MIDTypeHierarchy.isSubtypeOf(newModelElemTypeUri, modelElemTypeEndpoint.getTargetUri())) { // check if the cardinality is allowed if (MIDRegistry.checkNewEndpointUpperCardinality(modelElemTypeEndpoint, cardinalityTable)) { return true; } } return false; } public static List<String> getAllowedModelElementEndpointReferences(MappingReference mappingRef, ModelElementEndpointReference oldModelElemEndpointRef, ModelElementReference newModelElemRef) { if (newModelElemRef == null) { // model element reference not added yet return new ArrayList<String>(); } List<String> modelElemTypeEndpointUris = null; // count existing instances HashMap<String, Integer> cardinalityTable = new HashMap<String, Integer>(); for (ModelElementEndpointReference modelElemEndpointRef : mappingRef.getModelElemEndpointRefs()) { MIDRegistry.addEndpointCardinality(modelElemEndpointRef.getObject().getMetatypeUri(), cardinalityTable); } // possibly subtract model element endpoint to be replaced if (oldModelElemEndpointRef != null) { try { MIDRegistry.subtractEndpointCardinality(oldModelElemEndpointRef.getObject().getMetatypeUri(), cardinalityTable); } catch (MMINTException e) { MMINTException.print(IStatus.WARNING, "The model element endpoint to be replaced can't be found in the link, skipping it", e); } } // check allowance for (ModelElementEndpoint modelElemTypeEndpoint : mappingRef.getObject().getMetatype().getModelElemEndpoints()) { if (isAllowedModelElementEndpointReference(modelElemTypeEndpoint, newModelElemRef, cardinalityTable)) { if (modelElemTypeEndpointUris == null) { modelElemTypeEndpointUris = new ArrayList<String>(); } modelElemTypeEndpointUris.add(modelElemTypeEndpoint.getUri()); } } return modelElemTypeEndpointUris; } public static boolean areAllowedModelElementEndpointReferences(Mapping mapping, Mapping newMappingType) { HashMap<String, Integer> cardinalityTable = new HashMap<String, Integer>(); for (ModelElementEndpointReference modelElemEndpointRef : mapping.getModelElemEndpointRefs()) { boolean isAllowed = false; //TODO MMINT[INTROSPECTION] order of visit might affect the result, should be from the most specific to the less for (ModelElementEndpointReference modelElemTypeEndpointRef : newMappingType.getModelElemEndpointRefs()) { if (isAllowed = isAllowedModelElementEndpointReference(modelElemTypeEndpointRef.getObject(), modelElemEndpointRef.getModelElemRef(), cardinalityTable)) { MIDRegistry.addEndpointCardinality(modelElemTypeEndpointRef.getUri(), cardinalityTable); break; } } if (!isAllowed) { return false; } } return true; } public static boolean instanceofEMFClass(EObject modelObj, String eClassName) { if (eClassName.equals(modelObj.eClass().getName())) { return true; } for (EClass modelTypeObjSuper : modelObj.eClass().getEAllSuperTypes()) { if (eClassName.equals(modelTypeObjSuper.getName())) { return true; } } return false; } private static boolean isAllowedModelElement(ModelEndpointReference modelTypeEndpointRef, EObject modelObj, ModelElement modelElemType) { // check root if (MIDTypeHierarchy.isRootType(modelElemType)) { return true; } // check model element compliance EMFInfo modelObjEInfo = MIDRegistry.getModelElementEMFInfo(modelObj, MIDLevel.INSTANCES), modelElemTypeEInfo = modelElemType.getEInfo(); if (modelObjEInfo.isAttribute()) { // attribute compliance + class compliance if ( modelElemTypeEInfo.isAttribute() && modelObjEInfo.getFeatureName().equals(modelElemTypeEInfo.getFeatureName()) && instanceofEMFClass(((PrimitiveEObjectWrapper) modelObj).getOwner(), modelElemTypeEInfo.getClassName()) ) { return true; } } else { // class compliance + containment compliance if ( modelElemTypeEInfo.getFeatureName() == null && instanceofEMFClass(modelObj, modelElemTypeEInfo.getClassName()) ) { if (modelObjEInfo.getRelatedClassName() == null) { // root return true; } boolean isAllowed = true; // default is to allow if no containment model element type is present for (ModelElementReference modelElemTypeRef : modelTypeEndpointRef.getModelElemRefs()) { if (modelElemTypeRef.getUri().equals(modelElemType.getUri())) { // same model element type under test continue; } EMFInfo modelElemTypeContainmentEInfo = modelElemTypeRef.getObject().getEInfo(); if ( // not the right containment model element type modelElemTypeContainmentEInfo.getFeatureName() == null || modelElemTypeContainmentEInfo.isAttribute() || !instanceofEMFClass(modelObj, modelElemTypeContainmentEInfo.getRelatedClassName()) ) { continue; } if ( modelElemTypeContainmentEInfo.getFeatureName().equals(modelObjEInfo.getFeatureName()) && instanceofEMFClass(modelObj.eContainer(), modelElemTypeContainmentEInfo.getClassName()) ) { isAllowed = true; break; } else { // found unallowed containment, default no longer applies isAllowed = false; continue; } } if (isAllowed) { return true; } } } // look for UML stereotypes //TODO MMINT[UML] review // if (modelObj instanceof NamedElement) { // for (Stereotype stereotype : ((NamedElement) modelObj).getApplicableStereotypes()) { // if ( // modelElemTypeEInfo.getClassName().equals(stereotype.getName()) || // stereotype.getName().equals(MAVOUtils.MAVO_UML_STEREOTYPE_EQUIVALENCE.get(modelElemTypeEInfo.getClassName())) // ) { // return true; // } // } // } return false; } public static ModelElement getAllowedModelElementType(ModelEndpointReference modelEndpointRef, EObject modelObj) { ModelRel modelRelType = ((ModelRel) modelEndpointRef.eContainer()).getMetatype(); ModelEndpointReference modelTypeEndpointRef = MIDRegistry.getReference(modelEndpointRef.getObject().getMetatypeUri(), modelRelType.getModelEndpointRefs()); Iterator<ModelElementReference> modelElemTypeRefIter = MIDTypeHierarchy.getInverseTypeRefHierarchyIterator(modelTypeEndpointRef.getModelElemRefs()); while (modelElemTypeRefIter.hasNext()) { ModelElementReference modelElemTypeRef = modelElemTypeRefIter.next(); if (isAllowedModelElement(modelTypeEndpointRef, modelObj, modelElemTypeRef.getObject())) { return modelElemTypeRef.getObject(); } } //TODO MMINT[MODELELEMENT] what about dropped supertypes now, since they can be in a different model type ref? return null; } public static Mapping getAllowedMappingType(Mapping mapping) { ModelRel modelRelType = ((ModelRel) mapping.eContainer()).getMetatype(); mappingTypes: for (Mapping mappingType : modelRelType.getMappings()) { HashSet<String> allowedModelElemTypes = new HashSet<String>(); for (ModelElementEndpoint modelElemTypeEndpoint : mappingType.getModelElemEndpoints()) { allowedModelElemTypes.add(modelElemTypeEndpoint.getTargetUri()); } for (ModelElementEndpoint modelElemEndpoint : mapping.getModelElemEndpoints()) { if (!allowedModelElemTypes.contains(modelElemEndpoint.getTarget().getMetatypeUri())) { continue mappingTypes; } } return mappingType; } return null; } public static @NonNull IReasoningEngine getReasoner(@NonNull String constraintLanguage) throws MMINTException { Map<String, IReasoningEngine> reasoners = MMINT.getLanguageReasoners(constraintLanguage); if (reasoners == null || reasoners.isEmpty()) { throw new MMINTException("Can't find a reasoner for language " + constraintLanguage); } String reasonerName = MMINT.getPreference(MMINTConstants.PREFERENCE_MENU_LANGUAGE_REASONER + constraintLanguage); IReasoningEngine reasoner = reasoners.get(reasonerName); if (reasoner == null) { throw new MMINTException("Can't find reasoner " + reasonerName); } return reasoner; } /** * Checks if a constraint is satisfied on a model. * * @param model * The model. * @param constraint * The constraint. * @return True if the constraint is satisfied, false otherwise. */ public static boolean checkModelConstraint(Model model, ExtendibleElementConstraint constraint) { if (constraint == null || constraint.getImplementation() == null || constraint.getImplementation().equals("")) { return true; } IReasoningEngine reasoner; try { reasoner = getReasoner(constraint.getLanguage()); } catch (MMINTException e) { MMINTException.print(IStatus.WARNING, "Skipping model constraint check", e); return false; } MIDLevel constraintLevel; if (!model.getUri().equals(((ExtendibleElement) constraint.eContainer()).getUri())) { constraintLevel = MIDLevel.TYPES; } else { constraintLevel = MIDLevel.INSTANCES; } return reasoner.checkModelConstraint(model, constraint, constraintLevel); } public static boolean checkOperatorInputConstraint(@NonNull Operator operatorType, @NonNull Map<String, Model> inputsByName) { OperatorConstraint constraint = (OperatorConstraint) operatorType.getConstraint(); if (constraint == null || constraint.getImplementation() == null || constraint.getImplementation().equals("")) { return true; } IReasoningEngine reasoner; try { reasoner = getReasoner(constraint.getLanguage()); } catch (MMINTException e) { MMINTException.print(IStatus.WARNING, "Skipping operator input constraint check", e); return false; } return reasoner.checkOperatorInputConstraint(inputsByName, constraint); } public static boolean checkModelConstraintConsistency(ExtendibleElement type, String constraintLanguage, String constraintImplementation) { if (!(type instanceof Model) || constraintImplementation.equals("")) { return true; } IReasoningEngine reasoner; try { reasoner = getReasoner(constraintLanguage); } catch (MMINTException e) { MMINTException.print(IStatus.WARNING, "Skipping constraint consistency check", e); return true; } return reasoner.checkModelConstraintConsistency((Model) type, constraintImplementation); } //TODO MMINT[REFINE] Should really throw an exception with errors instead of returning null public static @Nullable Model refineModelByConstraint(@NonNull Model model) { if (model.getConstraint() == null) { return null; } IReasoningEngine reasoner; try { reasoner = getReasoner(model.getConstraint().getLanguage()); } catch (MMINTException e) { MMINTException.print(IStatus.WARNING, "Skipping constraint-based refinement", e); return null; } //TODO MMINT[MU-MMINT] Should copy the model constraint to the new model? option to do it or not? return reasoner.refineModelByConstraint(model); } }