-
-
Notifications
You must be signed in to change notification settings - Fork 357
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
35634fd
commit 69b5996
Showing
8 changed files
with
937 additions
and
0 deletions.
There are no files selected for viewing
111 changes: 111 additions & 0 deletions
111
src/main/java/spoon/refactoring/AbstractRenameRefactor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
238 changes: 238 additions & 0 deletions
238
src/main/java/spoon/refactoring/ChangeLocalVariableName.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,238 @@ | ||
/** | ||
* 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.CtCatchVariable; | ||
import spoon.reflect.code.CtLocalVariable; | ||
import spoon.reflect.declaration.CtElement; | ||
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.CtLocalVariableReference; | ||
import spoon.reflect.reference.CtReference; | ||
import spoon.reflect.reference.CtVariableReference; | ||
import spoon.reflect.visitor.Filter; | ||
import spoon.reflect.visitor.chain.CtConsumer; | ||
import spoon.reflect.visitor.chain.CtQueryable; | ||
import spoon.reflect.visitor.chain.CtScannerListener; | ||
import spoon.reflect.visitor.chain.ScanningMode; | ||
import spoon.reflect.visitor.filter.LocalVariableReferenceFunction; | ||
import spoon.reflect.visitor.filter.LocalVariableScopeFunction; | ||
import spoon.reflect.visitor.filter.PotentialVariableDeclarationFunction; | ||
import spoon.reflect.visitor.filter.SiblingsFunction; | ||
import spoon.reflect.visitor.filter.SiblingsFunction.Mode; | ||
import spoon.reflect.visitor.filter.VariableReferenceFunction; | ||
|
||
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); | ||
} | ||
|
||
private static class QueryDriver implements CtScannerListener { | ||
int nrOfNestedLocalClasses = 0; | ||
CtElement ignoredParent; | ||
|
||
@Override | ||
public ScanningMode enter(CtElement element) { | ||
if (ignoredParent != null && element instanceof CtElement) { | ||
CtElement ele = (CtElement) element; | ||
if (ele.hasParent(ignoredParent)) { | ||
return ScanningMode.SKIP_ALL; | ||
} | ||
} | ||
if (element instanceof CtType) { | ||
nrOfNestedLocalClasses++; | ||
} | ||
return ScanningMode.NORMAL; | ||
} | ||
|
||
@Override | ||
public void exit(CtElement element) { | ||
if (ignoredParent == element) { | ||
//we are living scope of ignored parent. We can stop checking it | ||
ignoredParent = null; | ||
} | ||
if (element instanceof CtType) { | ||
nrOfNestedLocalClasses--; | ||
} | ||
} | ||
|
||
public void ignoreChildrenOf(CtElement element) { | ||
if (ignoredParent != null) { | ||
throw new SpoonException("Unexpected state. The ignoredParent is already set"); | ||
} | ||
ignoredParent = element; | ||
} | ||
|
||
public boolean isInContextOfLocalClass() { | ||
return nrOfNestedLocalClasses > 0; | ||
} | ||
} | ||
|
||
@Override | ||
protected void detectNameConflicts(final List<Issue> issues) { | ||
/* | ||
* There can be these conflicts | ||
* 1) target variable would shadow before declared variable (parameter, localVariable, catchVariable) | ||
* -------------------------------------------------------------------------------------------------- | ||
*/ | ||
PotentialVariableDeclarationFunction potentialDeclarationFnc = new PotentialVariableDeclarationFunction(newName); | ||
CtVariable<?> var = getTarget().map(potentialDeclarationFnc).first(); | ||
if (var != null) { | ||
if (var instanceof CtField) { | ||
/* | ||
* we have found a field of same name. | ||
* It is not problem, because variables can hide field declaration. | ||
* Do nothing - OK | ||
*/ | ||
} else if (potentialDeclarationFnc.isTypeOnTheWay()) { | ||
/* | ||
* There is a local class declaration between future variable reference and variable declaration `var`. | ||
* The found variable declaration `var` can be hidden by target variable with newName | ||
* as long as there is no reference to `var` in visibility scope of the target variable. | ||
* So search for such `var` reference now | ||
*/ | ||
CtVariableReference<?> shadowedVar = target | ||
.map(new SiblingsFunction().includingSelf(true).mode(Mode.NEXT)) | ||
.map(new VariableReferenceFunction(var)).first(); | ||
if (shadowedVar != null) { | ||
//found variable reference, which would be shadowed by variable after rename. | ||
createNameConflictIssue(issues, var, shadowedVar); | ||
} else { | ||
/* | ||
* there is no local variable reference, which would be shadowed by variable after rename. | ||
* OK | ||
*/ | ||
} | ||
} else { | ||
/* | ||
* the found variable is in conflict with target variable with newName | ||
*/ | ||
createNameConflictIssue(issues, var); | ||
} | ||
} | ||
/* | ||
* 2) target variable is shadowed by later declared variable | ||
* --------------------------------------------------------- | ||
*/ | ||
final QueryDriver queryDriver = new QueryDriver(); | ||
getTarget().map(new LocalVariableScopeFunction(queryDriver)).select(new Filter<CtElement>() { | ||
/** | ||
* return true for all CtVariables, which are in conflict | ||
*/ | ||
@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 will shadow input local variable if it's reference is in visibility scope of that field. | ||
* Search for target variable reference in visibility scope of this field. | ||
* If found than we cannot rename target variable to newName, because that reference would be shadowed | ||
*/ | ||
queryDriver.ignoreChildrenOf(element); | ||
CtLocalVariableReference<?> shadowedVar = element.map(new LocalVariableReferenceFunction(target)).first(); | ||
if (shadowedVar != null) { | ||
createNameConflictIssue(issues, fieldRef.getFieldDeclaration(), shadowedVar); | ||
return true; | ||
} | ||
return false; | ||
} | ||
} | ||
return false; | ||
} | ||
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!"); | ||
} | ||
if (variable instanceof CtCatchVariable || variable instanceof CtLocalVariable || variable instanceof CtParameter) { | ||
/* | ||
* we have found a catch variable or local variable or parameter with new name. | ||
*/ | ||
if (queryDriver.isInContextOfLocalClass()) { | ||
/* | ||
* We are in context of local class. | ||
* This variable would shadow input local variable after rename | ||
* so we cannot rename if there exist a local variable reference in variable visibility scope. | ||
*/ | ||
queryDriver.ignoreChildrenOf(variable.getParent()); | ||
CtQueryable searchScope; | ||
if (variable instanceof CtLocalVariable) { | ||
searchScope = variable.map(new SiblingsFunction().includingSelf(true).mode(Mode.NEXT)); | ||
} else { | ||
searchScope = variable.getParent(); | ||
} | ||
|
||
CtLocalVariableReference<?> shadowedVar = searchScope.map(new LocalVariableReferenceFunction(target)).first(); | ||
if (shadowedVar != null) { | ||
//found local variable reference, which would be shadowed by variable after rename. | ||
createNameConflictIssue(issues, variable, shadowedVar); | ||
return true; | ||
} | ||
//there is no local variable reference, which would be shadowed by variable after rename. | ||
return false; | ||
} else { | ||
/* | ||
* We are not in context of local class. | ||
* So this variable is in conflict. Return it | ||
*/ | ||
createNameConflictIssue(issues, variable); | ||
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(); | ||
} | ||
|
||
protected void createNameConflictIssue(List<Issue> issues, CtVariable<?> conflictVar) { | ||
issues.add(new IssueImpl(conflictVar.getClass().getSimpleName() + " with name " + conflictVar.getSimpleName() + " is in conflict.")); | ||
} | ||
protected void createNameConflictIssue(List<Issue> issues, CtVariable<?> conflictVar, CtVariableReference<?> shadowedVarRef) { | ||
issues.add(new IssueImpl(conflictVar.getClass().getSimpleName() + " with name " + conflictVar.getSimpleName() + " would shadow local variable reference.")); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} |
Oops, something went wrong.