blob: 07deef3c54f82b77274363a6dd255b71188bd864 [file] [log] [blame]
/*******************************************************************************
* 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();
}
}