Skip to content

Commit

Permalink
feature: ChangeLocalVariableName + tests
Browse files Browse the repository at this point in the history
  • Loading branch information
pvojtechovsky committed Feb 7, 2017
1 parent 825335e commit 20db5d7
Show file tree
Hide file tree
Showing 8 changed files with 842 additions and 0 deletions.
111 changes: 111 additions & 0 deletions src/main/java/spoon/refactoring/AbstractRenameRefactor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/**
* Copyright (C) 2006-2017 INRIA and contributors
* Spoon - http://spoon.gforge.inria.fr/
*
* This software is governed by the CeCILL-C License under French law and
* abiding by the rules of distribution of free software. You can use, modify
* and/or redistribute the software under the terms of the CeCILL-C license as
* circulated by CEA, CNRS and INRIA at http://www.cecill.info.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the CeCILL-C License for more details.
*
* The fact that you are presently reading this means that you have had
* knowledge of the CeCILL-C license and that you accept its terms.
*/
package spoon.refactoring;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

import spoon.SpoonException;
import spoon.reflect.declaration.CtNamedElement;
import spoon.reflect.reference.CtReference;
import spoon.reflect.visitor.chain.CtConsumer;

public abstract class AbstractRenameRefactor<T extends CtNamedElement> implements Refactor {
public static final Pattern javaIdentifierRE = Pattern.compile("\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*");

protected T target;
protected String newName;
protected Pattern newNameValidationRE;

protected AbstractRenameRefactor(Pattern newNameValidationRE) {
this.newNameValidationRE = newNameValidationRE;
}

@Override
public void refactor() {
if (getTarget() == null) {
throw new SpoonException("The target of refactoring is not defined");
}
if (getNewName() == null) {
throw new SpoonException("The new name of refactoring is not defined");
}
List<Issue> issues = getIssues();
if (issues.isEmpty() == false) {
throw new SpoonException("Refactoring cannot be processed. There are issues: " + issues.toString());
}
refactorNoCheck();
}

protected void refactorNoCheck() {
forEachReference(new CtConsumer<CtReference>() {
@Override
public void accept(CtReference t) {
t.setSimpleName(AbstractRenameRefactor.this.newName);
}
});
target.setSimpleName(newName);
}

protected abstract void forEachReference(CtConsumer<CtReference> consumer);

@Override
public List<Issue> getIssues() {
List<Issue> issues = new ArrayList<>();
detectIssues(issues);
return issues;
}

protected void detectIssues(List<Issue> issues) {
checkNewNameIsValid(issues);
detectNameConflicts(issues);
}

/**
* checks whether {@link #newName} is valid java identifier
* @param issues
*/
protected void checkNewNameIsValid(List<Issue> issues) {
}

protected void detectNameConflicts(List<Issue> issues) {
}


protected boolean isJavaIdentifier(String name) {
return javaIdentifierRE.matcher(name).matches();
}

public T getTarget() {
return target;
}

public void setTarget(T target) {
this.target = target;
}

public String getNewName() {
return newName;
}

public void setNewName(String newName) {
if (newNameValidationRE != null && newNameValidationRE.matcher(newName).matches() == false) {
throw new SpoonException("New name \"" + newName + "\" is not valid name");
}
this.newName = newName;
}
}
222 changes: 222 additions & 0 deletions src/main/java/spoon/refactoring/ChangeLocalVariableName.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
/**
* Copyright (C) 2006-2017 INRIA and contributors
* Spoon - http://spoon.gforge.inria.fr/
*
* This software is governed by the CeCILL-C License under French law and
* abiding by the rules of distribution of free software. You can use, modify
* and/or redistribute the software under the terms of the CeCILL-C license as
* circulated by CEA, CNRS and INRIA at http://www.cecill.info.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the CeCILL-C License for more details.
*
* The fact that you are presently reading this means that you have had
* knowledge of the CeCILL-C license and that you accept its terms.
*/
package spoon.refactoring;

import java.util.Collection;
import java.util.List;
import java.util.regex.Pattern;

