/*******************************************************************************
* Copyright (c) 2010 Michal Antkiewicz.
* 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:
* Michal Antkiewicz - initial API and implementation
******************************************************************************/
package ca.uwaterloo.gsd.fsml.javaMappingInterpreter.mappings;
import java.util.ArrayList;
import java.util.Iterator;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EAnnotation;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.Statement;
import ca.uwaterloo.gsd.fsml.core.Cause;
import ca.uwaterloo.gsd.fsml.core.Context;
import ca.uwaterloo.gsd.fsml.core.FSMLMappingException;
import ca.uwaterloo.gsd.fsml.core.Mode;
import ca.uwaterloo.gsd.fsml.ecore.FSMLEcoreUtil;
import ca.uwaterloo.gsd.fsml.javaMappingInterpreter.CodeQueries;
import ca.uwaterloo.gsd.fsml.javaMappingInterpreter.CodeTransforms;
import ca.uwaterloo.gsd.fsml.javaMappingInterpreter.JavaMappingInterpreter;
import ca.uwaterloo.gsd.fsml.javaMappingInterpreter.analysis.ASTUtils;
import ca.uwaterloo.gsd.fsml.stats.Stats;
import ca.uwaterloo.gsd.fsml.sync.StructuralFeatureSyncItem;
import ca.uwaterloo.gsd.fsml.sync.SyncItem;
public class ArgumentValueMapping extends ArgumentIs_Mapping {
private StructuralFeatureSyncItem structuralFeatureSyncItem;
public ArgumentValueMapping(EObject element, EStructuralFeature feature, EAnnotation annotation, EClass concreteChildType, JavaMappingInterpreter interpreter, IProgressMonitor progressMonitor) throws FSMLMappingException {
super(element, feature, annotation, concreteChildType, interpreter, progressMonitor);
}
public ArgumentValueMapping(SyncItem syncItem, EAnnotation annotation, JavaMappingInterpreter interpreter, IProgressMonitor progressMonitor) throws FSMLMappingException {
super(syncItem, annotation, interpreter, progressMonitor);
if (syncItem instanceof StructuralFeatureSyncItem)
structuralFeatureSyncItem = (StructuralFeatureSyncItem) syncItem;
else
throw new FSMLMappingException(Cause.INCORRECT_TYPE, "StructuralFeatureSyncItem required.");
}
@Context(mode=Mode.REVERSE, required = true)
public IType contextIType;
@Override
protected boolean forward() throws FSMLMappingException {
Expression call = contextMethodInvocation != null ? contextMethodInvocation : contextClassInstanceCreation;
switch (syncItem.getReconciliationAction()) {
case CODE_ADD:
case CODE_CHANGE:
String type = retrieveTypeOfArgumentFromSignature(syncItem, index);
// remove semi-colon at the end of string if it exists
if (type.charAt(type.length()-1) == ';') {
type = type.substring(0, type.length()-1);
}
EAttribute attribute = (EAttribute) structuralFeatureSyncItem.getStructuralFeature();
// support for multiple values
int numValues = 0;
String[] literalValues = null;
String[] attributeValues = null;
if (attribute.isMany()) {
EList valuesList = (EList) element.eGet(attribute);
numValues = valuesList.size();
literalValues = new String[numValues];
attributeValues = new String[numValues];
for (int i = 0; i < numValues; ++i) {
attributeValues[i] = valuesList.get(i).toString();
}
}
else {
Object singleValue = element.eGet(attribute);
while (singleValue==null){
singleValue = FSMLEcoreUtil.getAttributeValueFromAttributeValueWizard(attribute);
if (singleValue!=null){
element.eSet(attribute, singleValue);
break;
}
}
if (singleValue != null) {
numValues = 1;
literalValues = new String[numValues];
attributeValues = new String[numValues];
attributeValues[0] = singleValue.toString();
}
}
String value = "";
for (int i = 0; i < numValues; ++i) {
if (type.equals("Ljava.lang.String")) {
literalValues[i] = '"' + attributeValues[i] + '"';
} else if (type.equals("Ljava.lang.Class")) {
String fullyQualifiedClassName = attributeValues[i];
literalValues[i] = Signature.getSimpleName(fullyQualifiedClassName) + ".class";
if (CodeTransforms.recordModifications((CompilationUnit)call.getRoot())) {
CodeTransforms.organizeImports(call.getRoot(), fullyQualifiedClassName);
}
} else if (type.equals(Signature.SIG_VOID)) {
literalValues[i] = "";
} else if (type.equals(Signature.SIG_CHAR)) {
literalValues[i] = "'" + attributeValues[i] + "'";
} else if (type.equals(Signature.SIG_DOUBLE)) {
literalValues[i] = attributeValues[i] + "d";
} else if (type.equals(Signature.SIG_FLOAT)) {
literalValues[i] = attributeValues[i] + "f";
} else if (type.equals(Signature.SIG_LONG)) {
literalValues[i] = attributeValues[i] + "L";
} else if (type.equals(Signature.SIG_BOOLEAN) ||
type.equals(Signature.SIG_BYTE) ||
type.equals(Signature.SIG_INT) ||
type.equals(Signature.SIG_SHORT)) {
literalValues[i] = attributeValues[i];
} else {
// user defined class types
literalValues[i] = attributeValues[i];
}
}
if (numValues > 1) {
// variable name is simply the name of the feature
String variableName = attribute.getName();
// cannot use boolean, since "visit" method can only access final variables
final ArrayList<SimpleName> foundVariables = new ArrayList<SimpleName>();
int newID = 0;
// find the closest parent which is a Statement
ASTNode aux = call;
while (!(aux instanceof Statement) && aux != null) {
aux = aux.getParent();
}
while (true) {
foundVariables.clear();
final String potentialUniqueVar = variableName + newID;
call.getRoot().accept(new ASTVisitor() {
public boolean visit(SimpleName simpleName) {
// don't need to keep looking if we already found a duplicate
if (!foundVariables.isEmpty()) return false;
if (simpleName.getIdentifier().equals(potentialUniqueVar)) {
foundVariables.add(simpleName);
}
return true;
}
}
);
if (foundVariables.isEmpty()) {
// this variable identifier is unique
variableName = potentialUniqueVar;
break;
} else {
++newID;
}
}
// insert above the actual method call
CodeTransforms.declareVariable(contextIJavaProject, null, call, variableName, type, literalValues[0], null, false, progressMonitor);
if (numValues > 1)
for (int i = 1; i < numValues; ++i) {
CodeTransforms.assignVariableValue(null, call, variableName, literalValues[i], progressMonitor);
}
// use the variable as the method call argument
value = variableName;
}
else {
// handle the case where the parameter is *NOT* multi-value
value = "";
if (numValues > 0) {
value = literalValues[0];
}
}
return CodeTransforms.replaceMethodCallArgument(null, call, index, value, progressMonitor) != null;
case CODE_REMOVE:
return CodeTransforms.replaceMethodCallArgument(null, call, index, "null", progressMonitor) != null;
}
return false;
}
@Override
protected boolean reverse() throws FSMLMappingException {
if (feature.getEType().getName().equals("EString")) {
Expression argument = null;
if (contextMethodInvocation != null)
argument = (Expression) contextMethodInvocation.arguments().get(index);
else if (contextClassInstanceCreation != null)
argument = (Expression) contextClassInstanceCreation.arguments().get(index);
ArrayList<Expression> valueExpressions = new ArrayList<Expression>();
try {
CodeQueries.getStaticExpressionsForExpression(argument, contextIType, valueExpressions, analysisManagers, progressMonitor);
}
catch (StackOverflowError e) {
Stats.INSTANCE.logError("ArgumentValueMapping::reverse(): Stack overflow for '" + contextIType.getFullyQualifiedName() + "' and argument '" + argument.toString() + "'");
}
// remove null literals
for (Iterator<Expression> i = valueExpressions.iterator(); i.hasNext(); ) {
if (i.next().getNodeType() == ASTNode.NULL_LITERAL)
i.remove();
}
if (valueExpressions != null || !valueExpressions.isEmpty()) {
if (valueExpressions.size() == 1) {
Expression expression = valueExpressions.get(0);
String expressionValue = ASTUtils.getStringValue(expression);
// check equalTo constraint
EAnnotation equalToAnnotation = feature.getEAnnotation("valueEqualsTo");
if (equalToAnnotation != null) {
String featurePath = (String) FSMLEcoreUtil.retrieveParameterValue(equalToAnnotation, JavaMappingInterpreter.QUERY_ATTRIBUTE, true);
// navigate to the String feature that holds the value this feature is expected to be equal to
FSMLEcoreUtil.NavigationResult target = FSMLEcoreUtil.navigateToEObject(element, featurePath);
// TODO: 'valueEqualsTo' constraint: handle all types
String targetValue = (String) target.eObject.eGet(target.eAttribute);
if (!expressionValue.equals(targetValue))
return setFeature(false);
}
return setFeatureContextAndMarker(expressionValue, null, expression, expressionValue, expressionValue);
}
if (feature.isMany()) {
for (Expression expression : valueExpressions) {
String expressionValue = ASTUtils.getStringValue(expression);
setFeatureContextAndMarker(expressionValue, null, expression, expressionValue, expressionValue);
}
}
else {
String value = "";
for (Iterator<Expression> i = valueExpressions.iterator(); i.hasNext(); ) {
Expression expression = i.next();
String expressionValue = ASTUtils.getStringValue(expression);
value += expressionValue + (i.hasNext() ? "', '" : "");
}
for (Expression expression : valueExpressions) {
setFeatureContextAndMarker(value, null, expression, null, ASTUtils.getStringValue(expression));
}
Stats.INSTANCE.logMessage("ArgumentValueMapping:reverse(): [0..1] a feature with multiple values. " + FSMLEcoreUtil.getFSMLId(element, feature, value));
}
return FSMLEcoreUtil.isFeaturePresent(element, feature);
}
Stats.INSTANCE.logImplVariant(element.eClass().getName() + "::" + feature.getName() + " <argumentValue>","Unable to retrieve value");
}
return setFeature(false);
}
}