package com.sap.ide.refactoring.core.reference;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.Collection;
import ngpm.NgpmPackage;
import org.junit.After;
import org.junit.Test;
import behavioral.actions.Block;
import behavioral.actions.NamedValueDeclaration;
import behavioral.actions.Return;
import behavioral.actions.Variable;
import com.sap.ide.cts.editor.AbstractGrammarBasedEditor;
import com.sap.ide.refactoring.core.reference.CompressingReEvaluationLog;
import com.sap.ide.refactoring.core.reference.ReEvaluationInfo;
import com.sap.ide.refactoring.core.reference.ReEvaluationLogger;
import com.sap.ide.refactoring.core.reference.ReEvaluationType;
import com.sap.ide.refactoring.test.RefactoringBaseTest;
import com.sap.mi.textual.parsing.textblocks.reference.GlobalDelayedReferenceResolver;
import com.sap.tc.moin.repository.events.ChangeListener;
import com.sap.tc.moin.repository.events.filter.ConnectionFilter;
import com.sap.tc.moin.repository.events.filter.EventFilter;
import com.sap.tc.moin.repository.events.type.AttributeValueChangeEvent;
import com.sap.tc.moin.repository.events.type.ChangeEvent;
import data.classes.ClassTypeDefinition;
import data.classes.MethodSignature;
import data.classes.NamedValue;
import data.classes.SapClass;
import dataaccess.expressions.MethodCallExpression;
import dataaccess.expressions.VariableExpression;
public class TestGlobalDelayedReferenceResolverIntegration extends RefactoringBaseTest {
private ChangeListener moinListener;
private ReEvaluationLogger reevaluationListener;
public TestGlobalDelayedReferenceResolverIntegration() {
isIntegrationTest = true;
}
private void registerChangeListener(final Collection<ChangeEvent> events) {
moinListener = new ChangeListener() {
@Override
public void notify(final ChangeEvent event) {
events.add(event);
}
};
final EventFilter filter = new ConnectionFilter(connection);
connection.getEventRegistry().registerListener(moinListener, filter);
}
/**
* Setups the global delayed reference resolver and returns a live-log which
* collects all re-evaluated references.
*/
private CompressingReEvaluationLog registerReferencesForReevaluation(SapClass clazz) {
// quick'n'dirty: just open the editor. It will do the registration
AbstractGrammarBasedEditor editor = openEditor(clazz);
getDocument(editor); // finish initialization
close(editor);
CompressingReEvaluationLog log = new CompressingReEvaluationLog();
reevaluationListener = new ReEvaluationLogger(log);
GlobalDelayedReferenceResolver.getInstance().addReferenceResolvingListener(reevaluationListener);
return log;
}
@After
public void tearDownRegisteredListeners() {
if (moinListener != null) {
connection.getEventRegistry().deregister(moinListener);
}
if (reevaluationListener != null) {
GlobalDelayedReferenceResolver.getInstance().removeReferenceResolvingListener(reevaluationListener);
}
}
/**
* Rename a class that is not referenced from anywhere.
* It is assumed, that the delayed reference resolver stays calm...
*
* @throws Exception
*/
@Test
public void testRenamRefactoringEventTriggering() throws Exception {
SapClass clazz = findRunletClass("ClassWithAbstractMethod");
registerReferencesForReevaluation(clazz);
// Change the name twice: Makes sure that we do not log
// side effects (other reevaluations) that happen just because
// there is A change.
clazz.setName("NewName");
ArrayList<ChangeEvent> eventQueue = new ArrayList<ChangeEvent>();
registerChangeListener(eventQueue);
clazz.setName("NewName2");
assertEquals(1, eventQueue.size());
assertTrue(eventQueue.iterator().next() instanceof AttributeValueChangeEvent);
}
@Test
public void testRenameDoesNotBreakExistingReference() {
SapClass clazz = findRunletClass("RedefineParameterTst2");
MethodSignature method = clazz.getOwnedSignatures().iterator().next();
Block block = (Block) method.getImplementation();
NamedValue variable = block.getVariables().iterator().next();
Return statement = (Return) block.getStatements().get(1);
NamedValue returnedVariable = ((VariableExpression) statement.getArgument()).getVariable();
assertEquals("Should bind to the variable (not the method parameter)",
variable, returnedVariable);
registerReferencesForReevaluation(clazz);
ArrayList<ChangeEvent> eventQueue = new ArrayList<ChangeEvent>();
registerChangeListener(eventQueue);
variable.setName("someStrangeNewName");
// We do not expect a re-evaluation. Just an attribute change event for the rename.
assertEquals(1, eventQueue.size());
assertTrue(eventQueue.iterator().next() instanceof AttributeValueChangeEvent);
assertEquals("someStrangeNewName", ((VariableExpression) statement.getArgument()).getVariable().getName());
}
/**
* We remove a variable declaration from a block. We expect that all variable usages
* now use the other variable declaration of the same name in the current scope.
*/
@Test
public void testVariableDeleteTriggersReBind() {
SapClass clazz = findRunletClass("RedefineParameterTst2");
CompressingReEvaluationLog log = registerReferencesForReevaluation(clazz);
MethodSignature method = clazz.getOwnedSignatures().iterator().next();
NamedValue parameter = method.getInput().iterator().next();
Block block = (Block) method.getImplementation();
NamedValueDeclaration variableDeclaration = (NamedValueDeclaration) block.getStatements().get(0);
Return returnStatement = (Return) block.getStatements().get(1);
NamedValue variable = variableDeclaration.getNamedValue();
NamedValue oldReturnedVariable = ((VariableExpression) returnStatement.getArgument()).getVariable();
assertEquals("Should bind to the variable (not the method parameter)",
variable, oldReturnedVariable);
variableDeclaration.refDelete();
// Assert new model state
NamedValue newReturnedVariable = ((VariableExpression) returnStatement.getArgument()).getVariable();
assertEquals("Should rebind to the method parameter)",
parameter, newReturnedVariable);
// Assert re-evalutaion change log
assertEquals("Only the actual variable rebind should have happend", 1, log.getLoggedReEvaluations().size());
ReEvaluationInfo reEvalInfo = log.getLoggedReEvaluations().iterator().next();
assertEquals(ReEvaluationType.REBOUND_TO_OTHER, reEvalInfo.type);
assertEquals(oldReturnedVariable, reEvalInfo.originalValue);
assertEquals(newReturnedVariable, reEvalInfo.currentValue);
}
/**
* We create a new variable declaration, shadowing the existing ones.
* We expect that all variable usages now bind to the new variable.
*/
@Test
public void testVariableCreationTriggersReBind() {
SapClass clazz = findRunletClass("RedefineParameterTst2");
CompressingReEvaluationLog log = registerReferencesForReevaluation(clazz);
MethodSignature method = clazz.getOwnedSignatures().iterator().next();
Block block = (Block) method.getImplementation();
NamedValue variable = block.getVariables().iterator().next();
Return returnStatement = (Return) block.getStatements().get(block.getStatements().size() -1);
NamedValue oldReturnedVariable = ((VariableExpression) returnStatement.getArgument()).getVariable();
assertEquals("Should bind to the variable (not the method parameter)",
variable, oldReturnedVariable);
NgpmPackage rootPkg = connection.getPackage(NgpmPackage.PACKAGE_DESCRIPTOR);
Variable newVariable = (Variable) rootPkg.getBehavioral().getActions().getVariable().refCreateInstance();
newVariable.setName(variable.getName()); // give the name of the currently bound variable
NamedValueDeclaration newVariableDeclaration = (NamedValueDeclaration) rootPkg.getBehavioral().getActions().getNamedValueDeclaration().refCreateInstance();
newVariableDeclaration.setNamedValue(newVariable);
// finally, put the variable into scope
block.getStatements().add(1, newVariableDeclaration);
// Assert the model
NamedValue newReturnedVariable = ((VariableExpression) returnStatement.getArgument()).getVariable();
assertEquals("Should rebind to the newly created variable)",
newVariable, newReturnedVariable);
// Assert re-evalutaion change log
assertEquals("Only the actual variable rebind should have happend", 1, log.getLoggedReEvaluations().size());
ReEvaluationInfo reEvalInfo = log.getLoggedReEvaluations().iterator().next();
assertEquals(ReEvaluationType.REBOUND_TO_OTHER, reEvalInfo.type);
assertEquals(oldReturnedVariable, reEvalInfo.originalValue);
assertEquals(newReturnedVariable, reEvalInfo.currentValue);
}
/**
* We remove a variable declaration from a block and later move it back
* We expect that the log detects that we rebind to the same.
*/
@Test
public void testMoveVariableOutOfScopeAndBackAgain() {
SapClass clazz = findRunletClass("RedefineParameterTst2");
CompressingReEvaluationLog log = registerReferencesForReevaluation(clazz);
MethodSignature method = clazz.getOwnedSignatures().iterator().next();
Block block = (Block) method.getImplementation();
NamedValueDeclaration oldVariableDeclaration = (NamedValueDeclaration) block.getStatements().get(0);
Return returnStatement = (Return) block.getStatements().get(1);
NamedValue variable = oldVariableDeclaration.getNamedValue();
NamedValue oldReturnedVariable = ((VariableExpression) returnStatement.getArgument()).getVariable();
assertEquals("Should bind to the variable (not the method parameter)",
variable, oldReturnedVariable);
// kill the old variableDecl and then add a new; both using the same attribute
oldVariableDeclaration.refDelete();
NgpmPackage rootPkg = connection.getPackage(NgpmPackage.PACKAGE_DESCRIPTOR);
NamedValueDeclaration newVariableDeclaration = (NamedValueDeclaration) rootPkg.getBehavioral().getActions().getNamedValueDeclaration().refCreateInstance();
newVariableDeclaration.setNamedValue((Variable) variable);
block.getStatements().add(0, newVariableDeclaration);
// Assert new model state
NamedValue newReturnedVariable = ((VariableExpression) returnStatement.getArgument()).getVariable();
assertEquals("Should still rebind to the old known variable)",
variable, newReturnedVariable);
// Assert re-evalutaion change log
assertEquals("Only the actual variable rebind should have happend", 1, log.getLoggedReEvaluations().size());
ReEvaluationInfo reEvalInfo = log.getLoggedReEvaluations().iterator().next();
assertEquals(ReEvaluationType.REBOUND_TO_SAME, reEvalInfo.type);
assertEquals(reEvalInfo.currentValue, reEvalInfo.originalValue);
}
/**
* Replace the parameter type String with Number. Number doesn't has a length() method.
* The existing length() call should therefore no longer be bound.
*/
@Test
public void testParameterTypeChangesUnboundsMethodCall() {
SapClass clazz = findRunletClass("MethodCallOutputMultiplicityTest");
CompressingReEvaluationLog log = registerReferencesForReevaluation(clazz);
MethodSignature method = clazz.getOwnedSignatures().iterator().next();
NamedValue parameter = method.getInput().iterator().next();
ClassTypeDefinition typeDefinition = (ClassTypeDefinition) parameter.getType();
Return returnStatement = (Return) ((Block) method.getImplementation()).getStatements().get(0);
MethodCallExpression methodCall = (MethodCallExpression) returnStatement.getArgument();
assertEquals("At first, the method of String should be called",
findRunletClass("String"), methodCall.getMethodSignature().getOwningClass());
typeDefinition.setClazz(findRunletClass("Number"));
// Assert new model state
assertTrue("Number does not have such a method. Method should no longer be bound.",
methodCall.getMethodSignature() == null);
// Assert re-evalutaion change log
assertEquals("Only the actual method unsetting should have happend", 1, log.getLoggedReEvaluations().size());
ReEvaluationInfo reEvalInfo = log.getLoggedReEvaluations().iterator().next();
assertEquals(ReEvaluationType.FROM_BOUND_TO_FREE, reEvalInfo.type);
}
/**
* Replace the parameter type String with Binary. Binary has a length() method.
* The existing length() call should therefore no longer be bound to the method
* on String, but to the respective Method on binary.
*/
@Test
public void testParameterTypeChangesReBindsMethodCall() {
SapClass clazz = findRunletClass("MethodCallOutputMultiplicityTest");
CompressingReEvaluationLog log = registerReferencesForReevaluation(clazz);
MethodSignature method = clazz.getOwnedSignatures().iterator().next();
NamedValue parameter = method.getInput().iterator().next();
ClassTypeDefinition typeDefinition = (ClassTypeDefinition) parameter.getType();
Return returnStatement = (Return) ((Block) method.getImplementation()).getStatements().get(0);
MethodCallExpression methodCall = (MethodCallExpression) returnStatement.getArgument();
assertEquals("At first, the method of String should be called",
findRunletClass("String"), methodCall.getMethodSignature().getOwningClass());
typeDefinition.setClazz(findRunletClass("Binary"));
// Assert new model state
assertEquals("Method should now be bound to Binary.",
findRunletClass("Binary"), methodCall.getMethodSignature().getOwningClass());
// Assert re-evalutaion change log
assertEquals("Only the actual method rebind should have happend", 1, log.getLoggedReEvaluations().size());
ReEvaluationInfo reEvalInfo = log.getLoggedReEvaluations().iterator().next();
assertEquals(ReEvaluationType.REBOUND_TO_OTHER, reEvalInfo.type);
}
}