/*****************************************************************************
* Copyright (c) 2010 Atos Origin.
*
*
* 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:
* Atos Origin - Initial API and implementation
*
*****************************************************************************/
package org.eclipse.papyrus.uml.diagram.activity.helper;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.validation.IValidationContext;
import org.eclipse.uml2.uml.Action;
import org.eclipse.uml2.uml.Activity;
import org.eclipse.uml2.uml.ActivityEdge;
import org.eclipse.uml2.uml.ActivityNode;
import org.eclipse.uml2.uml.Behavior;
import org.eclipse.uml2.uml.CallOperationAction;
import org.eclipse.uml2.uml.ControlNode;
import org.eclipse.uml2.uml.DecisionNode;
import org.eclipse.uml2.uml.Element;
import org.eclipse.uml2.uml.ExceptionHandler;
import org.eclipse.uml2.uml.ExecutableNode;
import org.eclipse.uml2.uml.InputPin;
import org.eclipse.uml2.uml.InterruptibleActivityRegion;
import org.eclipse.uml2.uml.ObjectFlow;
import org.eclipse.uml2.uml.ObjectNode;
import org.eclipse.uml2.uml.Operation;
import org.eclipse.uml2.uml.OutputPin;
import org.eclipse.uml2.uml.Parameter;
import org.eclipse.uml2.uml.ParameterDirectionKind;
import org.eclipse.uml2.uml.Pin;
import org.eclipse.uml2.uml.StructuredActivityNode;
import org.eclipse.uml2.uml.Type;
import org.eclipse.uml2.uml.UMLPackage;
import org.eclipse.uml2.uml.ValuePin;
import org.eclipse.uml2.uml.ValueSpecification;
public class UMLValidationHelper {
/**
* Exception Handler constraint define in From UML Superstructure Version 2.4.1 with change bars
*/
private static final String EXCEPTION_HANDLER_CONSTRAINT_2 = "An edge that has a source in an exception handler structured node must also have its target in the handler, and vice versa.";
/**
* Exception Handler constraint define in From UML Superstructure Version 2.4.1 with change bars
*/
private static final String EXCEPTION_HANDLER_CONSTRAINT_3 = "If the protected node is a StructuredActivityNode with output pins, then the exception handler body must also be a StructuredActivityNode with output pins that correspond in number and types to those of the protected node.";
/**
* Exception Handler constraint define in From UML Superstructure Version 2.4.1 with change bars
*/
private static final String EXCEPTION_HANDLER_CONSTRAINT_4 = "The handler body has one input, and that input is the same as the exception input";
/**
* Exception Handler constraint define in From UML Superstructure Version 2.4.1 with change bars
*/
private static final String EXCEPTION_HANDLER_CONSTRAINT_1 = "The exception handler and its input object node are not the source or target of any edge.";
/**
* The source and target of an edge must be in the same activity as the
* edge.
*
* @param context
* The receiving '<em><b>Activity Edge</b></em>' model object.
* @param ctx
* The cache of context-specific information.
*/
public static IStatus validateSourceAndTarget(ActivityEdge context, IValidationContext ctx) {
Activity edgeActivity = context.getActivity();
if(edgeActivity == null) {
// edge is contained by activity or group. Its activity is by
// consequence a parent of it
Element edgeOwner = context.getOwner();
while(edgeOwner != null && !(edgeOwner instanceof Activity)) {
edgeOwner = edgeOwner.getOwner();
}
if(edgeOwner instanceof Activity) {
edgeActivity = (Activity)edgeOwner;
} else {
// this case should not occur. But in such a case, model is
// invalid for another
// reason
return ctx.createSuccessStatus();
}
}
ActivityNode source = context.getSource();
ActivityNode target = context.getTarget();
if(source != null && target != null) {
Activity sourceActivity = source.getActivity();
if(sourceActivity == null) {
// activity node is contained by activity or group. Its activity
// is by consequence a
// parent of it
Element sourceOwner = source.getOwner();
while(sourceOwner != null && !(sourceOwner instanceof Activity)) {
sourceOwner = sourceOwner.getOwner();
}
if(sourceOwner instanceof Activity) {
sourceActivity = (Activity)sourceOwner;
} else {
// this case should not occur. But in such a case, model is
// invalid for another
// reason
return ctx.createSuccessStatus();
}
}
Activity targetActivity = target.getActivity();
if(targetActivity == null) {
// activity node is contained by activity or group. Its activity
// is by consequence a
// parent of it
Element targetOwner = target.getOwner();
while(targetOwner != null && !(targetOwner instanceof Activity)) {
targetOwner = targetOwner.getOwner();
}
if(targetOwner instanceof Activity) {
targetActivity = (Activity)targetOwner;
} else {
// this case should not occur. But in such a case, model is
// invalid for another
// reason
return ctx.createSuccessStatus();
}
}
if(!edgeActivity.equals(sourceActivity) || !edgeActivity.equals(targetActivity)) {
return ctx.createFailureStatus();
}
}
return ctx.createSuccessStatus();
}
/**
* Implementation of the constraint
* [1] Interrupting edges of a region must have their source node in the region and their target node outside the region in the
* same activity containing the region.
* From UML Superstructure Version 2.4.1 with change bars
* USE for validation framework
*
* @param context
* @param ctx
* @param interrupts
* @return
*/
public static IStatus validateInterruptibleEdge(ActivityEdge context, IValidationContext ctx) {
return validateInterruptibleEdge(context, context.getInterrupts()) ? ctx.createSuccessStatus() : ctx.createFailureStatus("Interrupting edges of a region must have their source node in the region and their target node outside the region in the same activity containing the region.");////$NON-NLS-0$
}
/**
* Implementation of the constraint on Exception Handler:
* [2] An edge that has a source in an exception handler structured node must also have its target in the handler, and vice versa.
*
* @param context
* @param ctx
* @return
*/
public static IStatus validateException_StructuredActivityNode_Constraint2(ActivityEdge context, IValidationContext ctx) {
/*
* Test the constraint un Exception handler section from Version 2.4.1 with change bars
* [2] An edge that has a source in an exception handler structured node must also have its target in the handler, and vice versa.
*/
ActivityNode source = context.getSource();
ActivityNode target = context.getTarget();
if(source != null && target != null) {
StructuredActivityNode inStrucActNode = source.getInStructuredNode();
if(inStrucActNode != null) {
if(!inStrucActNode.equals(target.getInStructuredNode())) {
return ctx.createFailureStatus(EXCEPTION_HANDLER_CONSTRAINT_2);
}
} else {
if(target.getInStructuredNode() != null) {
return ctx.createFailureStatus(EXCEPTION_HANDLER_CONSTRAINT_2);
}
}
}
return ctx.createSuccessStatus();
}
/**
* Implementation of the constraint on Exception Handler:
* [4] The handler body has one input, and that input is the same as the exception input
*
* @param context
* @param ctx
* @return
*/
public static IStatus validateException_HandlerBody_Constraint4(ExceptionHandler context, IValidationContext ctx) {
ExecutableNode handlerBody = context.getHandlerBody();
/*
* [4] The handler body has one input, and that input is the same as the exception input
*/
if(handlerBody != null) {
if(handlerBody instanceof Action) {
Action handlerBodyAction = (Action)handlerBody;
if(handlerBodyAction.getInputs() == null || handlerBodyAction.getInputs().size() != 1) {
return ctx.createFailureStatus(EXCEPTION_HANDLER_CONSTRAINT_4);////$NON-NLS-1$
}
}
}
return ctx.createSuccessStatus();
}
/**
* Implementation of the constraint:
* [3] If the protected node is a StructuredActivityNode with output pins, then the exception handler body must also be a
* StructuredActivityNode with output pins that correspond in number and types to those of the protected node.
*
* @param context
* @param ctx
* @return
*/
public static IStatus validateException_SourceAndTargetEdge_Constraint1(ExceptionHandler context, IValidationContext ctx) {
/*
* [1] The exception handler and its input object node are not the source or target of any edge.
*/
ObjectNode exceptionInput = context.getExceptionInput();
if(exceptionInput != null) {
EList<ActivityEdge> incominEdges = exceptionInput.getIncomings();
EList<ActivityEdge> outgoingEdges = exceptionInput.getOutgoings();
if((incominEdges != null && incominEdges.size() != 0) || (outgoingEdges != null && outgoingEdges.size() != 0)) {
return ctx.createFailureStatus(EXCEPTION_HANDLER_CONSTRAINT_1);
}
}
return ctx.createSuccessStatus();
}
/**
* Implementation of the constraint:
* [3] If the protected node is a StructuredActivityNode with output pins, then the exception handler body must also be a
* StructuredActivityNode with output pins that correspond in number and types to those of the protected node.
*
* @param context
* @param ctx
* @return
*/
public static IStatus validateException_StructuredActivityNode_Constraint3(ExceptionHandler context, IValidationContext ctx) {
/*
* [3] If the protected node is a StructuredActivityNode with output pins, then the exception handler body must also be a
* StructuredActivityNode with output pins that correspond in number and types to those of the protected node.
*/
ExecutableNode protectedNode = context.getProtectedNode();
ExecutableNode handlerBody = context.getHandlerBody();
if(protectedNode != null && handlerBody != null) {
if(protectedNode instanceof StructuredActivityNode) {
StructuredActivityNode structuredActNode = (StructuredActivityNode)protectedNode;
if(handlerBody instanceof StructuredActivityNode) {
EList<OutputPin> protectedNodeOutputPin = structuredActNode.getOutputs();
EList<OutputPin> handlerBodyOutputPin = ((StructuredActivityNode)handlerBody).getOutputs();
if(protectedNodeOutputPin.size() != handlerBodyOutputPin.size()) {
return ctx.createFailureStatus(EXCEPTION_HANDLER_CONSTRAINT_3);
}
for(int i = 0; i < protectedNodeOutputPin.size(); i++) {
OutputPin outputPin = protectedNodeOutputPin.get(i);
if(outputPin != null && outputPin.equals(handlerBodyOutputPin.get(i))) {
return ctx.createFailureStatus(EXCEPTION_HANDLER_CONSTRAINT_3);
}
}
} else if(handlerBody != null) {
return ctx.createFailureStatus(EXCEPTION_HANDLER_CONSTRAINT_3);
}
}
}
return ctx.createSuccessStatus();
}
/**
* Implementation of the constraint
* [1] Interrupting edges of a region must have their source node in the region and their target node outside the region in the
* same activity containing the region.
* From UML Superstructure Version 2.4.1 with change bars
*
* @param context
* @param ctx
* @param interrupts
* @return
*/
public static boolean validateInterruptibleEdge(ActivityEdge context, InterruptibleActivityRegion interrupts) {
if(interrupts != null) {
//validate source
Element source = context.getSource();
boolean validSource = false;
while(source instanceof ActivityNode && !validSource) {
if(((ActivityNode)source).getInInterruptibleRegions().contains(interrupts)) {
validSource = true;
}
source = source.getOwner();
}
if(!validSource) {
return false;
}
//validate target
Element target = context.getTarget();
while(target instanceof ActivityNode) {
if(((ActivityNode)target).getInInterruptibleRegions().contains(interrupts)) {
return false;
}
target = target.getOwner();
}
}
return true;
}
/**
* Object nodes connected by an object flow, with optionally intervening
* control nodes, must have compatible types. In particular, the downstream
* object node type must be the same or a supertype of the upstream object
* node type.
*
* @param context
* The receiving '<em><b>Object Flow</b></em>' model object.
* @param ctx
* The cache of context-specific information.
*/
public static IStatus validateCompatibleTypes(ObjectFlow context, IValidationContext ctx) {
/*
* In case object flow have transformation behavior, the basic
* constraint (which describes general case only) does not need to be
* respected. In such a case Type consistency is checked by
* validateTransformationBehaviour. The UML specification is not very
* precise on this point. The constraint must be relaxed when a
* transformation interferes.
*/
if(context.getTransformation() == null) {
List<Type> srcTypes = getUpstreamExpectedTypes(context, new LinkedList<ObjectFlow>());
for(Type srcType : srcTypes) {
for(Type targetType : getDownstreamExpectedTypes(context, new LinkedList<ObjectFlow>())) {
if(!isSuperType(targetType, srcType)) {
return ctx.createFailureStatus();
}
}
}
}
// if there is a transformation, validateTransformationBehaviour
// applies.
return ctx.createSuccessStatus();
}
/**
* Object nodes connected by an object flow, with optionally intervening
* control nodes, must have the same upper bounds.
*
* @param context
* The receiving '<em><b>Object Flow</b></em>' model object.
* @param ctx
* The cache of context-specific information.
*/
public static IStatus validateSameUpperBounds(ObjectFlow context, IValidationContext ctx) {
ActivityNode source = context.getSource();
if(source instanceof ObjectNode) {
ValueSpecification srcUpper = ((ObjectNode)source).getUpperBound();
for(ObjectNode targetNode : getDownStreamObjectNodes(context)) {
ValueSpecification targetUpper = targetNode.getUpperBound();
if(!EcoreUtil.equals(srcUpper, targetUpper)) {
return ctx.createFailureStatus();
}
}
}
return ctx.createSuccessStatus();
}
/**
* An edge with constant weight may not target an object node, or lead to an
* object node downstream with no intervening actions, that has an upper
* bound less than the weight.
*
* @param context
* The receiving '<em><b>Object Flow</b></em>' model object.
* @param ctx
* The cache of context-specific information.
*/
public static IStatus validateTarget(ObjectFlow context, IValidationContext ctx) {
int weight = 0;
try {
if(context.getWeight() != null) {
weight = context.getWeight().integerValue();
} else {
// no weight specified
return ctx.createSuccessStatus();
}
} catch (UnsupportedOperationException noValueExc) {
return ctx.createSuccessStatus();
}
for(ObjectNode targetNode : getDownStreamObjectNodes(context)) {
int targetUpper = 0;
try {
if(targetNode.getUpperBound() != null) {
targetUpper = targetNode.getUpperBound().integerValue();
} else {
// no upper bound specified
continue;
}
} catch (UnsupportedOperationException noValueExc) {
continue;
}
if(targetUpper < weight) {
return ctx.createFailureStatus();
}
}
return ctx.createSuccessStatus();
}
/**
* A transformation behavior has one input parameter and one output
* parameter. The input parameter must be the same as or a supertype of the
* type of object token coming from the source end. The output parameter
* must be the same or a subtype of the type of object token expected
* downstream. The behavior cannot have side effects.
*
* @param context
* The receiving '<em><b>Object Flow</b></em>' model object.
* @param ctx
* The cache of context-specific information.
*/
public static IStatus validateTransformationBehaviour(ObjectFlow context, IValidationContext ctx) {
Behavior transformation = context.getTransformation();
if(transformation != null) {
int numberIn = 0;
int numberOut = 0;
Parameter paramIn = null;
Parameter paramOut = null;
for(Parameter param : transformation.getOwnedParameters()) {
switch(param.getDirection()) {
case IN_LITERAL:
numberIn++;
paramIn = param;
break;
case OUT_LITERAL:
case RETURN_LITERAL:
numberOut++;
paramOut = param;
break;
case INOUT_LITERAL:
numberIn++;
paramIn = param;
numberOut++;
paramOut = param;
break;
}
}
if(numberIn != 1 || numberOut != 1) {
return ctx.createFailureStatus();
}
// check types coming in the flow
List<Type> srcTypes = getUpstreamExpectedTypes(context, new LinkedList<ObjectFlow>());
for(Type typeToCheck : srcTypes) {
if(!isSuperType(paramIn.getType(), typeToCheck)) {
ctx.createFailureStatus();
}
}
// check types going out the flow
List<Type> targetTypes = getDownstreamExpectedTypes(context, new LinkedList<ObjectFlow>());
for(Type typeToCheck : targetTypes) {
if(!isSuperType(typeToCheck, paramOut.getType())) {
ctx.createFailureStatus();
}
}
}
return ctx.createSuccessStatus();
}
/**
* A selection behavior has one input parameter and one output parameter.
* The input parameter must be a bag of elements of the same as or a
* supertype of the type of source object node. The output parameter must be
* the same or a subtype of the type of source object node. The behavior
* cannot have side effects.
*
* @param context
* The receiving '<em><b>Object Flow</b></em>' model object.
* @param ctx
* The cache of context-specific information.
*/
public static IStatus validateInputAndOutputParameter(ObjectFlow context, IValidationContext ctx) {
Behavior selection = context.getSelection();
if(selection != null) {
int numberIn = 0;
int numberOut = 0;
Parameter paramIn = null;
Parameter paramOut = null;
for(Parameter param : selection.getOwnedParameters()) {
switch(param.getDirection()) {
case IN_LITERAL:
numberIn++;
paramIn = param;
break;
case OUT_LITERAL:
case RETURN_LITERAL:
numberOut++;
paramOut = param;
break;
case INOUT_LITERAL:
numberIn++;
paramIn = param;
numberOut++;
paramOut = param;
break;
}
}
if(numberIn != 1 || numberOut != 1) {
return ctx.createFailureStatus();
}
// check types coming in the flow
List<Type> srcTypes = getUpstreamExpectedTypes(context, new LinkedList<ObjectFlow>());
for(Type typeToCheck : srcTypes) {
if(!isSuperType(paramIn.getType(), typeToCheck)) {
ctx.createFailureStatus();
}
}
// check types going out the flow
List<Type> targetTypes = getDownstreamExpectedTypes(context, new LinkedList<ObjectFlow>());
for(Type typeToCheck : targetTypes) {
if(!isSuperType(typeToCheck, paramOut.getType())) {
ctx.createFailureStatus();
}
}
}
return ctx.createSuccessStatus();
}
/**
* The type of value specification must be compatible with the type of the
* value pin.
*
* @param context
* The receiving '<em><b>Value Pin</b></em>' model object.
* @param ctx
* The cache of context-specific information.
*/
public static IStatus validateCompatibleType(ValuePin context, IValidationContext ctx) {
ValueSpecification value = context.getValue();
if(value != null) {
if(!isSuperType(context.getType(), value.getType())) {
return ctx.createFailureStatus();
}
}
return ctx.createSuccessStatus();
}
/**
* If the decision node has no decision input flow and an incoming object
* flow, then a decision input behavior has one input parameter whose type
* is the same as or a supertype of the type of object tokens offered on the
* incoming edge.
*
* @param context
* The receiving '<em><b>Decision Node</b></em>' model object.
* @param ctx
* The cache of context-specific information.
*/
public static IStatus validateIncomingObjectOneInputParameter(DecisionNode context, IValidationContext ctx) {
Behavior behavior = context.getDecisionInput();
if(behavior != null) {
ObjectFlow decisionInputFlow = context.getDecisionInputFlow();
ActivityEdge incomingObjectFlow = context.getIncoming(null, true, UMLPackage.eINSTANCE.getObjectFlow());
if(decisionInputFlow == null && incomingObjectFlow != null) {
ObjectFlow inFlow = (ObjectFlow)incomingObjectFlow;
/*
* No decision input flow and an incoming object flow. There
* must be 1 in parameter with type compatibility with
* incomingObjectFlow
*/
boolean parameterFound = false;
for(Parameter param : behavior.getOwnedParameters()) {
if(ParameterDirectionKind.IN_LITERAL.equals(param.getDirection())) {
if(!parameterFound) {
// recover type coming from the flow
List<Type> types = getTypeComingFromFlow(inFlow, new LinkedList<ObjectFlow>());
for(Type comingType : types) {
if(!isSuperType(param.getType(), comingType)) {
// type of the parameter is not compatible
// with incoming edge
return ctx.createFailureStatus();
}
}
} else {
// unexpected second input parameter
return ctx.createFailureStatus();
}
}
}
if(!parameterFound) {
// expected input parameter not found
return ctx.createFailureStatus();
}
}
}
return ctx.createSuccessStatus();
}
/**
* If the decision node has a decision input flow and an incoming control
* flow, then a decision input behavior has one input parameter whose type
* is the same as or a supertype of the type of object tokens offered on the
* decision input flow.
*
* @param context
* The receiving '<em><b>Decision Node</b></em>' model object.
* @param ctx
* The cache of context-specific information.
*/
public static IStatus validateIncomingControlOneInputParameter(DecisionNode context, IValidationContext ctx) {
Behavior behavior = context.getDecisionInput();
if(behavior != null) {
ObjectFlow decisionInputFlow = context.getDecisionInputFlow();
ActivityEdge incomingControlFlow = context.getIncoming(null, true, UMLPackage.eINSTANCE.getControlFlow());
if(decisionInputFlow != null && incomingControlFlow != null) {
/*
* Decision input flow and an incoming control flow. There must
* be 1 in parameter with type compatibility with
* decisionInputFlow
*/
boolean parameterFound = false;
for(Parameter param : behavior.getOwnedParameters()) {
if(ParameterDirectionKind.IN_LITERAL.equals(param.getDirection())) {
if(!parameterFound) {
// recover type coming from the flow
List<Type> types = getTypeComingFromFlow(decisionInputFlow, new LinkedList<ObjectFlow>());
for(Type comingType : types) {
if(!isSuperType(param.getType(), comingType)) {
// type of the parameter is not compatible
// with incoming edge
return ctx.createFailureStatus();
}
}
parameterFound = true;
} else {
// unexpected second input parameter
return ctx.createFailureStatus();
}
}
}
if(!parameterFound) {
// expected input parameter not found
return ctx.createFailureStatus();
}
}
}
return ctx.createSuccessStatus();
}
/**
* If the decision node has a decision input flow and an second incoming
* object flow, then a decision input behavior has two input parameters, the
* first of which has a type that is the same as or a supertype of the type
* of the type of object tokens offered on the nondecision input flow and
* the second of which has a type that is the same as or a supertype of the
* type of object tokens offered on the decision input flow.
*
* @param context
* The receiving '<em><b>Decision Node</b></em>' model object.
* @param ctx
* The cache of context-specific information.
*/
public static IStatus validateTwoInputParameters(DecisionNode context, IValidationContext ctx) {
Behavior behavior = context.getDecisionInput();
if(behavior != null) {
ObjectFlow decisionInputFlow = context.getDecisionInputFlow();
ActivityEdge incomingObjectFlow = null;
for(ActivityEdge incomingEdge : context.getIncomings()) {
// filter the decision flow
if(incomingEdge instanceof ObjectFlow && incomingEdge != decisionInputFlow) {
incomingObjectFlow = incomingEdge;
}
}
if(decisionInputFlow != null && incomingObjectFlow != null) {
ObjectFlow inFlow = (ObjectFlow)incomingObjectFlow;
/*
* Decision input flow and an other incoming object flow. There
* must be 2 in parameters with type compatibility with each
* flow
*/
int numberOfParameterFound = 0;
for(Parameter param : behavior.getOwnedParameters()) {
if(ParameterDirectionKind.IN_LITERAL.equals(param.getDirection())) {
if(numberOfParameterFound == 0) {
// recover type coming from the non decision flow
List<Type> types = getTypeComingFromFlow(inFlow, new LinkedList<ObjectFlow>());
for(Type comingType : types) {
if(!isSuperType(param.getType(), comingType)) {
// type of the parameter is not compatible
// with incoming edge
return ctx.createFailureStatus();
}
}
numberOfParameterFound++;
} else if(numberOfParameterFound == 1) {
// recover type coming from the decision flow
List<Type> types = getTypeComingFromFlow(decisionInputFlow, new LinkedList<ObjectFlow>());
for(Type comingType : types) {
if(!isSuperType(param.getType(), comingType)) {
// type of the parameter is not compatible
// with incoming edge
return ctx.createFailureStatus();
}
}
numberOfParameterFound++;
} else {
// unexpected third input parameter
return ctx.createFailureStatus();
}
}
}
if(numberOfParameterFound < 2) {
// expected input parameters not found
return ctx.createFailureStatus();
}
}
}
return ctx.createSuccessStatus();
}
/**
* A selection behavior has one input parameter and one output parameter.
* The input parameter must be a bag of elements of the same type as the
* object node or a supertype of the type of object node. The output
* parameter must be the same or a subtype of the type of object node. The
* behavior cannot have side effects.
*
* @param context
* The receiving '<em><b>Object Node</b></em>' model object.
* @param ctx
* The cache of context-specific information.
*/
public static IStatus validateInputOutputParameter(ObjectNode context, IValidationContext ctx) {
Behavior selection = context.getSelection();
if(selection != null) {
Parameter inParam = null;
Parameter outParam = null;
for(Parameter param : selection.getOwnedParameters()) {
switch(param.getDirection()) {
case IN_LITERAL:
if(inParam != null) {
// second input found
return ctx.createFailureStatus();
}
inParam = param;
break;
case OUT_LITERAL:
case RETURN_LITERAL:
if(outParam != null) {
// second output found
return ctx.createFailureStatus();
}
outParam = param;
break;
case INOUT_LITERAL:
if(inParam != null) {
// second input found
return ctx.createFailureStatus();
}
inParam = param;
if(outParam != null) {
// second output found
return ctx.createFailureStatus();
}
outParam = param;
break;
}
}
if(inParam == null || outParam == null) {
// missing a parameter
return ctx.createFailureStatus();
}
// check type compatibility
if(!isSuperType(inParam.getType(), context.getType())) {
return ctx.createFailureStatus();
}
if(!isSuperType(context.getType(), outParam.getType())) {
return ctx.createFailureStatus();
}
}
return ctx.createSuccessStatus();
}
/**
* Check that type is compatible with the first one as parent
*
* @param superType
* the type which should be supertype
* @param childType
* the type which should be at lower level
* @return true if superType is a supertype of childType or if one of them
* is null
*/
private static boolean isSuperType(Type superType, Type childType) {
if(superType == null || childType == null) {
return true;
}
return childType.conformsTo(superType);
}
/**
* Get all object nodes which are downstream this object flow. These are the
* target of the object flow, eventually by the intermediate of control
* nodes.
*
* @param objectFlow
* the object flow to explore
* @return list of object nodes downstream
*/
private static List<ObjectNode> getDownStreamObjectNodes(ObjectFlow objectFlow) {
ActivityNode target = objectFlow.getTarget();
if(target instanceof ObjectNode) {
return Collections.singletonList((ObjectNode)target);
} else if(target instanceof ControlNode) {
List<ObjectNode> result = new LinkedList<ObjectNode>();
for(ActivityEdge outgoingEdge : target.getOutgoings()) {
if(outgoingEdge instanceof ObjectFlow) {
result.addAll(getDownStreamObjectNodes((ObjectFlow)outgoingEdge));
}
}
return result;
}
return Collections.emptyList();
}
/**
* Get the type which should be expected upstream an object flow
* (considering the source)
*
* @param objectFlow
* the object flow
* @param alreadyMetObjectFlows
* the list of object flows which have already been visited to
* avoid loops. Callers shall pass new LinkedList<ObjectFlow>()
* @return the list of types according to different incoming flows
* (unspecified types omitted).
*/
private static List<Type> getUpstreamExpectedTypes(ObjectFlow objectFlow, List<ObjectFlow> alreadyMetObjectFlows) {
ActivityNode src = objectFlow.getSource();
// handle loops
if(alreadyMetObjectFlows.contains(objectFlow)) {
// We are engaged on a loop of object flows. Break it now.
return Collections.emptyList();
} else {
alreadyMetObjectFlows.add(objectFlow);
}
// recover incoming types
if(src instanceof ObjectNode) {
// type of source object node
Type type = ((ObjectNode)src).getType();
if(type != null) {
return Collections.singletonList(type);
} else {
return Collections.emptyList();
}
} else if(src instanceof ControlNode) {
// type coming to the control node from object flows
List<Type> result = new LinkedList<Type>();
for(ActivityEdge incomingEdge : src.getIncomings()) {
if(incomingEdge instanceof ObjectFlow) {
ObjectFlow incomingFlow = (ObjectFlow)incomingEdge;
// get the types the incoming flow sends
result.addAll(getTypeComingFromFlow(incomingFlow, alreadyMetObjectFlows));
}
}
return result;
}
return Collections.emptyList();
}
/**
* Get the type which should be expected downstream an object flow
* (considering the target)
*
* @param objectFlow
* the object flow
* @param alreadyMetObjectFlows
* the list of object flows which have already been visited to
* avoid loops. Callers shall pass new LinkedList<ObjectFlow>()
* @return the list of types according to different outgoing flows
* (unspecified types omitted).
*/
private static List<Type> getDownstreamExpectedTypes(ObjectFlow objectFlow, List<ObjectFlow> alreadyMetObjectFlows) {
ActivityNode target = objectFlow.getTarget();
// handle loops
if(alreadyMetObjectFlows.contains(objectFlow)) {
// We are engaged on a loop of object flows. Break it now.
return Collections.emptyList();
} else {
alreadyMetObjectFlows.add(objectFlow);
}
// recover outgoing types
if(target instanceof ObjectNode) {
// type of target object node
Type type = ((ObjectNode)target).getType();
if(type != null) {
return Collections.singletonList(type);
} else {
return Collections.emptyList();
}
} else if(target instanceof ControlNode) {
// type coming to the control node from object flows
List<Type> result = new LinkedList<Type>();
for(ActivityEdge outgoingEdge : target.getOutgoings()) {
if(outgoingEdge instanceof ObjectFlow) {
ObjectFlow outgoingFlow = (ObjectFlow)outgoingEdge;
// get the types the outgoing flow expects
result.addAll(getTypeExpectedByFlow(outgoingFlow, alreadyMetObjectFlows));
}
}
return result;
}
return Collections.emptyList();
}
/**
* Get the types which an object flow sends (considering itself and its
* source)
*
* @param inputFlow
* the object flow
* @param alreadyMetObjectFlows
* the list of object flows which have already been visited to
* avoid loops. Callers shall pass new LinkedList<ObjectFlow>()
* @return the list of types according to different incoming flows
* (unspecified types omitted).
*/
private static List<Type> getTypeComingFromFlow(ObjectFlow inputFlow, List<ObjectFlow> alreadyMetObjectFlows) {
List<Type> result = new LinkedList<Type>();
if(inputFlow.getTransformation() == null && inputFlow.getSelection() == null) {
// type coming from other object flows' sources
result.addAll(getUpstreamExpectedTypes(inputFlow, alreadyMetObjectFlows));
} else if(inputFlow.getTransformation() != null) {
// type coming from other object flows' transformation behavior
for(Parameter transfParam : inputFlow.getTransformation().getOwnedParameters()) {
switch(transfParam.getDirection()) {
case IN_LITERAL:
break;
case OUT_LITERAL:
case RETURN_LITERAL:
case INOUT_LITERAL:
if(transfParam.getType() != null) {
result.add(transfParam.getType());
}
break;
}
}
} else if(inputFlow.getSelection() != null) {
// type coming from other object flows' selection behavior
for(Parameter selParam : inputFlow.getSelection().getOwnedParameters()) {
switch(selParam.getDirection()) {
case IN_LITERAL:
break;
case OUT_LITERAL:
case RETURN_LITERAL:
case INOUT_LITERAL:
if(selParam.getType() != null) {
result.add(selParam.getType());
}
break;
}
}
}
return result;
}
/**
* Get the types which an object flow handles (considering itself and its
* target)
*
* @param outputFlow
* the object flow
* @param alreadyMetObjectFlows
* the list of object flows which have already been visited to
* avoid loops. Callers shall pass new LinkedList<ObjectFlow>()
* @return the list of types according to different outgoing flows
* (unspecified types omitted).
*/
private static List<Type> getTypeExpectedByFlow(ObjectFlow outputFlow, List<ObjectFlow> alreadyMetObjectFlows) {
List<Type> result = new LinkedList<Type>();
if(outputFlow.getTransformation() == null) {
// type coming from other object flows' targets
result.addAll(getDownstreamExpectedTypes(outputFlow, alreadyMetObjectFlows));
} else {
// type coming from other object flows' transformation behavior
for(Parameter transfParam : outputFlow.getTransformation().getOwnedParameters()) {
switch(transfParam.getDirection()) {
case IN_LITERAL:
case INOUT_LITERAL:
if(transfParam.getType() != null) {
result.add(transfParam.getType());
}
break;
case OUT_LITERAL:
case RETURN_LITERAL:
break;
}
}
}
return result;
}
enum Direction {
IN, OUT
};
/**
* Validate Call Operation Action,
* the validity of the parameters with pins
*
* @param action
* @param ctx
* @return OK_STATUS if paramters and pins are synchronised
*/
@PinAndParameterSynchronizeValidator
public static IStatus validateCallOperation(CallOperationAction action, IValidationContext ctx) {
if(action.getOperation() == null) {
return ctx.createFailureStatus(String.format("%s does not have operation", action.getName()));
}
// in check
List<Parameter> ins = getParameters(action.getOperation(), Direction.IN);
EList<InputPin> inputs = action.getArguments();
if(ins.size() != inputs.size()) {
return ctx.createFailureStatus(String.format("pins of %s does not have the same number of input pins as input parameters of the operation %s", action.getName(), action.getOperation().getName()));
}
int index = 0;
for(Parameter p : ins) {
IStatus status = validatePin(index, p, inputs, ctx);
if(!status.isOK()) {
return status;
}
index++;
}
// out check
List<Parameter> outs = getParameters(action.getOperation(), Direction.OUT);
int indexOuts = 0;
EList<OutputPin> outputs = action.getOutputs();
if(outs.size() != outputs.size()) {
return ctx.createFailureStatus(String.format("pins of %s does not have the same number of output pins as output parameters of the operation %s", action.getName(), action.getOperation().getName()));
}
for(Parameter p : outs) {
IStatus status = validatePin(indexOuts, p, outputs, ctx);
if(!status.isOK()) {
return status;
}
indexOuts++;
}
// chic type check
return Status.OK_STATUS;
}
private static IStatus validatePin(int index, Parameter p, EList<? extends Pin> inputs, IValidationContext ctx) {
Pin pin = inputs.get(index);
for(EStructuralFeature a : pin.eClass().getEAllStructuralFeatures()) {
EStructuralFeature feature = getFeature(a.getName(), p.eClass());
if(!a.isDerived() && a.isChangeable() && feature != null) {
if(!pin.eGet(a).equals(p.eGet(feature))) {
return ctx.createFailureStatus(String.format("attribute %s and attribute %s are different for pin %s", a.getName(), feature.getName(), pin.getName()));
}
}
}
// check type
if((pin.getType() == null || p.getType() == null) && p.getType() != pin.getType()) {
return ctx.createFailureStatus(String.format("type of pin %s is different the parameter %s", pin.getName(), p.getName()));
}
if(pin.getType() != null && !pin.getType().conformsTo(p.getType())) {
return ctx.createFailureStatus(String.format("type of pin %s is not compatible with the parameter %s", pin.getName(), p.getName()));
}
return Status.OK_STATUS;
}
private static EStructuralFeature getFeature(String name, EClass eclass) {
for(EStructuralFeature a : eclass.getEAllAttributes()) {
if(a.getName() != null && a.getName().equals(name)) {
return a;
}
}
return null;
}
private static List<Parameter> getParameters(Operation operation, Direction theDirection) {
List<Parameter> parameters = new ArrayList<Parameter>(operation.getOwnedParameters().size());
for(Parameter p : operation.getOwnedParameters()) {
if(theDirection == Direction.IN) {
if(p.getDirection() == ParameterDirectionKind.IN_LITERAL || p.getDirection() == ParameterDirectionKind.INOUT_LITERAL) {
parameters.add(p);
}
} else if(theDirection == Direction.OUT) {
if(p.getDirection() == ParameterDirectionKind.OUT_LITERAL || p.getDirection() == ParameterDirectionKind.INOUT_LITERAL || p.getDirection() == ParameterDirectionKind.RETURN_LITERAL) {
parameters.add(p);
}
}
}
return parameters;
}
}