/*******************************************************************************
* 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.Collection;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.emf.ecore.EAnnotation;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.CastExpression;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.ThisExpression;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.search.FieldReferenceMatch;
import ca.uwaterloo.gsd.fsml.core.Cause;
import ca.uwaterloo.gsd.fsml.core.FSMLMappingException;
import ca.uwaterloo.gsd.fsml.core.Mode;
import ca.uwaterloo.gsd.fsml.core.Parameter;
import ca.uwaterloo.gsd.fsml.ecore.FSMLEcoreUtil;
import ca.uwaterloo.gsd.fsml.ecore.FSMLEcoreUtil.NavigationResult;
import ca.uwaterloo.gsd.fsml.javaMappingInterpreter.CodeTransforms;
import ca.uwaterloo.gsd.fsml.javaMappingInterpreter.JavaMappingInterpreter;
import ca.uwaterloo.gsd.fsml.javaMappingInterpreter.OccurrencesFinder;
import ca.uwaterloo.gsd.fsml.javaMappingInterpreter.analysis.ASTUtils;
import ca.uwaterloo.gsd.fsml.javaMappingInterpreter.analysis.JavaModelUtils;
import ca.uwaterloo.gsd.fsml.stats.Stats;
import ca.uwaterloo.gsd.fsml.sync.SyncItem;
public class ArgumentMapping extends JavaMapping {
public ArgumentMapping(EObject element, EStructuralFeature feature, EAnnotation annotation, EClass concreteChildType, JavaMappingInterpreter interpreter, IProgressMonitor progressMonitor) throws FSMLMappingException {
super(element, feature, annotation, concreteChildType, interpreter, progressMonitor);
argument1Index = Integer.parseInt(argument);
argument2Index = Integer.parseInt(sameAsArg);
NavigationResult navigationResult1 = FSMLEcoreUtil.navigateToEObject(element, ofMethodCall);
if (navigationResult1.errorMessage == null)
methodInvocation1 = contextManager.getContextMethodInvocation(navigationResult1.eObject, true, progressMonitor);
else
throw new FSMLMappingException(Cause.INCORRECT_VALUE, JavaMappingInterpreter.DETAIL_OF_METHOD_CALL);
NavigationResult navigationResult2 = FSMLEcoreUtil.navigateToEObject(element, ofCall);
if (navigationResult2.errorMessage == null)
methodInvocation2 = contextManager.getContextMethodInvocation(navigationResult2.eObject, true, progressMonitor);
else
throw new FSMLMappingException(Cause.INCORRECT_VALUE, JavaMappingInterpreter.DETAIL_OF_CALL);
}
public ArgumentMapping(SyncItem syncItem, EAnnotation annotation, JavaMappingInterpreter interpreter, IProgressMonitor progressMonitor) throws FSMLMappingException {
super(syncItem, annotation, interpreter, progressMonitor);
argument1Index = Integer.parseInt(argument);
argument2Index = Integer.parseInt(sameAsArg);
NavigationResult navigationResult1 = FSMLEcoreUtil.navigateToEObject(syncItem, ofMethodCall + "../");
if (navigationResult1.errorMessage == null) {
SyncItem contextSyncItem = (SyncItem) navigationResult1.eObject;
EObject contextElement = contextSyncItem.getCode() != null ? contextSyncItem.getCode() : contextSyncItem.getModel();
methodInvocation1 = contextManager.getContextMethodInvocation(contextElement, true, progressMonitor);
}
else
throw new FSMLMappingException(Cause.INCORRECT_VALUE, JavaMappingInterpreter.DETAIL_OF_METHOD_CALL);
NavigationResult navigationResult2 = FSMLEcoreUtil.navigateToEObject(syncItem, ofCall + "../");
if (navigationResult2.errorMessage == null) {
SyncItem contextSyncItem = (SyncItem) navigationResult2.eObject;
EObject contextElement = contextSyncItem.getCode() != null ? contextSyncItem.getCode() : contextSyncItem.getModel();
methodInvocation2 = contextManager.getContextMethodInvocation(contextElement, true, progressMonitor);
}
else
throw new FSMLMappingException(Cause.INCORRECT_VALUE, JavaMappingInterpreter.DETAIL_OF_CALL);
}
@Parameter(name=JavaMappingInterpreter.DETAIL_ARGUMENT, mode=Mode.ALL, required=true)
public String argument;
int argument1Index;
@Parameter(name=JavaMappingInterpreter.DETAIL_OF_METHOD_CALL, mode=Mode.ALL, required=true)
public String ofMethodCall;
@Parameter(name=JavaMappingInterpreter.DETAIL_SAME_AS_ARG, mode=Mode.ALL, required=true)
public String sameAsArg;
int argument2Index;
@Parameter(name=JavaMappingInterpreter.DETAIL_OF_CALL, mode=Mode.ALL, required=true)
public String ofCall;
MethodInvocation methodInvocation1;
MethodInvocation methodInvocation2;
@Override
protected boolean forward() throws FSMLMappingException {
// variant with 'this'
boolean result = false;
switch (syncItem.getReconciliationAction()) {
case CODE_ADD:
if (methodInvocation1 != null)
result = CodeTransforms.replaceMethodCallArgument(null, methodInvocation1, argument1Index, "this", progressMonitor) != null;
if (methodInvocation2 != null)
result = result && CodeTransforms.replaceMethodCallArgument(null, methodInvocation2, argument2Index, "this", progressMonitor) != null;
if (result) {
// make sure that 'this' implements an appropriate interface
// TODO: cannot rely on bindings since the code hasn't compiled yet!!!
String typeSignature = retrieveTypeOfArgumentFromSignature(syncItem, argument1Index);
String requiredTypeName = Signature.toString(typeSignature);
TypeDeclaration typeDeclaration = (TypeDeclaration) ASTUtils.getAncestorOfType(methodInvocation1, ASTNode.TYPE_DECLARATION);
boolean found = false;
Type superclassType = typeDeclaration.getSuperclassType();
if (superclassType != null) {
String superClassFQName = ASTUtils.getFullyQualifiedName(superclassType);
if (requiredTypeName.equals(superClassFQName))
found = true;
}
else
found = false;
if (!found) {
// check interfaces
for (Object superInterface : typeDeclaration.superInterfaceTypes()) {
if (requiredTypeName.equals(ASTUtils.getFullyQualifiedName((Type) superInterface))) {
found = true;
break;
}
}
}
if (!found) {
try {
IType requiredIType = contextIJavaProject.findType(requiredTypeName);
if (requiredIType.isInterface())
CodeTransforms.addInterfaceDeclaration(contextIJavaProject, null, typeDeclaration, requiredTypeName, progressMonitor);
else if (requiredIType.isClass())
CodeTransforms.addExtendsDeclaration(contextIJavaProject, null, typeDeclaration, requiredTypeName, progressMonitor);
} catch (JavaModelException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
break;
case CODE_REMOVE:
if (methodInvocation1 != null)
result = CodeTransforms.replaceMethodCallArgument(null, methodInvocation1, argument1Index, "null", progressMonitor) != null;
if (methodInvocation2 != null)
result = result && CodeTransforms.replaceMethodCallArgument(null, methodInvocation2, argument2Index, "null", progressMonitor) != null;
break;
}
return result;
}
@Override
protected boolean reverse() throws FSMLMappingException {
// indexes are 1-based in the metamodel and 0-based in the AST
argument1Index--;
argument2Index--;
// get the arguments
Expression argument1 = (Expression) methodInvocation1.arguments().get(argument1Index);
Expression argument2 = (Expression) methodInvocation2.arguments().get(argument2Index);
if (argument1.getNodeType() == ASTNode.CAST_EXPRESSION)
argument1 = ((CastExpression) argument1).getExpression();
if (argument2.getNodeType() == ASTNode.CAST_EXPRESSION)
argument2 = ((CastExpression) argument2).getExpression();
boolean result = false;
if (argument1.getNodeType() == ASTNode.THIS_EXPRESSION &&
argument2.getNodeType() == ASTNode.THIS_EXPRESSION) {
// need to check whether 'this' resolves to the same type
ThisExpression thisExpression1 = (ThisExpression) argument1;
ThisExpression thisExpression2 = (ThisExpression) argument2;
ITypeBinding typeBinding1 = thisExpression1.resolveTypeBinding();
ITypeBinding typeBinding2 = thisExpression2.resolveTypeBinding();
if (typeBinding1 == null || typeBinding2 == null) {
Stats.INSTANCE.logMessage("ArgumentMapping::reverse(): type binding is null. returning false.");
return setFeature(false);
}
if (typeBinding1.isEqualTo(typeBinding2))
result = true;
else {
// need to see whether dynamically resolve to the same type
// TODO:
Stats.INSTANCE.logMessage("ArgumentMapping::reverse(): two 'this' literals with different bindings");
}
}
else if (argument1.getNodeType() == ASTNode.SIMPLE_NAME &&
argument2.getNodeType() == ASTNode.SIMPLE_NAME){
// private field assigned once?
SimpleName simpleName1 = (SimpleName) argument1;
SimpleName simpleName2 = (SimpleName) argument2;
IBinding argument1Binding = simpleName1.resolveBinding();
IBinding argument2Binding = simpleName2.resolveBinding();
if (argument1Binding == null || argument2Binding == null)
return setFeature(false);
if (argument1Binding.isEqualTo(argument2Binding)) {
// refer to the same field or variable
// see how many times it is assigned with non-null.
CompilationUnit compilationUnit = (CompilationUnit) methodInvocation1.getRoot();
// check the initializer first.
VariableDeclarationFragment variableDeclarationFragment = ASTUtils.getVariableDeclarationFragmentNode((IVariableBinding) argument1Binding, compilationUnit);
// return true if the field is private.
FieldDeclaration fieldDeclaration = (FieldDeclaration) variableDeclarationFragment.getParent();
// assume true, because the default visibility is private
result = true;
for (Object modifierObject : fieldDeclaration.modifiers()) {
if (modifierObject instanceof Modifier) {
Modifier modifier = (Modifier) modifierObject;
if (modifier.isPublic() || modifier.isProtected()) {
// See if the field is assigned outside of the context class.
IField iField = (IField) ((IVariableBinding) argument1Binding).getJavaElement();
try {
Collection<FieldReferenceMatch> writeAccesses = JavaModelUtils.writesToField(contextIJavaProject, iField, progressMonitor);
IType fieldTopLevelType = JavaModelUtils.getTopLevelType(iField);
String fieldTopLevelTypeKey = fieldTopLevelType.getKey();
for (FieldReferenceMatch writeAccess : writeAccesses) {
Object matchElement = writeAccess.getElement();
if (matchElement instanceof IJavaElement) {
IJavaElement matchJavaElement = (IJavaElement) matchElement;
IType writeAccessIType = JavaModelUtils.getTopLevelType(matchJavaElement);
String topLevelKey = writeAccessIType.getKey();
if (writeAccessIType != null && !fieldTopLevelTypeKey.equals(topLevelKey))
createMarkerDescriptor(writeAccess, null, "external assignment");
}
}
} catch (JavaModelException e) {
e.printStackTrace();
}
break;
}
}
}
OccurrencesFinder occurrencesFinder = new OccurrencesFinder();
occurrencesFinder.initialize(compilationUnit, variableDeclarationFragment.getName());
occurrencesFinder.performGlobally();
for (ASTNode astNode : occurrencesFinder.getWriteUsages())
createMarkerDescriptor(astNode.getParent(), null, "assignment");
}
}
if (result) {
createMarkerDescriptor(argument1, null, "object");
createMarkerDescriptor(argument2, null, "object");
}
return setFeature(result);
}
}