ValidationJavaTranslator.java
package org.andromda.translation.ocl.validation;
import java.io.InputStream;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Stack;
import org.andromda.core.engine.ModelProcessorException;
import org.andromda.core.translation.TranslationUtils;
import org.andromda.metafacades.uml.ModelElementFacade;
import org.andromda.translation.ocl.BaseTranslator;
import org.andromda.translation.ocl.node.*;
import org.andromda.translation.ocl.syntax.ConcreteSyntaxUtils;
import org.andromda.translation.ocl.syntax.OCLFeatures;
import org.andromda.translation.ocl.syntax.OCLPatterns;
import org.apache.commons.lang.StringUtils;
/**
* <p/>
* Provides translation of OCL validation constraints to the Java language. </p>
*
* @author Wouter Zoons
* @author Chad Brandon
*/
public class ValidationJavaTranslator
extends BaseTranslator
{
private static Properties features = null;
static
{
try
{
URL featuresUri = ValidationJavaTranslator.class.getResource("features.properties");
if (featuresUri == null)
{
throw new ModelProcessorException("Could not load file --> '" + featuresUri + '\'');
}
features = new Properties();
InputStream stream = featuresUri.openStream();
features.load(stream);
stream.close();
stream = null;
}
catch (final Throwable throwable)
{
throw new ValidationTranslatorException(throwable);
}
}
/**
* The package to which the OCL translator classes belong.
*/
private static final String OCL_TRANSLATOR_PACKAGE = "org.andromda.translation.ocl.validation";
/**
* This is the start of a new constraint. We prepare everything by resetting and initializing the required objects.
* @param node
*/
public void caseAContextDeclaration(AContextDeclaration node)
{
newTranslationLayer();
{
Object[] temp = node.getContextDeclaration().toArray();
for (int ctr = 0; ctr < temp.length; ctr++)
{
((PContextDeclaration) temp[ctr]).apply(this);
}
}
mergeTranslationLayers();
this.getExpression().appendToTranslatedExpression(translationLayers.peek());
translationLayers.clear();
}
/**
* @see org.andromda.translation.ocl.analysis.DepthFirstAdapter#caseAClassifierContextDeclaration(org.andromda.translation.ocl.node.AClassifierContextDeclaration)
*/
public void caseAClassifierContextDeclaration(AClassifierContextDeclaration node)
{
// explicitly call super method so
// that we can set the type of the expression
super.inAClassifierContextDeclaration(node);
Object[] temp = node.getClassifierExpressionBody().toArray();
for (int ctr = 0; ctr < temp.length; ctr++)
((PClassifierExpressionBody) temp[ctr]).apply(this);
}
/**
* @see org.andromda.translation.ocl.analysis.DepthFirstAdapter#caseAOperationContextDeclaration(org.andromda.translation.ocl.node.AOperationContextDeclaration)
*/
public void caseAOperationContextDeclaration(AOperationContextDeclaration node)
{
// explicitly call super method so
// that we can set the type of the expression
super.inAOperationContextDeclaration(node);
Object[] temp = node.getOperationExpressionBody().toArray();
for (int ctr = 0; ctr < temp.length; ctr++)
((POperationExpressionBody) temp[ctr]).apply(this);
}
/**
* @see org.andromda.translation.ocl.analysis.DepthFirstAdapter#caseAAttributeOrAssociationContextDeclaration(org.andromda.translation.ocl.node.AAttributeOrAssociationContextDeclaration)
*/
public void caseAAttributeOrAssociationContextDeclaration(AAttributeOrAssociationContextDeclaration node)
{
super.inAAttributeOrAssociationContextDeclaration(node);
Object[] temp = node.getAttributeOrAssociationExpressionBody().toArray();
for (int ctr = 0; ctr < temp.length; ctr++)
((PAttributeOrAssociationExpressionBody) temp[ctr]).apply(this);
}
/**
* @see org.andromda.translation.ocl.analysis.DepthFirstAdapter#caseAInvClassifierExpressionBody(org.andromda.translation.ocl.node.AInvClassifierExpressionBody)
*/
public void caseAInvClassifierExpressionBody(AInvClassifierExpressionBody node)
{
// explicitly call super method so
// that we can set the type of the expression
super.inAInvClassifierExpressionBody(node);
node.getExpression().apply(this);
}
/**
* @see org.andromda.translation.ocl.analysis.DepthFirstAdapter#caseADefClassifierExpressionBody(org.andromda.translation.ocl.node.ADefClassifierExpressionBody)
*/
public void caseADefClassifierExpressionBody(ADefClassifierExpressionBody node)
{
// explicitly call super method so
// that we can set the type of the expression
super.inADefClassifierExpressionBody(node);
node.getDefinitionExpression().apply(this);
}
/**
* We need to keep track that what follows is in the scope of an arrow feature call, this is important because it
* means it is a feature that is implied by the OCL language, rather than the model on which the constraint
* applies.
* @param node
*/
public void inAArrowPropertyCallExpressionTail(AArrowPropertyCallExpressionTail node)
{
this.arrowPropertyCallStack.push(Boolean.TRUE);
}
/**
* Undo the arrow feature call trace.
* @param node
*/
public void outAArrowPropertyCallExpressionTail(AArrowPropertyCallExpressionTail node)
{
this.arrowPropertyCallStack.pop();
}
/**
* This indicates we have entered a feature call, we need to mark this to counterpart any previous arrow feature
* call flags.
* @param node
*/
public void inADotPropertyCallExpressionTail(ADotPropertyCallExpressionTail node)
{
this.arrowPropertyCallStack.push(Boolean.FALSE);
}
/**
* Undo the dot feature call trace.
* @param node
*/
public void outADotPropertyCallExpressionTail(ADotPropertyCallExpressionTail node)
{
this.arrowPropertyCallStack.pop();
}
/**
* Here we need to make sure the equals sign '=' is not translated into the 'equal' keyword. OCL uses '=' for
* comparison as well as for assignment, Java uses '==', '=' and .equals() so we override the default OCL value here
* to use '=' instead of 'equal'
* @param node
*/
public void caseALetVariableDeclaration(ALetVariableDeclaration node)
{
inALetVariableDeclaration(node);
if (node.getVariableDeclaration() != null)
{
node.getVariableDeclaration().apply(this);
}
if (node.getEqual() != null)
{
write("=");
}
if (node.getExpression() != null)
{
node.getExpression().apply(this);
}
outALetVariableDeclaration(node);
}
/**
* Add a variable to the context.
* @param node
*/
public void inALetVariableDeclaration(ALetVariableDeclaration node)
{
newTranslationLayer(); // this layer will be disposed later on, we do
// not write variable declarations
AVariableDeclaration variableDeclaration = (AVariableDeclaration) node.getVariableDeclaration();
String variableName = variableDeclaration.getName().getText();
newTranslationLayer();
node.getExpression().apply(this);
String variableValue = translationLayers.pop().toString();
addLetVariableToContext(variableName, variableValue);
}
/**
* In Java we need to end the declaration statement with a semicolon, this is handled here.
* @param node
*/
public void outALetVariableDeclaration(ALetVariableDeclaration node)
{
write(";");
translationLayers.pop();
}
/**
* Renders a variable declaration. Missing types will imply the Object type.
* @param node
*/
public void caseAVariableDeclaration(AVariableDeclaration node)
{
if (node.getTypeDeclaration() == null)
write("Object");
else
node.getTypeDeclaration().apply(this);
write(" "); // we need to add a space between the type and the name
node.getName().apply(this);
}
/**
* @see org.andromda.translation.ocl.analysis.DepthFirstAdapter#caseATypeDeclaration(org.andromda.translation.ocl.node.ATypeDeclaration)
*/
public void caseATypeDeclaration(ATypeDeclaration node)
{
node.getType().apply(this);
}
/**
* @see org.andromda.translation.ocl.analysis.DepthFirstAdapter#caseAVariableDeclarationList(org.andromda.translation.ocl.node.AVariableDeclarationList)
*/
public void caseAVariableDeclarationList(AVariableDeclarationList node)
{
node.getVariableDeclaration().apply(this);
if (node.getVariableDeclarationValue() != null)
node.getVariableDeclarationValue().apply(this);
Object[] temp = node.getVariableDeclarationListTail().toArray();
for (int ctr = 0; ctr < temp.length; ctr++)
((PVariableDeclarationListTail) temp[ctr]).apply(this);
}
/**
* @see org.andromda.translation.ocl.analysis.DepthFirstAdapter#caseAVariableDeclarationListTail(org.andromda.translation.ocl.node.AVariableDeclarationListTail)
*/
public void caseAVariableDeclarationListTail(AVariableDeclarationListTail node)
{
node.getComma().apply(this);
node.getVariableDeclaration().apply(this);
if (node.getVariableDeclarationValue() != null)
node.getVariableDeclarationValue().apply(this);
}
/**
* @see org.andromda.translation.ocl.analysis.DepthFirstAdapter#caseAEqualExpression(org.andromda.translation.ocl.node.AEqualExpression)
*/
public void caseAEqualExpression(AEqualExpression node)
{
node.getEqual().apply(this);
node.getExpression().apply(this);
}
/**
* @see org.andromda.translation.ocl.analysis.DepthFirstAdapter#caseABodyOperationStereotype(org.andromda.translation.ocl.node.ABodyOperationStereotype)
*/
public void caseABodyOperationStereotype(ABodyOperationStereotype node)
{
}
/**
* @see org.andromda.translation.ocl.analysis.DepthFirstAdapter#caseAPreOperationStereotype(org.andromda.translation.ocl.node.APreOperationStereotype)
*/
public void caseAPreOperationStereotype(APreOperationStereotype node)
{
}
/**
* @see org.andromda.translation.ocl.analysis.DepthFirstAdapter#caseAPostOperationStereotype(org.andromda.translation.ocl.node.APostOperationStereotype)
*/
public void caseAPostOperationStereotype(APostOperationStereotype node)
{
}
/**
* @see org.andromda.translation.ocl.analysis.DepthFirstAdapter#caseAMessageExpression(org.andromda.translation.ocl.node.AMessageExpression)
*/
public void caseAMessageExpression(AMessageExpression node)
{
}
/**
* @see org.andromda.translation.ocl.analysis.DepthFirstAdapter#caseAIfExpression(org.andromda.translation.ocl.node.AIfExpression)
*/
public void caseAIfExpression(AIfExpression node)
{
node.getIf().apply(this);
write("(");
node.getIfBranch().apply(this);
write(")");
node.getThen().apply(this);
write("{");
node.getThenBranch().apply(this);
write(";");
write("}");
node.getElse().apply(this);
write("{");
node.getElseBranch().apply(this);
write(";");
write("}");
}
/**
* @see org.andromda.translation.ocl.analysis.DepthFirstAdapter#caseAPropertyCallExpression(org.andromda.translation.ocl.node.APropertyCallExpression)
*/
public void caseAPropertyCallExpression(APropertyCallExpression node)
{
newTranslationLayer();
node.getPrimaryExpression().apply(this);
Object[] temp = node.getPropertyCallExpressionTail().toArray();
for (int ctr = 0; ctr < temp.length; ctr++)
((PPropertyCallExpressionTail) temp[ctr]).apply(this);
mergeTranslationLayerAfter();
}
/**
* @see org.andromda.translation.ocl.analysis.DepthFirstAdapter#caseADotPropertyCallExpressionTail(org.andromda.translation.ocl.node.ADotPropertyCallExpressionTail)
*/
public void caseADotPropertyCallExpressionTail(ADotPropertyCallExpressionTail node)
{
inADotPropertyCallExpressionTail(node);
String expression = TranslationUtils.trimToEmpty(node);
// we prepend an introspection call if the expression is
// an operation call
if (OCLPatterns.isOperation(expression))
{
AFeatureCall featureCall = (AFeatureCall) node.getFeatureCall();
String featureCallExpression = TranslationUtils.trimToEmpty(node.getFeatureCall());
if (OCLFeatures.isOclIsKindOf(featureCallExpression))
{
this.handleOclIsKindOf(featureCall);
} else if (OCLFeatures.isOclIsTypeOf(featureCallExpression))
{
this.handleOclIsTypeOf(featureCall);
} else if (OCLFeatures.isConcat(featureCallExpression))
{
this.handleConcat(featureCall);
} else
{
this.handleDotFeatureCall(featureCall);
}
}
outADotPropertyCallExpressionTail(node);
}
/**
* oclIsKindOf(type) is a special feature defined by OCL on all objects.
*/
private void handleOclIsKindOf(Object node)
{
String type = this.getParametersAsType(node);
if (type != null)
{
write(" instanceof ");
write(type);
}
}
/**
* oclIsTypeOf(type) is a special feature defined by OCL on all objects.
*/
private void handleOclIsTypeOf(Object node)
{
String type = this.getParametersAsType(node);
if (type != null)
{
write(".getClass().getName().equals(");
write(type);
write(".class.getName())");
}
}
/**
* Extracts the parameters from the given <code>node</code> and returns the parameters as a type (or null if none
* can be extracted).
*
* @param node the node from which to extrac the parameters
* @return the fully qualified type name.
*/
private String getParametersAsType(Object node)
{
String type = null;
if (node instanceof AFeatureCall)
{
type = ConcreteSyntaxUtils.getParametersAsString((AFeatureCall) node);
} else if (node instanceof AFeatureCallParameters)
{
type = ConcreteSyntaxUtils.getParametersAsString((AFeatureCallParameters) node);
}
if (type != null)
{
type = type.replaceAll("\\s*::\\s*", ".");
// if we don't have a package define, attempt to find the model
// element
// in the same package as the context element.
if (type.indexOf('.') == -1)
{
if (this.getModelElement() != null)
{
type = this.getModelElement().getPackageName() + '.' + type;
}
}
}
return type;
}
/**
* contact(string) is a special feature defined by OCL on strings.
*/
private void handleConcat(AFeatureCall featureCall)
{
write(" + \"\" + ");
write(OCL_INTROSPECTOR_INVOKE_PREFIX);
write(CONTEXT_ELEMENT_NAME);
write(",\"");
write(ConcreteSyntaxUtils.getParametersAsString(featureCall).replaceAll("\\s*", ""));
write("\")");
}
/**
* Handles a <strong>dot </strong> feature call. Its expected that this <code>featureCall</code>'s parent is a
* ADotPropertyCallExpressionTail. This is here because dot feature calls must be handled differently than
* <code>arrow<code> feature calls.
*
* @param featureCall the <strong>dot</strong> <code>featureCall</code> to handle.
*/
public void handleDotFeatureCall(AFeatureCall featureCall)
{
this.prependToTranslationLayer(OCL_INTROSPECTOR_INVOKE_PREFIX);
this.appendToTranslationLayer(",\"");
this.appendToTranslationLayer(TranslationUtils.deleteWhitespace(featureCall));
this.appendToTranslationLayer("\"");
if (featureCall.getFeatureCallParameters() != null)
{
List parameters = ConcreteSyntaxUtils.getParameters(featureCall);
if (parameters != null && !parameters.isEmpty())
{
write(",new Object[]{");
this.appendToTranslationLayer(OCL_INTROSPECTOR_INVOKE_PREFIX);
this.appendToTranslationLayer(CONTEXT_ELEMENT_NAME);
this.appendToTranslationLayer(",\"");
this.appendToTranslationLayer(ConcreteSyntaxUtils.getParameters(featureCall).get(0));
this.appendToTranslationLayer("\")}");
}
}
this.appendToTranslationLayer(")");
}
/**
* @see org.andromda.translation.ocl.analysis.DepthFirstAdapter#caseAArrowPropertyCallExpressionTail(org.andromda.translation.ocl.node.AArrowPropertyCallExpressionTail)
*/
public void caseAArrowPropertyCallExpressionTail(AArrowPropertyCallExpressionTail node)
{
inAArrowPropertyCallExpressionTail(node);
node.getArrow().apply(this);
this.handleArrowFeatureCall((AFeatureCall) node.getFeatureCall());
outAArrowPropertyCallExpressionTail(node);
}
/**
* @see org.andromda.translation.ocl.BaseTranslator#isOperationArgument(String)
*/
protected boolean isOperationArgument(String argument)
{
return super.isOperationArgument(this.getRootName(argument));
}
/**
* Gets the root path name from the given <code>navigationalPath</code> (by trimming off any additional navigational
* path).
*
* @param navigationalPath the navigational property path (i.e. car.door)
* @return the root of the path name.
*/
private String getRootName(String navigationalPath)
{
return StringUtils.trimToEmpty(navigationalPath).replaceAll("\\..*", "");
}
/**
* Gets the tail of the navigational path (that is it removes the root from the path and returns the tail).
*
* @param navigationalPath the navigational property path (i.e. car.door)
* @return the tail of the path name.
*/
private String getPathTail(String navigationalPath)
{
final int dotIndex = navigationalPath.indexOf('.');
return dotIndex != -1 ? navigationalPath.substring(dotIndex + 1, navigationalPath.length()) : navigationalPath;
}
/**
* TODO: improve implementation to reduce the code duplication (avoid having two write statements)
* @param node
*/
public void caseAFeaturePrimaryExpression(AFeaturePrimaryExpression node)
{
inAFeaturePrimaryExpression(node);
if (node.getPathName() != null)
{
final String variableName = ((APathName) node.getPathName()).getName().getText();
final String variableValue = getDeclaredLetVariableValue(variableName);
final boolean isDeclaredAsLetVariable = (variableValue != null);
String featureExpression = TranslationUtils.deleteWhitespace(node);
if (isDeclaredAsLetVariable)
{
write(variableValue);
} else if (node.getFeatureCallParameters() == null || OCLPatterns.isOperation(featureExpression))
{
APropertyCallExpression expression = (APropertyCallExpression) node.parent();
String expressionAsString = ConcreteSyntaxUtils.getPrimaryExpression(expression);
// remove any references to 'self.' as we write
expressionAsString = expressionAsString.replaceAll("self\\.", "");
if (OCLFeatures.isSelf(expressionAsString))
{
write(CONTEXT_ELEMENT_NAME);
} else if (StringUtils.isNotBlank(expressionAsString))
{
boolean convertToBoolean = false;
if (node.parent().parent() instanceof AUnaryExpression)
{
AUnaryExpression unaryExpression = (AUnaryExpression) node.parent().parent();
// we convert each unary not expression to boolean
convertToBoolean = unaryExpression.getUnaryOperator() instanceof ANotUnaryOperator;
if (convertToBoolean)
{
this.write(BOOLEAN_WRAP_PREFIX);
}
}
if (OCLFeatures.isOclIsKindOf(expressionAsString))
{
this.write("object");
this.handleOclIsKindOf(node.getFeatureCallParameters());
} else if (OCLFeatures.isOclIsTypeOf(expressionAsString))
{
this.write("object");
this.handleOclIsTypeOf(node.getFeatureCallParameters());
} else
{
// whether or not an introspector call is required
boolean introspectorCall = true;
String invokedObject = CONTEXT_ELEMENT_NAME;
// if we're in an arrow call we assume the invoked
// object is the object for which the arrow call applies
if (this.arrowPropertyCallStack.peek().equals(Boolean.TRUE))
{
invokedObject = "object";
}
if (this.isOperationArgument(expressionAsString))
{
// - if the expression is an argument, then
// that becomes the invoked object
invokedObject = this.getRootName(expressionAsString);
expressionAsString = this.getPathTail(expressionAsString);
introspectorCall = !invokedObject.equals(expressionAsString);
}
if (introspectorCall)
{
write(OCL_INTROSPECTOR_INVOKE_PREFIX);
}
write(invokedObject);
if (introspectorCall)
{
write(",\"");
write(expressionAsString);
}
if (introspectorCall)
{
write("\")");
}
if (convertToBoolean)
{
this.write(BOOLEAN_WRAP_SUFFIX);
}
}
if (this.requiresBooleanConversion)
{
this.write(BOOLEAN_WRAP_SUFFIX);
this.requiresBooleanConversion = false;
}
}
} else
{
node.getPathName().apply(this);
}
}
if (node.getIsMarkedPre() != null)
{
node.getIsMarkedPre().apply(this);
}
if (node.getQualifiers() != null)
{
// we use introspection when in an arrow, so passing
// feature name as a String without parentheses
if (this.arrowPropertyCallStack.peek().equals(Boolean.FALSE))
{
node.getQualifiers().apply(this);
}
}
outAFeaturePrimaryExpression(node);
}
/**
* Handles an <strong>arrow </strong> feature call. Its expected that this <code>featureCall</code>'s parent is a
* AArrowPropertyCallExpressionTail. This is here because arrow feature calls must be handled differently than
* <code>dot<code> feature calls.
*
* @param featureCall the <strong>arrow</strong> <code>featureCall</code> to handle.
*/
public void handleArrowFeatureCall(AFeatureCall featureCall)
{
AFeatureCallParameters params = (AFeatureCallParameters) featureCall.getFeatureCallParameters();
AActualParameterList list = null;
if (params != null)
{
list = (AActualParameterList) params.getActualParameterList();
}
boolean arrow = this.arrowPropertyCallStack.peek().equals(Boolean.TRUE) &&
StringUtils.isNotBlank(String.valueOf(list));
{
newTranslationLayer();
final String navigationalPath = ConcreteSyntaxUtils.getArrowFeatureCallResultNavigationalPath(
(APropertyCallExpression) featureCall.parent().parent());
// if the result of an arrow feature (collection operation) has a
// navigational
// path, retrieve it and wrap the current expression with an OCL
// introspector call
boolean resultNavigationalPath = StringUtils.isNotBlank(navigationalPath);
if (resultNavigationalPath)
{
write(OCL_INTROSPECTOR_INVOKE_PREFIX);
}
write(OCL_TRANSLATOR_PACKAGE);
write(".OCLCollections.");
inAFeatureCall(featureCall);
if (featureCall.getPathName() != null)
{
featureCall.getPathName().apply(this);
}
String featureCallName = TranslationUtils.trimToEmpty(featureCall.getPathName());
AFeatureCallParameters parameters = (AFeatureCallParameters) featureCall.getFeatureCallParameters();
if (parameters != null)
{
if (parameters.getLParen() != null)
{
parameters.getLParen().apply(this);
}
mergeTranslationLayerBefore();
AActualParameterList parameterList = (AActualParameterList) parameters.getActualParameterList();
if (parameterList != null)
{
List expressions = parameterList.getCommaExpression();
if (parameterList.getExpression() != null)
{
if (arrow)
{
write(",");
write(features.getProperty(featureCallName));
write(" ");
if (OCLPredicateFeatures.isPredicateFeature(featureCallName))
{
write(BOOLEAN_WRAP_PREFIX);
}
}
parameterList.getExpression().apply(this);
}
for (int ctr = 0; ctr < expressions.size(); ctr++)
{
Node expression = (Node) expressions.get(ctr);
if (expression != null)
{
write(",");
expression.apply(this);
}
}
if (parameterList.getExpression() != null)
{
if (OCLPredicateFeatures.isPredicateFeature(featureCallName))
{
write(BOOLEAN_WRAP_SUFFIX);
}
if (arrow)
write(";}}");
}
}
if (parameters.getRParen() != null)
{
parameters.getRParen().apply(this);
}
}
// now since we have a navigational path off of the
// result we need to write the path and close off
// the call to the OCL introspector.
if (resultNavigationalPath)
{
write(",\"");
write(navigationalPath);
write("\"");
write(")");
}
this.outAFeatureCall(featureCall);
}
}
/**
* @see org.andromda.translation.ocl.analysis.DepthFirstAdapter#caseALetExp(org.andromda.translation.ocl.node.ALetExp)
*/
public void caseALetExp(ALetExp node)
{
inALetExp(node);
if (node.getLet() != null)
{
node.getLet().apply(this);
}
if (node.getLetVariableDeclaration() != null)
{
node.getLetVariableDeclaration().apply(this);
}
if (node.getLetExpSub() != null)
{
node.getLetExpSub().apply(this);
}
outALetExp(node);
}
/**
* We are ready to store a new context of variables
* @param node
*/
public void inALetExp(ALetExp node)
{
newLetVariableContext();
}
/**
* The variables are out of scope, we need to purge their context.
* @param node
*/
public void outALetExp(ALetExp node)
{
dropLetVariableContext();
}
/**
* @see org.andromda.translation.ocl.analysis.DepthFirstAdapter#caseAVariableDeclarationLetExpSub(org.andromda.translation.ocl.node.AVariableDeclarationLetExpSub)
*/
public void caseAVariableDeclarationLetExpSub(AVariableDeclarationLetExpSub node)
{
node.getComma().apply(this);
node.getLetVariableDeclaration().apply(this);
node.getLetExpSub().apply(this);
}
/**
* @see org.andromda.translation.ocl.analysis.DepthFirstAdapter#caseALogicalExp(org.andromda.translation.ocl.node.ALogicalExp)
*/
public void caseALogicalExp(ALogicalExp node)
{
newTranslationLayer();
if (node.getRelationalExpression() != null)
{
node.getRelationalExpression().apply(this);
}
Object[] tails = node.getLogicalExpressionTail().toArray();
for (int ctr = 0; ctr < tails.length; ctr++)
{
((ALogicalExpressionTail) tails[ctr]).apply(this);
}
mergeTranslationLayerAfter();
}
/**
* @see org.andromda.translation.ocl.analysis.DepthFirstAdapter#caseALogicalExpressionTail(org.andromda.translation.ocl.node.ALogicalExpressionTail)
*/
public void caseALogicalExpressionTail(ALogicalExpressionTail node)
{
node.getLogicalOperator().apply(this);
if (node.getLogicalOperator() instanceof AImpliesLogicalOperator)
{
prependToTranslationLayer("(");
}
if (node.getRelationalExpression() != null)
{
node.getRelationalExpression().apply(this);
}
if (node.getLogicalOperator() instanceof AImpliesLogicalOperator)
{
write(":true)");
}
}
/**
* @see org.andromda.translation.ocl.analysis.DepthFirstAdapter#caseARelationalExpressionTail(org.andromda.translation.ocl.node.ARelationalExpressionTail)
*/
public void caseARelationalExpressionTail(ARelationalExpressionTail node)
{
inARelationalExpressionTail(node);
newTranslationLayer();
write(OCL_TRANSLATOR_PACKAGE);
write(".OCLExpressions.");
node.getRelationalOperator().apply(this);
write("(");
mergeTranslationLayerBefore();
if (node.getAdditiveExpression() != null)
{
write(",");
node.getAdditiveExpression().apply(this);
}
write(")");
outARelationalExpressionTail(node);
}
/**
* A flag indicating if the expression needs to be converted/wrapped in a Java Boolean instance.
*/
private boolean requiresBooleanConversion = false;
/**
* @see org.andromda.translation.ocl.analysis.DepthFirstAdapter#inARelationalExpression(org.andromda.translation.ocl.node.ARelationalExpression)
*/
public void inARelationalExpression(ARelationalExpression node)
{
// in this block of code, we determine whether or not
// the next appended expression needs to be
// converted/wrapped by a Java Boolean
if (node.getRelationalExpressionTail() == null)
{
Object parent = node.parent();
Object expression = null;
if (parent instanceof ALogicalExp)
{
List tails = ((ALogicalExp) parent).getLogicalExpressionTail();
if (tails != null && !tails.isEmpty())
{
expression = tails.get(0);
// if it's an AND or OR expression set the expression
// to the node
if (OCLPatterns.isAndOrOrExpression(expression))
{
expression = node;
}
}
} else if (parent instanceof ALogicalExpressionTail)
{
expression = node;
}
requiresBooleanConversion = expression != null
&& OCLPatterns.isNavigationalPath(expression)
&& !OCLPatterns.isOperation(node);
if (this.requiresBooleanConversion)
{
this.write(BOOLEAN_WRAP_PREFIX);
}
}
newTranslationLayer();
}
/**
* @see org.andromda.translation.ocl.analysis.DepthFirstAdapter#outARelationalExpression(org.andromda.translation.ocl.node.ARelationalExpression)
*/
public void outARelationalExpression(ARelationalExpression node)
{
mergeTranslationLayerAfter();
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTName(org.andromda.translation.ocl.node.TName)
*/
public void caseTName(TName node)
{
write(node.getText());
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTAnd(org.andromda.translation.ocl.node.TAnd)
*/
public void caseTAnd(TAnd tAnd)
{
write("&&");
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTOr(org.andromda.translation.ocl.node.TOr)
*/
public void caseTOr(TOr tOr)
{
write("||");
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTXor(org.andromda.translation.ocl.node.TXor)
*/
public void caseTXor(TXor tXor)
{
write("^");
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTImplies(org.andromda.translation.ocl.node.TImplies)
*/
public void caseTImplies(TImplies tImplies)
{
// convert any non boolean's to boolean
this.prependToTranslationLayer(BOOLEAN_WRAP_PREFIX);
this.appendToTranslationLayer(BOOLEAN_WRAP_SUFFIX);
write("?");
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTNot(org.andromda.translation.ocl.node.TNot)
*/
public void caseTNot(TNot tNot)
{
write("!");
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTPlus(org.andromda.translation.ocl.node.TPlus)
*/
public void caseTPlus(TPlus tPlus)
{
write("+");
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTMinus(org.andromda.translation.ocl.node.TMinus)
*/
public void caseTMinus(TMinus tMinus)
{
write("-");
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTMult(org.andromda.translation.ocl.node.TMult)
*/
public void caseTMult(TMult tMult)
{
write("*");
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTDiv(org.andromda.translation.ocl.node.TDiv)
*/
public void caseTDiv(TDiv tDiv)
{
write("/");
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTEqual(org.andromda.translation.ocl.node.TEqual)
*/
public void caseTEqual(TEqual tEqual)
{
write("equal");
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTNotEqual(org.andromda.translation.ocl.node.TNotEqual)
*/
public void caseTNotEqual(TNotEqual tNotEqual)
{
write("notEqual");
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTLt(org.andromda.translation.ocl.node.TLt)
*/
public void caseTLt(TLt tLt)
{
write("less");
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTLteq(org.andromda.translation.ocl.node.TLteq)
*/
public void caseTLteq(TLteq tLteq)
{
write("lessOrEqual");
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTGt(org.andromda.translation.ocl.node.TGt)
*/
public void caseTGt(TGt tGt)
{
write("greater");
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTGteq(org.andromda.translation.ocl.node.TGteq)
*/
public void caseTGteq(TGteq tGteq)
{
write("greaterOrEqual");
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTInv(org.andromda.translation.ocl.node.TInv)
*/
public void caseTInv(TInv tInv)
{
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTDef(org.andromda.translation.ocl.node.TDef)
*/
public void caseTDef(TDef tDef)
{
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTLet(org.andromda.translation.ocl.node.TLet)
*/
public void caseTLet(TLet tLet)
{
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTColon(org.andromda.translation.ocl.node.TColon)
*/
public void caseTColon(TColon tColon)
{
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTLBrace(org.andromda.translation.ocl.node.TLBrace)
*/
public void caseTLBrace(TLBrace tlBrace)
{
write("{");
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTLBracket(org.andromda.translation.ocl.node.TLBracket)
*/
public void caseTLBracket(TLBracket tlBracket)
{
write("[");
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTLParen(org.andromda.translation.ocl.node.TLParen)
*/
public void caseTLParen(TLParen tlParen)
{
write("(");
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTRBrace(org.andromda.translation.ocl.node.TRBrace)
*/
public void caseTRBrace(TRBrace trBrace)
{
write("}");
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTRBracket(org.andromda.translation.ocl.node.TRBracket)
*/
public void caseTRBracket(TRBracket trBracket)
{
write("]");
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTRParen(org.andromda.translation.ocl.node.TRParen)
*/
public void caseTRParen(TRParen trParen)
{
write(")");
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTContext(org.andromda.translation.ocl.node.TContext)
*/
public void caseTContext(TContext tContext)
{
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTBoolean(org.andromda.translation.ocl.node.TBoolean)
*/
public void caseTBoolean(TBoolean tBoolean)
{
write(tBoolean.getText());
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTApostrophe(org.andromda.translation.ocl.node.TApostrophe)
*/
public void caseTApostrophe(TApostrophe tApostrophe)
{
write("\'");
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTBlank(org.andromda.translation.ocl.node.TBlank)
*/
public void caseTBlank(TBlank tBlank)
{
write(" ");
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTCollection(org.andromda.translation.ocl.node.TCollection)
*/
public void caseTCollection(TCollection tCollection)
{
write("java.util.Collection ");
}
/**
* @param tSingleLineComment
*/
public void caseTComment(TSingleLineComment tSingleLineComment)
{
write("// ");
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTEndif(org.andromda.translation.ocl.node.TEndif)
*/
public void caseTEndif(TEndif tEndif)
{
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTAttr(org.andromda.translation.ocl.node.TAttr)
*/
public void caseTAttr(TAttr tAttr)
{
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTBag(org.andromda.translation.ocl.node.TBag)
*/
public void caseTBag(TBag tBag)
{
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTBar(org.andromda.translation.ocl.node.TBar)
*/
public void caseTBar(TBar tBar)
{
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTBody(org.andromda.translation.ocl.node.TBody)
*/
public void caseTBody(TBody tBody)
{
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTCommercialAt(org.andromda.translation.ocl.node.TCommercialAt)
*/
public void caseTCommercialAt(TCommercialAt tCommercialAt)
{
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTDerive(org.andromda.translation.ocl.node.TDerive)
*/
public void caseTDerive(TDerive tDerive)
{
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTEndpackage(org.andromda.translation.ocl.node.TEndpackage)
*/
public void caseTEndpackage(TEndpackage tEndpackage)
{
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTEnum(org.andromda.translation.ocl.node.TEnum)
*/
public void caseTEnum(TEnum tEnum)
{
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTIn(org.andromda.translation.ocl.node.TIn)
*/
public void caseTIn(TIn tIn)
{
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTInit(org.andromda.translation.ocl.node.TInit)
*/
public void caseTInit(TInit tInit)
{
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTInt(org.andromda.translation.ocl.node.TInt)
*/
public void caseTInt(TInt tInt)
{
write(tInt.getText());
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTIsSentOperator(org.andromda.translation.ocl.node.TIsSentOperator)
*/
public void caseTIsSentOperator(TIsSentOperator tIsSentOperator)
{
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTMessageOperator(org.andromda.translation.ocl.node.TMessageOperator)
*/
public void caseTMessageOperator(TMessageOperator tMessageOperator)
{
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTNewLine(org.andromda.translation.ocl.node.TNewLine)
*/
public void caseTNewLine(TNewLine tNewLine)
{
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTOper(org.andromda.translation.ocl.node.TOper)
*/
public void caseTOper(TOper tOper)
{
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTOrderedset(org.andromda.translation.ocl.node.TOrderedset)
*/
public void caseTOrderedset(TOrderedset tOrderedset)
{
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTPackage(org.andromda.translation.ocl.node.TPackage)
*/
public void caseTPackage(TPackage tPackage)
{
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTPost(org.andromda.translation.ocl.node.TPost)
*/
public void caseTPost(TPost tPost)
{
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTPre(org.andromda.translation.ocl.node.TPre)
*/
public void caseTPre(TPre tPre)
{
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTArrow(org.andromda.translation.ocl.node.TArrow)
*/
public void caseTArrow(TArrow tArrow)
{
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTIf(org.andromda.translation.ocl.node.TIf)
*/
public void caseTIf(TIf tIf)
{
write("if");
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTElse(org.andromda.translation.ocl.node.TElse)
*/
public void caseTElse(TElse tElse)
{
write("else");
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTThen(org.andromda.translation.ocl.node.TThen)
*/
public void caseTThen(TThen tThen)
{
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTRange(org.andromda.translation.ocl.node.TRange)
*/
public void caseTRange(TRange tRange)
{
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTReal(org.andromda.translation.ocl.node.TReal)
*/
public void caseTReal(TReal tReal)
{
write(tReal.getText());
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTComma(org.andromda.translation.ocl.node.TComma)
*/
public void caseTComma(TComma tComma)
{
write(", ");
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTDot(org.andromda.translation.ocl.node.TDot)
*/
public void caseTDot(TDot tDot)
{
write(".");
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTSemicolon(org.andromda.translation.ocl.node.TSemicolon)
*/
public void caseTSemicolon(TSemicolon tSemicolon)
{
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTUnknown(org.andromda.translation.ocl.node.TUnknown)
*/
public void caseTUnknown(TUnknown tUnknown)
{
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTScopeOperator(org.andromda.translation.ocl.node.TScopeOperator)
*/
public void caseTScopeOperator(TScopeOperator tScopeOperator)
{
write(".");
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTSequence(org.andromda.translation.ocl.node.TSequence)
*/
public void caseTSequence(TSequence tSequence)
{
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTSet(org.andromda.translation.ocl.node.TSet)
*/
public void caseTSet(TSet tSet)
{
}
/**
* @param tStringLit
* TODO: this method very naively replaces every single quote by a double quote, this should be updated
*/
public void caseTStringLit(TStringLit tStringLit)
{
final StringBuffer buffer = new StringBuffer(tStringLit.getText().replace('\'', '\"'));
write(buffer);
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTTab(org.andromda.translation.ocl.node.TTab)
*/
public void caseTTab(TTab tTab)
{
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTTuple(org.andromda.translation.ocl.node.TTuple)
*/
public void caseTTuple(TTuple tTuple)
{
}
/**
* @see org.andromda.translation.ocl.analysis.AnalysisAdapter#caseTTupletype(org.andromda.translation.ocl.node.TTupletype)
*/
public void caseTTupletype(TTupletype tTupletype)
{
}
private final Stack translationLayers = new Stack();
/**
* Contains Boolean.TRUE on the top when the most recent property call was an arrow property call, contains
* Boolean.FALSE otherwise.
*/
private final Stack arrowPropertyCallStack = new Stack();
/**
* This stack contains elements implementing the Map interface. For each definition of variables a new Map element
* will be pushed onto the stack. This element contains the variables defined in the definition.
* <p/>
* The keys and values contained in the Map are the names of the variables only (String instances).
*/
private final Stack letVariableStack = new Stack();
private void write(Object object)
{
appendToTranslationLayer(String.valueOf(object));
}
private StringBuffer newTranslationLayer()
{
return (StringBuffer) translationLayers.push(new StringBuffer());
}
private StringBuffer appendToTranslationLayer(Object appendix)
{
return ((StringBuffer) translationLayers.peek()).append(appendix);
}
private StringBuffer prependToTranslationLayer(Object appendix)
{
return ((StringBuffer) translationLayers.peek()).insert(0, appendix);
}
private StringBuffer mergeTranslationLayerAfter()
{
StringBuffer newTop = null;
if (translationLayers.size() > 1)
{
newTop = appendToTranslationLayer(translationLayers.pop());
}
return newTop;
}
private StringBuffer mergeTranslationLayerBefore()
{
StringBuffer newTop = null;
if (translationLayers.size() > 1)
{
newTop = prependToTranslationLayer(translationLayers.pop());
}
return newTop;
}
private StringBuffer mergeTranslationLayers()
{
while (mergeTranslationLayerAfter() != null) ;
return (StringBuffer) translationLayers.peek();
}
private String getDeclaredLetVariableValue(String variableName)
{
for (final Iterator iterator = letVariableStack.iterator(); iterator.hasNext();)
{
Map variableMap = (Map) iterator.next();
if (variableMap.containsKey(variableName))
{
return (String) variableMap.get(variableName);
}
}
return null;
}
private void newLetVariableContext()
{
letVariableStack.push(new HashMap(4));
}
private void dropLetVariableContext()
{
if (!letVariableStack.isEmpty())
letVariableStack.pop();
}
private void addLetVariableToContext(String variableName, String variableValue)
{
((Map) letVariableStack.peek()).put(variableName, variableValue);
}
/**
* Gets the current context element as a {@link org.andromda.uml.metafacades.ModelElementFacade}.
*
* @return the context element as a model element facade
*/
private ModelElementFacade getModelElement()
{
return (ModelElementFacade) this.getContextElement();
}
/**
*
*/
public ValidationJavaTranslator()
{
arrowPropertyCallStack.push(Boolean.FALSE);
}
/**
* The name of the context element within the translated expression.
*/
private static final String CONTEXT_ELEMENT_NAME = "contextElement";
/**
* The prefix for calling the OCLIntrospector to invoke a property or method on an element.
*/
private static final String OCL_INTROSPECTOR_INVOKE_PREFIX = OCL_TRANSLATOR_PACKAGE + ".OCLIntrospector.invoke(";
/**
* The prefix for converting expressions to boolean expressions
*/
private static final String BOOLEAN_WRAP_PREFIX = "Boolean.valueOf(String.valueOf(";
/**
* The suffix for converting expressions to boolean expressions;
*/
private static final String BOOLEAN_WRAP_SUFFIX = ")).booleanValue()";
/**
* We need to wrap every expression with a converter so that any expressions that return just objects are converted
* to boolean values.
*
* @see org.andromda.translation.ocl.BaseTranslator#postProcess()
*/
@Override
public void postProcess()
{
this.getExpression().insertInTranslatedExpression(0, OCL_TRANSLATOR_PACKAGE + ".OCLResultEnsurer.ensure(");
this.getExpression().insertInTranslatedExpression(0, "boolean constraintValid = ");
this.getExpression().insertInTranslatedExpression(0,
"final Object " + CONTEXT_ELEMENT_NAME + " = this; ");
this.getExpression().appendToTranslatedExpression(");");
}
}