| /******************************************************************************* |
| * Copyright (c) 2004-2008 Andras Schmidt, Andras Balogh, Istvan Rath and Daniel Varro |
| * 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: |
| * Andras Schmidt, Andras Balogh, Istvan Rath - initial API and implementation |
| *******************************************************************************/ |
| |
| package org.eclipse.viatra2.modelChecker.impl; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashSet; |
| |
| import org.eclipse.viatra2.core.EMultiplicityKind; |
| import org.eclipse.viatra2.core.IEntity; |
| import org.eclipse.viatra2.core.IModelElement; |
| import org.eclipse.viatra2.core.IModelSpace; |
| import org.eclipse.viatra2.core.IRelation; |
| import org.eclipse.viatra2.modelChecker.EProblemType; |
| import org.eclipse.viatra2.modelChecker.IProblem; |
| import org.eclipse.viatra2.modelChecker.Problem; |
| |
| /** |
| * Summary: This class contains various validation methods for checking the |
| * consistency of the VPM model space. |
| * |
| * Description: This class contains the following checks on the VPM model space: |
| * - type checking for source and target end of relations - validation of |
| * containment prescribed by isAggregation - no dangling edges are present in |
| * the model space - correct refinement of aggregation relations - correct |
| * refinement of relation multiplicities |
| * |
| * @author Akos Schmidt |
| * @author Daniel Varro |
| */ |
| public class ModelChecker { |
| |
| public Collection<IProblem> checkAllModelElement(IModelSpace modelSpace) { |
| try { |
| modelSpace.getTransactionManager().beginTransaction(); |
| IModelElement toCheck = modelSpace.getModelManager().getRoot(); |
| Collection<IProblem> ret = checkBranch(toCheck); |
| return ret; |
| } finally { |
| modelSpace.getTransactionManager().commitTransaction(); |
| } |
| } |
| |
| public Collection<IProblem> checkBranch(IModelElement me) { |
| ArrayList<IProblem> ret = new ArrayList<IProblem>(); |
| checkBranch(me, ret); |
| return ret; |
| } |
| |
| public void checkBranch(IModelElement me, Collection<IProblem> ret) { |
| if (me != null) { |
| checkModelElement(me, ret); |
| Collection<IModelElement> children = me.getElementsInNamespace(); |
| for (IModelElement element : children) { |
| checkBranch(element, ret); |
| } |
| } |
| } |
| |
| public Collection<IProblem> checkModelElement(IModelElement me) { |
| ArrayList<IProblem> ret = new ArrayList<IProblem>(); |
| checkModelElement(me, ret); |
| return ret; |
| } |
| |
| protected void checkModelElement(IModelElement me, Collection<IProblem> ret) { |
| // Take all relations leading out from the current model elements
|
| Collection<IRelation> relsFrom = me.getRelationsFrom(); |
| for (IRelation relation : relsFrom) { |
| checkRelationSource(me, relation, ret); |
| checkSourceMultiplicity(me, relation, new HashSet<IRelation>(), ret); |
| if (!relation.getAllSupertypes().isEmpty()) { |
| checkMetamodelConsistencyForRelation(me, relation, ret); |
| } |
| relationSupertypeValidation(me, relation, ret); |
| } |
| // Take all relations leading into the current model elements
|
| Collection<IRelation> relsTo = me.getRelationsTo(); |
| for (IRelation relation : relsTo) { |
| checkRelationTarget(me, relation, ret); |
| checkTargetMultiplicity(me, relation, new HashSet<IRelation>(), ret); |
| } |
| } |
| |
| protected void checkMetamodelConsistencyForRelation(IModelElement current, |
| IRelation relation, Collection<IProblem> problems) { |
| EMultiplicityKind subMultiplicity = relation.getMultiplicity(); |
| Collection<IModelElement> supertypes = relation.getSupertypes(); |
| for (IModelElement supertype : supertypes) { |
| IRelation relSupertype = (IRelation) supertype; |
| EMultiplicityKind superMultiplicity = relSupertype |
| .getMultiplicity(); |
| // Check the proper refinement of multiplicity restrictions
|
| if (!isMultiplicityRefinement(subMultiplicity, superMultiplicity)) { |
| problems.add(new Problem(current, relation, relSupertype, |
| EProblemType.MULTIPLICITY_REFINEMENT_INVALID)); |
| } |
| // Check the proper refinement of aggregations
|
| if (relSupertype.getIsAggregation() && !relation.getIsAggregation()) { |
| problems.add(new Problem(current, relation, relSupertype, |
| EProblemType.AGGREGATION_REFINEMENT_INVALID)); |
| } |
| |
| } |
| } |
| |
| protected boolean isMultiplicityRefinement(EMultiplicityKind subMulti, |
| EMultiplicityKind superMulti) { |
| // If the super multiplicity is MANY_TO_MANY, any refinement is allowed
|
| if (superMulti.compareTo(EMultiplicityKind.MANY_TO_MANY) == 0) { |
| return true; |
| } |
| // If the super multiplicity is MANY_TO_ONE, and
|
| // the sub multiplicity is either MANY_TO_ONE, or ONE_TO_ONE -> true
|
| else if (superMulti.compareTo(EMultiplicityKind.MANY_TO_ONE) == 0) { |
| if (subMulti.compareTo(EMultiplicityKind.MANY_TO_ONE) == 0 |
| || subMulti.compareTo(EMultiplicityKind.ONE_TO_ONE) == 0) { |
| return true; |
| } else { |
| return false; |
| } |
| } |
| // If the super multiplicity is ONE_TO_MANY, and
|
| // the sub multiplicity is either ONE_TO_MANY, or ONE_TO_ONE -> true
|
| else if (superMulti.compareTo(EMultiplicityKind.ONE_TO_MANY) == 0) { |
| if (subMulti.compareTo(EMultiplicityKind.ONE_TO_MANY) == 0 |
| || subMulti.compareTo(EMultiplicityKind.ONE_TO_ONE) == 0) { |
| return true; |
| } else { |
| return false; |
| } |
| } |
| // If the super multiplicity is ONE_TO_ONE, and
|
| // the sub multiplicity is ONE_TO_ONE -> true
|
| else if (superMulti.compareTo(EMultiplicityKind.ONE_TO_ONE) == 0 |
| && subMulti.compareTo(EMultiplicityKind.ONE_TO_ONE) == 0) { |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| /** |
| * Checks that multiplicity of a relation is respected by outgoing relations |
| * |
| * @param current |
| * : the current model element (typically entity) to be traversed |
| * @param relation |
| * : the relation to be investigated |
| * @param problematicTypes |
| * : collection of types already violating multiplicity at the |
| * current element |
| * @param problems |
| * : list of problems |
| */ |
| protected void checkSourceMultiplicity(IModelElement current, |
| IRelation relation, Collection<IRelation> problematicTypes, |
| Collection<IProblem> problems) { |
| // Type-specific model validation
|
| // It is sufficient to carry out validation for direct types of relation
|
| Collection<IModelElement> relTypes = relation.getTypes(); |
| for (IModelElement type : relTypes) { |
| IRelation relType = (IRelation) type; |
| // Type checking for source end of relation
|
| // Checking multiplicity for source end of relation is respected by |
| // outgoing relations
|
| EMultiplicityKind multiplicity = relType.getMultiplicity(); |
| if (multiplicity.compareTo(EMultiplicityKind.ONE_TO_ONE) == 0 |
| || multiplicity.compareTo(EMultiplicityKind.MANY_TO_ONE) == 0) { |
| if (current.getAllRelationFromByType(relType).size() > 1 |
| && !problematicTypes.contains(relType)) { |
| problems |
| .add(new Problem( |
| current, |
| relation, |
| relType, |
| EProblemType.OUTGOING_MULTIPLICITY_CONSTRAINT_VIOLATED)); |
| // Avoid to print error messages twice for the same type
|
| problematicTypes.add(relType); |
| } |
| } |
| //
|
| } |
| } |
| |
| /** |
| * Checks that multiplicity of a relation is respected by incoming relations |
| * |
| * @param current |
| * : the current model element (typically entity) to be traversed |
| * @param relation |
| * : the relation to be investigated |
| * @param problematicTypes |
| * : collection of types already violating multiplicity at the |
| * current element |
| * @param problems |
| * : list of problems |
| */ |
| protected void checkTargetMultiplicity(IModelElement current, |
| IRelation relation, Collection<IRelation> problematicTypes, |
| Collection<IProblem> problems) { |
| // Type-specific model validation
|
| // It is sufficient to carry out validation for direct types of relation
|
| Collection<IModelElement> relTypes = relation.getTypes(); |
| for (IModelElement type : relTypes) { |
| IRelation relType = (IRelation) type; |
| // Type checking for source end of relation
|
| // Checking multiplicity for source end of relation is respected by |
| // outgoing relations
|
| EMultiplicityKind multiplicity = relType.getMultiplicity(); |
| if (multiplicity.compareTo(EMultiplicityKind.ONE_TO_ONE) == 0 |
| || multiplicity.compareTo(EMultiplicityKind.ONE_TO_MANY) == 0) { |
| if (current.getAllRelationToByType(relType).size() > 1 |
| && !problematicTypes.contains(relType)) { |
| problems |
| .add(new Problem( |
| current, |
| relation, |
| relType, |
| EProblemType.INCOMING_MULTIPLICITY_CONSTRAINT_VIOLATED)); |
| // Avoid to print error messages twice for the same type
|
| problematicTypes.add(relType); |
| } |
| } |
| //
|
| } |
| } |
| |
| /** |
| * This method carries out the following validations for the source end of a |
| * relation: (1) relation is not dangling i.e. its other (target) end is |
| * defined (2) type checking the source end of relation (3) checking that |
| * multiplicity of relation is respected by outgoing relations |
| * |
| * @param current |
| * : the current model element (typically entity) to be traversed |
| * @param rel |
| * : the relation to be investigated |
| * @param problems |
| * : the list of problems found |
| */ |
| protected void checkRelationSource(IModelElement current, IRelation rel, |
| Collection<IProblem> problems) { |
| // Check dangling edges, i.e. that the target of the relation is not |
| // null (when the relation is corrupt)
|
| if (rel.getTo() == null) { |
| problems.add(new Problem(current, rel, (IRelation) null, |
| EProblemType.INVALID_RELATION_TARGET)); |
| } |
| // Type-specific model validation
|
| Collection<IModelElement> relTypes = rel.getAllTypes(); |
| for (IModelElement type : relTypes) { |
| IRelation relType = (IRelation) type; |
| // Type checking for source end of relation
|
| typecheckRelationSource(current, relType, rel, problems); |
| } |
| } |
| |
| /** |
| * This method carries out the following validations for the target end of a |
| * relation: (1) relation is not dangling i.e. its other (source) end is |
| * defined (2) type checking the target end of relation (3) checking that |
| * multiplicity of relation is respected by incoming relations |
| * |
| * @param current |
| * : the current model element (typically entity) to be traversed |
| * @param rel |
| * : the relation to be investigated |
| * @param problems |
| * : the list of problems found |
| */ |
| protected void checkRelationTarget(IModelElement current, IRelation rel, |
| Collection<IProblem> problems) { |
| // Check dangling edges, i.e. that the source of the relation is not |
| // null (when the relation is corrupt)
|
| if (rel.getFrom() == null) { |
| problems.add(new Problem(current, rel, (IRelation) null, |
| EProblemType.INVALID_RELATION_SOURCE)); |
| } |
| // Type-specific model validation
|
| Collection<IModelElement> relTypes = rel.getAllTypes(); |
| for (IModelElement type : relTypes) { |
| IRelation relType = (IRelation) type; |
| // Type checking for target end of relation
|
| typecheckRelationTarget(current, relType, rel, problems); |
| } |
| } |
| |
| /** |
| * Checks that the source entity of a relation with an aggregation type is |
| * the parent of the target entity of this relation. This check is triggered |
| * only when both the source and the target end of a relation is an entity. |
| * |
| * @param rel |
| * : the relation to be investigated |
| * @param relType |
| * : the type of the relation |
| * @param problems |
| * : collection of model validation problems |
| */ |
| protected void checkContainmentIsConsistentWithIsAggregation(IRelation rel, |
| IRelation relType, Collection<IProblem> problems) { |
| // TODO: Check if the instanceof calls can be changed to isEntity() calls |
| if (rel.getFrom() instanceof IEntity && rel.getTo() instanceof IEntity) { |
| IEntity fromEntity = (IEntity) rel.getFrom(); |
| IEntity toEntity = (IEntity) rel.getTo(); |
| // TODO: Check that this is the correct comparison to be used
|
| if (toEntity.getParent() != fromEntity) { |
| problems.add(new Problem(fromEntity, rel, relType, |
| EProblemType.AGGREGATION_INVALID)); |
| } |
| } |
| |
| } |
| |
| /** |
| * Type checking the source end of a relation, and checking if the |
| * containment hierarchy is consistent with the isAggregation parameter |
| * |
| * @param current |
| * : the source end of a relation |
| * @param relType |
| * : the type of the relation |
| * @param rel |
| * : the relation itself |
| * @param ret |
| * : list of problems |
| */ |
| protected void typecheckRelationSource(IModelElement current, |
| IRelation relType, IRelation rel, Collection<IProblem> ret) { |
| assert (current != null); |
| if (relType.getIsAggregation()) { |
| checkContainmentIsConsistentWithIsAggregation(rel, relType, ret); |
| } |
| // if(relType.getIsAnyFrom())
|
| // return;
|
| Collection<IModelElement> types = current.getAllTypes(); |
| for (IModelElement element : types) { |
| Collection<IRelation> edgeTypes = element.getRelationsFrom(); |
| if (edgeTypes.contains(relType)) { |
| // Type ok
|
| return; |
| } |
| } |
| ret.add(new Problem(current, rel, relType, |
| EProblemType.INVALID_RELATION_SOURCE_TYPE)); |
| } |
| |
| /** |
| * Type checking the target end of a relation |
| * |
| * @param current |
| * : the source end of a relation |
| * @param relType |
| * : the type of the relation |
| * @param rel |
| * : the relation itself |
| * @param ret |
| * : list of problems |
| */ |
| |
| protected void typecheckRelationTarget(IModelElement current, |
| IRelation relType, IRelation rel, Collection<IProblem> ret) { |
| // if(relType.getIsAnyTo())
|
| // return;
|
| Collection<IModelElement> types = current.getAllTypes(); |
| for (IModelElement element : types) { |
| Collection<IRelation> edgeTypes = element.getRelationsTo(); |
| if (edgeTypes.contains(relType)) { |
| // Type ok
|
| return; |
| } |
| } |
| ret.add(new Problem(current, rel, relType, |
| EProblemType.INVALID_RELATION_TARGET_TYPE)); |
| } |
| |
| /** |
| * Checks if all refinements (subtypes) of a relation are properly typed, |
| * i.e. the refinement itself is source and target consistent |
| * |
| * @param me : the source of the relation |
| * @param rel : the relation to be checked |
| * @param problems : list of problems |
| */ |
| protected void relationSupertypeValidation(IModelElement me, IRelation rel, |
| Collection<IProblem> problems) { |
| for (IModelElement subElement: rel.getSubtypes()) { |
| // Check that all direct subtypes of relation rel are also relations |
| if (subElement instanceof IRelation) { |
| IRelation subRel = (IRelation) subElement; |
| // If X < Y, check that src(X) < src(Y) when both X and Y are relations |
| if (!rel.getFrom().equals(subRel.getFrom()) && |
| !rel.getFrom().isSubtypeOf(subRel.getFrom())) { |
| problems.add(new Problem(me, rel, subRel, |
| EProblemType.INVALID_REFINEMENT_SOURCE_TYPE)); |
| } |
| |
| // If X < Y, check that trg(X) < trg(Y) when both X and Y are relations |
| if (!rel.getTo().equals(subRel.getTo()) && |
| !rel.getTo().isSubtypeOf(subRel.getTo())) { |
| problems.add(new Problem(me, rel, subRel, |
| EProblemType.INVALID_REFINEMENT_TARGET_TYPE)); |
| } |
| } |
| else { |
| // If rel is refined into an entity |
| if (subElement.isEntity()) { |
| problems.add(new Problem(me, rel,(IEntity) subElement, |
| EProblemType.INVALID_REFINEMENT)); |
| } |
| } |
| } |
| for (IModelElement superElement: rel.getSupertypes()) { |
| // If rel is refined into an entity, issue an error message |
| if (superElement.isEntity()) { |
| problems.add(new Problem(me, rel, (IEntity) superElement, |
| EProblemType.INVALID_REFINEMENT)); |
| } |
| } |
| } |
| |
| /** |
| * @deprecated |
| */ |
| public String printProblems(Collection<IProblem> problems) { |
| if (problems == null) |
| return ""; |
| StringBuffer buf = new StringBuffer(); |
| for (IProblem problem : problems) { |
| buf.append("not compatible types"); |
| buf.append(" " + problem.getProblemType().toString() + " "); |
| buf.append("(entity, relation, relation type):"); |
| buf.append(problem.getEndPoint().getFullyQualifiedName() + ","); |
| buf.append(problem.getRelation().getFullyQualifiedName() + ","); |
| buf.append(problem.getRelationType().getFullyQualifiedName()); |
| buf.append("\n"); |
| } |
| return buf.toString(); |
| } |
| } |