import spoon.SpoonException;
import spoon.reflect.code.CtCatch;
import spoon.reflect.code.CtCatchVariable;
import spoon.reflect.code.CtLambda;
import spoon.reflect.code.CtLocalVariable;
import spoon.reflect.declaration.CtElement;
import spoon.reflect.declaration.CtExecutable;
import spoon.reflect.declaration.CtField;
import spoon.reflect.declaration.CtParameter;
import spoon.reflect.declaration.CtType;
import spoon.reflect.declaration.CtVariable;
import spoon.reflect.reference.CtFieldReference;
import spoon.reflect.reference.CtReference;
import spoon.reflect.visitor.Filter;
import spoon.reflect.visitor.chain.CtConsumer;
import spoon.reflect.visitor.filter.VariableReferencePossibleDeclarationFunction;
import spoon.reflect.visitor.filter.LocalVariableScopeFunction;
import spoon.reflect.visitor.filter.ParentFunction;
import spoon.reflect.visitor.filter.VariableReferenceFunction;
import spoon.reflect.visitor.filter.VariableScopeFunction;

public class ChangeLocalVariableName extends AbstractRenameRefactor<CtLocalVariable<?>> {

public static Pattern validVariableNameRE = javaIdentifierRE;

public ChangeLocalVariableName() {
super(validVariableNameRE);
}

@Override
protected void forEachReference(CtConsumer<CtReference> consumer) {
getTarget().map(new VariableReferenceFunction()).forEach(consumer);
}

@Override
protected void detectNameConflicts(List<Issue> issues) {
//There can these conflicts
//1) target variable would shadow before declared variable (parameter, localVariable, catchVariable)
//in this case we do not return fields, because it is allowed to shadow them - the fields can use "this." prefix to keep their reference valid
CtVariable<?> conflictVar = getTarget().map(new VariableReferencePossibleDeclarationFunction().includingFields(false))
.select(new Filter<CtVariable<?>>() {
@Override
public boolean matches(CtVariable<?> var) {
return newName.equals(var.getSimpleName());
}
}).first();
if (conflictVar != null) {
Issue issue = createNameConflictIssue(conflictVar);
if (issue != null) {
issues.add(issue);
}
}

//2) target variable is shadowed by later declared variable
//skip evaluation of children of local classes, their nested variables can shadow this variable, so they are not in conflict
conflictVar = getTarget().map(new LocalVariableScopeFunction()).select(new Filter<CtElement>() {
@Override
public boolean matches(CtElement element) {
if (element instanceof CtType<?>) {
CtType<?> localClass = (CtType<?>) element;
//TODO use faster hasField, implemented using map(new AllFieldsFunction()).select(new NameFilter(newName)).first()!=null
Collection<CtFieldReference<?>> fields = localClass.getAllFields();
for (CtFieldReference<?> fieldRef : fields) {
if (newName.equals(fieldRef.getSimpleName())) {
/*
* we have found a local class field, which shadows input local variable.
* So there cannot be conflict with input local variable in nested nodes of localClass.
* Skip scanning of children, or at least ignore all conflicts in children of localClass
*/
//TODO!! Skip scanning of children of this element
throw new RuntimeException("TODO");
}
}
} else if (element instanceof CtVariable<?>) {
CtVariable<?> variable = (CtVariable<?>) element;
if (newName.equals(variable.getSimpleName()) == false) {
//the variable with different name. Ignore it
return false;
}
//we have found a variable with new name
if (variable instanceof CtField) {
throw new SpoonException("This should not happen. The children of local class which contains a field with new name should be skipped!");
} else if (variable instanceof CtParameter) {
/*
* we have found a parameter with new name. It already shadows input local variable,
* so there cannot be conflict with input local variable in nested nodes of parent executable.
*/
//TODO!! Skip scanning of next children of element.getParent()
throw new RuntimeException("TODO");
} else if (variable instanceof CtCatchVariable || variable instanceof CtLocalVariable) {
/*
* we have found a catch variable or local variable with new name.
*/
if (isInContextOfLocalClass) {
/*
* We are in context of local class.
* This variable already shadows input local variable,
* so there cannot be conflict with input local variable in nested nodes of parent executable.
*/
//TODO!! Skip scanning of next children of element.getParent()
throw new RuntimeException("TODO");
} else {
/*
* We are not in context of local class.
* So this is conflict. Return it
*/
return true;
}
} else {
//CtField should not be there, because the children of local class which contains a field with new name should be skipped!
//Any new variable type???
throw new SpoonException("Unexpected variable " + variable.getClass().getName());
}
}
return false;
}
}).first();
if (conflictVar != null) {
Issue issue = createNameConflictIssue(conflictVar);
if (issue != null) {
issues.add(issue);
}
}
//search for parent element, which represents scope of variables, which might be in conflict
//it means first Method, Constructor or AnonymousExecutable. The Lambda is not scope for variables.
CtExecutable<?> l_scope = getTarget().map(new ParentFunction()).select(new Filter<CtExecutable<?>>() {
@Override
public boolean matches(CtExecutable<?> element) {
if (element instanceof CtLambda) {
return false;
}
return true;
}
}).first();

//1) search for all variable declarations whose name is equal to newName and which contains target variable in their visibility scope
l_scope.filterChildren(new Filter<CtVariable<?>>() {
@Override
public boolean matches(CtVariable<?> var) {
if (newName.equals(var.getSimpleName())) {
/*
* We have found a CtVariable whose simple name is equal to newName of target variable.
* Check if these variables are in conflict by
* visiting all elements in scope of input variable
* and match them if they are target variable.
* In other words, search for declaration of target variable in scope of input variable.
* If it is found, then there is conflict
*/
return isDeclaredInScopeOfSource(var, target);
}
return false;
}
})
//called for each variable declaration which is in conflict with newName
.forEach(new CtConsumer<CtVariable<?>>() {
@Override
public void accept(CtVariable<?> conflictVar) {
Issue issue = createNameConflictIssue(conflictVar);
if (issue != null) {
issues.add(issue);
}
}
});

//2) search for all variable declarations whose name is equal to newName and which are in visibility scope of target variable
target.map(new VariableScopeFunction()).select(new Filter<CtVariable<?>>() {
@Override
public boolean matches(CtVariable<?> var) {
return newName.equals(var.getSimpleName());
}
}).forEach(new CtConsumer<CtVariable<?>>() {
@Override
public void accept(CtVariable<?> conflictVar) {
Issue issue = createNameConflictIssue(conflictVar);
if (issue != null) {
issues.add(issue);
}
}
});
}

/**
* Detects whether target variable is declared in scope of source variable
*
* @param source - source variable declaration
* @param target - target variable declaration
* @return true if target variable is declared in visibility scope of source variable
*/
protected boolean isDeclaredInScopeOfSource(CtVariable<?> source, CtVariable<?> target) {
return source.map(new VariableScopeFunction()).select(new Filter<CtElement>() {
public boolean matches(CtElement element) {
return element == target;
};
}).first() != null;
}

protected Issue createNameConflictIssue(CtVariable<?> conflictVar) {
return new IssueImpl(conflictVar.getSimpleName() + " with name " + conflictVar.getSimpleName() + " already exists.");
}
}
21 changes: 21 additions & 0 deletions src/main/java/spoon/refactoring/Issue.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* Copyright (C) 2006-2017 INRIA and contributors
* Spoon - http://spoon.gforge.inria.fr/
*
* This software is governed by the CeCILL-C License under French law and
* abiding by the rules of distribution of free software. You can use, modify
* and/or redistribute the software under the terms of the CeCILL-C license as
* circulated by CEA, CNRS and INRIA at http://www.cecill.info.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the CeCILL-C License for more details.
*
* The fact that you are presently reading this means that you have had
* knowledge of the CeCILL-C license and that you accept its terms.
*/
package spoon.refactoring;

public interface Issue {
String getMessage();
}
36 changes: 36 additions & 0 deletions src/main/java/spoon/refactoring/IssueImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* Copyright (C) 2006-2017 INRIA and contributors
* Spoon - http://spoon.gforge.inria.fr/
*
* This software is governed by the CeCILL-C License under French law and
* abiding by the rules of distribution of free software. You can use, modify
* and/or redistribute the software under the terms of the CeCILL-C license as
* circulated by CEA, CNRS and INRIA at http://www.cecill.info.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the CeCILL-C License for more details.
*
* The fact that you are presently reading this means that you have had
* knowledge of the CeCILL-C license and that you accept its terms.
*/
package spoon.refactoring;

public class IssueImpl implements Issue {

private String message;

public IssueImpl(String message) {
this.message = message;
}

@Override
public String getMessage() {
return message;
}

@Override
public String toString() {
return getMessage();
}
}
Loading

0 comments on commit 20db5d7

Please sign in to comment.