From 20db5d7ee4ba911347e11a3fd2ffb5e6ebd5bba5 Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Tue, 7 Feb 2017 19:24:32 +0100 Subject: [PATCH] feature: ChangeLocalVariableName + tests --- .../refactoring/AbstractRenameRefactor.java | 111 ++++++++ .../refactoring/ChangeLocalVariableName.java | 222 ++++++++++++++++ src/main/java/spoon/refactoring/Issue.java | 21 ++ .../java/spoon/refactoring/IssueImpl.java | 36 +++ src/main/java/spoon/refactoring/Refactor.java | 27 ++ .../refactoring/ChangeVariableNameTest.java | 173 +++++++++++++ .../refactoring/testclasses/TryRename.java | 16 ++ .../testclasses/VariableRename.java | 236 ++++++++++++++++++ 8 files changed, 842 insertions(+) create mode 100644 src/main/java/spoon/refactoring/AbstractRenameRefactor.java create mode 100644 src/main/java/spoon/refactoring/ChangeLocalVariableName.java create mode 100644 src/main/java/spoon/refactoring/Issue.java create mode 100644 src/main/java/spoon/refactoring/IssueImpl.java create mode 100644 src/main/java/spoon/refactoring/Refactor.java create mode 100644 src/test/java/spoon/test/refactoring/ChangeVariableNameTest.java create mode 100644 src/test/java/spoon/test/refactoring/testclasses/TryRename.java create mode 100644 src/test/java/spoon/test/refactoring/testclasses/VariableRename.java diff --git a/src/main/java/spoon/refactoring/AbstractRenameRefactor.java b/src/main/java/spoon/refactoring/AbstractRenameRefactor.java new file mode 100644 index 00000000000..a43f238297d --- /dev/null +++ b/src/main/java/spoon/refactoring/AbstractRenameRefactor.java @@ -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 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 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() { + @Override + public void accept(CtReference t) { + t.setSimpleName(AbstractRenameRefactor.this.newName); + } + }); + target.setSimpleName(newName); + } + + protected abstract void forEachReference(CtConsumer consumer); + + @Override + public List getIssues() { + List issues = new ArrayList<>(); + detectIssues(issues); + return issues; + } + + protected void detectIssues(List issues) { + checkNewNameIsValid(issues); + detectNameConflicts(issues); + } + + /** + * checks whether {@link #newName} is valid java identifier + * @param issues + */ + protected void checkNewNameIsValid(List issues) { + } + + protected void detectNameConflicts(List 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; + } +} diff --git a/src/main/java/spoon/refactoring/ChangeLocalVariableName.java b/src/main/java/spoon/refactoring/ChangeLocalVariableName.java new file mode 100644 index 00000000000..a36e51f1ed3 --- /dev/null +++ b/src/main/java/spoon/refactoring/ChangeLocalVariableName.java @@ -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> { + + public static Pattern validVariableNameRE = javaIdentifierRE; + + public ChangeLocalVariableName() { + super(validVariableNameRE); + } + + @Override + protected void forEachReference(CtConsumer consumer) { + getTarget().map(new VariableReferenceFunction()).forEach(consumer); + } + + @Override + protected void detectNameConflicts(List 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>() { + @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() { + @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> 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>() { + @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>() { + @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>() { + @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>() { + @Override + public boolean matches(CtVariable var) { + return newName.equals(var.getSimpleName()); + } + }).forEach(new CtConsumer>() { + @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() { + 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."); + } +} diff --git a/src/main/java/spoon/refactoring/Issue.java b/src/main/java/spoon/refactoring/Issue.java new file mode 100644 index 00000000000..011278c9805 --- /dev/null +++ b/src/main/java/spoon/refactoring/Issue.java @@ -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(); +} diff --git a/src/main/java/spoon/refactoring/IssueImpl.java b/src/main/java/spoon/refactoring/IssueImpl.java new file mode 100644 index 00000000000..9161fc0575a --- /dev/null +++ b/src/main/java/spoon/refactoring/IssueImpl.java @@ -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(); + } +} diff --git a/src/main/java/spoon/refactoring/Refactor.java b/src/main/java/spoon/refactoring/Refactor.java new file mode 100644 index 00000000000..b6ab969d09c --- /dev/null +++ b/src/main/java/spoon/refactoring/Refactor.java @@ -0,0 +1,27 @@ +/** + * 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.List; + +/** + * + */ +public interface Refactor { + List getIssues(); + void refactor(); +} diff --git a/src/test/java/spoon/test/refactoring/ChangeVariableNameTest.java b/src/test/java/spoon/test/refactoring/ChangeVariableNameTest.java new file mode 100644 index 00000000000..e4a0f6b6615 --- /dev/null +++ b/src/test/java/spoon/test/refactoring/ChangeVariableNameTest.java @@ -0,0 +1,173 @@ +package spoon.test.refactoring; + +import static org.junit.Assert.*; + +import org.junit.Before; +import org.junit.Test; + +import spoon.SpoonException; +import spoon.refactoring.ChangeLocalVariableName; +import spoon.reflect.code.CtLocalVariable; +import spoon.reflect.declaration.CtAnnotation; +import spoon.reflect.declaration.CtClass; +import spoon.reflect.declaration.CtElement; +import spoon.reflect.declaration.CtMethod; +import spoon.reflect.declaration.CtType; +import spoon.reflect.declaration.CtVariable; +import spoon.reflect.factory.Factory; +import spoon.reflect.reference.CtTypeReference; +import spoon.test.refactoring.testclasses.TryRename; +import spoon.test.refactoring.testclasses.VariableRename; +import spoon.testing.utils.ModelUtils; + +public class ChangeVariableNameTest +{ + Factory factory; + CtClass varRenameClass; + CtTypeReference tryRename; + CtLocalVariable local1Var; + + @Before + public void setup() throws Exception { + varRenameClass = (CtClass)ModelUtils.buildClass(VariableRename.class); + factory = varRenameClass.getFactory(); + tryRename = factory.createCtTypeReference(TryRename.class); + local1Var = varRenameClass.filterChildren((CtLocalVariable var)->true).first(); + } + + @Test + public void testModelConsistency() throws Exception { + new VariableRename(); + } + + @Test + public void testRenameLocalVariableToUsedName() throws Exception { + + varRenameClass.getMethods().forEach(method->{ + method.filterChildren((CtVariable var)->true) + .map((CtVariable var)->var.getAnnotation(tryRename)) + .forEach((CtAnnotation annotation)->{ + String[] newNames = annotation.getActualAnnotation().value(); + CtVariable targetVariable = (CtVariable)annotation.getAnnotatedElement(); + for (String newName : newNames) { + boolean renameShouldPass = newName.startsWith("-")==false; + if (!renameShouldPass) { + newName = newName.substring(1); + } + if (targetVariable instanceof CtLocalVariable) { + checkLocalVariableRename((CtLocalVariable) targetVariable, newName, renameShouldPass); + } else { + //TODO rename of other variables + } + } + }); + }); + } + + protected void checkLocalVariableRename(CtLocalVariable targetVariable, String newName, boolean renameShouldPass) { + + String originName = targetVariable.getSimpleName(); + ChangeLocalVariableName refactor = new ChangeLocalVariableName(); + refactor.setTarget(targetVariable); + refactor.setNewName(newName); + if(renameShouldPass) { + try { + refactor.refactor(); + } catch(SpoonException e) { + throw new AssertionError(getParentMethodName(targetVariable)+" Rename of \""+originName+"\" should NOT fail when trying rename to \""+newName+"\"", e); + } + assertEquals(getParentMethodName(targetVariable)+" Rename of \""+originName+"\" to \""+newName+"\" passed, but the name of variable was not changed", newName, targetVariable.getSimpleName()); + } else { + try { + refactor.refactor(); + fail(getParentMethodName(targetVariable)+" Rename of \""+originName+"\" should fail when trying rename to \""+newName+"\""); + } catch(SpoonException e) { + } + assertEquals(getParentMethodName(targetVariable)+" Rename of \""+originName+"\" failed when trying rename to \""+newName+"\" but the name of variable should not be changed", originName, targetVariable.getSimpleName()); + } + printModelAndTestConsistency(); + if(renameShouldPass) { + //rollback changes + refactor.setNewName(originName); + refactor.refactor(); + } + assertEquals(originName, targetVariable.getSimpleName()); + } + + private void printModelAndTestConsistency() { + /* + * TODO + * 1) print modified model, + * 2) build it + * 3) create instance using that new model + * 4) test consistency of the class + */ + } + + private String getParentMethodName(CtElement ele) { + return ele.getParent(CtType.class).getSimpleName()+"#"+ele.getParent(CtMethod.class).getSimpleName(); + } + + + @Test + public void testRefactorWithoutTarget() throws Exception { + + ChangeLocalVariableName refactor = new ChangeLocalVariableName(); + refactor.setNewName("local1"); + try { + refactor.refactor(); + fail(); + } catch(SpoonException e) { + + } + } + + @Test + public void testRenameLocalVariableToSameName() throws Exception { + + ChangeLocalVariableName refactor = new ChangeLocalVariableName(); + refactor.setTarget(local1Var); + refactor.setNewName("local1"); + refactor.refactor(); + assertEquals("local1", local1Var.getSimpleName()); + } + + @Test + public void testRenameLocalVariableToInvalidName() throws Exception { + + ChangeLocalVariableName refactor = new ChangeLocalVariableName(); + try { + refactor.setNewName(""); + fail(); + } catch(SpoonException e) { + } + assertEquals("local1", local1Var.getSimpleName()); + + try { + refactor.setNewName("x "); + fail(); + } catch(SpoonException e) { + } + + try { + refactor.setNewName("x y"); + fail(); + } catch(SpoonException e) { + } + + try { + refactor.setNewName("x("); + fail(); + } catch(SpoonException e) { + } + } + + @Test + public void testRenameLocalVariableToValidName() throws Exception { + ChangeLocalVariableName refactor = new ChangeLocalVariableName(); + refactor.setTarget(local1Var); + refactor.setNewName("local3"); + refactor.refactor(); + assertEquals("local3", local1Var.getSimpleName()); + } +} diff --git a/src/test/java/spoon/test/refactoring/testclasses/TryRename.java b/src/test/java/spoon/test/refactoring/testclasses/TryRename.java new file mode 100644 index 00000000000..8f418d22600 --- /dev/null +++ b/src/test/java/spoon/test/refactoring/testclasses/TryRename.java @@ -0,0 +1,16 @@ +package spoon.test.refactoring.testclasses; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.LOCAL_VARIABLE, ElementType.PARAMETER, ElementType.FIELD}) +public @interface TryRename { + /** + * @return the list of names whose rename should pass + */ + String[] value(); + +} diff --git a/src/test/java/spoon/test/refactoring/testclasses/VariableRename.java b/src/test/java/spoon/test/refactoring/testclasses/VariableRename.java new file mode 100644 index 00000000000..296c43e5dd9 --- /dev/null +++ b/src/test/java/spoon/test/refactoring/testclasses/VariableRename.java @@ -0,0 +1,236 @@ +package spoon.test.refactoring.testclasses; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Function; + +import static org.junit.Assert.*; + +public class VariableRename +{ + public VariableRename() + { + //call all not private methods of this class automatically, to check assertions + Method[] methods = getClass().getDeclaredMethods(); + for (Method method : methods) { + try { + if(Modifier.isPrivate(method.getModifiers())) { + continue; + } + method.invoke(this); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + throw new RuntimeException("Invocation of method "+method.getName()+" failed", e); + } + } + } + + void callConflictWithParam() { + conflictWithParam(2); + } + + /** + * tests conflict of local variable with parameter + */ + private void conflictWithParam(@TryRename("-var1") int var2) { + @TryRename("-var2") + int var1 = 1; + assertTrue(var1 == 1); + assertTrue(var2 == 2); + } + + /** + * tests conflict of local variable with CtCatchVariable + */ + private void conflictWithParam() { + @TryRename({"-var2", "-var3"}) + int var1 = 1; + try { + assertTrue(var1 == 1); + @TryRename({"-var1", "var3"}) + int var2 = 2; + assertTrue(var2 == 2); + throw new NumberFormatException(); + } catch (@TryRename({"-var1", "var2"}) NumberFormatException var3) { + assertTrue(var1 == 1); + } + assertTrue(var1 == 1); + } + + /** + * Tests nested class and conflict with field, and nested local variable which must not be shadowed + */ + void nestedClassMethod() { + @TryRename({"var2", "var3", "var4", "var5", "var6"}) + int var1 = 1; + new Consumer() { + //must not rename to var1, because it would shadow var1 reference below + @TryRename({"-var1", "var3", "var4", "var5", "var6"}) + int var2 = 2; + @Override + public void accept( + //must not rename to var1, because reference to var1 below would be shadowed + @TryRename({"-var1", "var2", "-var3", "-var5", "-var6"}) Integer var4 + ) { + //cannot rename to var1, because reference to var1 below would be shadowed + @TryRename({"-var1", "var2", "-var4", "-var5", "-var6"}) + int var3 = 3; + try { + //cannot rename to var1, because reference to var1 below would be shadowed + @TryRename({"-var1", "var2", "-var3", "-var4", "var6"}) + int var5 = 5; + assertTrue(var1 == 1); + assertTrue(var2 == 2); + assertTrue(var3 == 3); + assertTrue(var4 == 4); + assertTrue(var5 == 5); + throw new NumberFormatException(); + } catch ( + //cannot rename to var1, because reference to var1 below would be shadowed + @TryRename({"-var1", "var2", "-var3", "-var4", "var5"}) NumberFormatException var6) { + assertTrue(var1 == 1); + assertTrue(var2 == 2); + assertTrue(var3 == 3); + assertTrue(var4 == 4); + } + } + }.accept(4); + assertTrue(var1 == 1); + } + + void nestedClassMethodWithShadowVar() { + @TryRename({"var2", "var3"}) + int var1 = 2; + new Runnable() { + @TryRename({"var1", "var3"}) + int var2 = 3; + @Override + public void run() { + @TryRename({"-var1", "var2"}) + int var3 = 1; + assertTrue(var1 == 2); + //this var1 shadows above defined var1. It can be renamed + @TryRename({"var2", "-var3"}) + int var1 = 4; + assertTrue(var1 == 4); + assertTrue(var2 == 3); + assertTrue(var3 == 1); + } + }.run(); + assertTrue(var1 == 2); + } + + void nestedClassMethodWithShadowVarAndField() { + @TryRename({"var2", "var3"}) + int var1 = 2; + new Runnable() { + @TryRename({"var2", "var3"}) + //this var1 shadows above defined var1. + int var1 = 3; + @Override + public void run() { + @TryRename({"-var1", "var2"}) + int var2 = 1; + assertTrue(var1 == 3); + @TryRename({"var2", "-var3"}) + int var1 = 4; + assertTrue(var1 == 4); + assertTrue(this.var1 == 3); + assertTrue(var2 == 1); + } + }.run(); + assertTrue(var1 == 2); + } + + void lambda() { + @TryRename({"-var2", "-var3"}) + int var1 = 1; + assertTrue(var1 == 1); + Function fnc = (@TryRename({"-var1", "-var3"}) Integer var2)->{ + @TryRename({"-var1", "-var2"}) + int var3 = 3; + assertTrue(var1 == 1); + assertTrue(var2 == 2); + assertTrue(var3 == 3); + return var2; + }; + assertTrue(fnc.apply(2) == 2); + } + + void tryCatch() { + @TryRename({"-var2", "-var3", "-var4"}) + int var1 = 1; + assertTrue(var1 == 1); + try { + @TryRename({"-var1","-var3","var4"}) + int var2 = 2; + assertTrue(var1 == 1); + assertTrue(var2 == 2); + throw new Exception("ex2"); + } catch (@TryRename({"-var1", "var2", "-var4"}) Exception var3) { + @TryRename({"-var1", "var2", "-var3"}) + int var4 = 4; + assertTrue(var1 == 1); + assertTrue(var4 == 4); + } + } + + /* + private String[] method(String param1) { + List values = new ArrayList<>(); + new Runnable() { + @Override + public void run() { + String local9 = "l9"; + values.add(local9); + Function fnc = (String local7)->{ + String local8 = "l8"; + try { + throw new Exception("ex2"); + } catch (Exception local6) { + values.add(member1); + values.add(static1); + values.add(param1); + { + String local0 = "l0"; + values.add(local0); + } + String local1 = "l1"; + values.add(local1); + String local2 = "l2"; + values.add(local2); + for(int local3 = 0; local3<1; local3++) + { + values.add("l3_"+String.valueOf(local3)); + String local4 = "l4"; + values.add(local4); + } + values.add(method1()); + values.add(staticMethod1()); + try { + throw new Exception("ex1"); + } catch (Exception local5) { + values.add(local5.getMessage()); + } + values.add(local6.getMessage()); + } + values.add(local7); + return local8; + }; + values.add(fnc.apply("l7")); + } + }.run(); + return values.toArray(new String[0]); + } + + private String method1() { + return "met1"; + } + private static String staticMethod1() { + return "statMet1"; + } + */ +}