forked from INRIA/spoon
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(CtDeprecatedRef): removes only unused deprecated methods (INRIA#3200
- Loading branch information
1 parent
c1f947f
commit cfbaf62
Showing
9 changed files
with
462 additions
and
44 deletions.
There are no files selected for viewing
80 changes: 80 additions & 0 deletions
80
src/main/java/spoon/refactoring/CtDeprecatedRefactoring.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,80 @@ | ||
/** | ||
* Copyright (C) 2006-2019 INRIA and contributors | ||
* | ||
* Spoon is available either under the terms of the MIT License (see LICENSE-MIT.txt) of the Cecill-C License (see LICENSE-CECILL-C.txt). You as the user are entitled to choose the terms under which to adopt Spoon. | ||
*/ | ||
package spoon.refactoring; | ||
|
||
import java.util.Collection; | ||
import java.util.Iterator; | ||
import spoon.Launcher; | ||
import spoon.reflect.CtModel; | ||
import spoon.reflect.declaration.CtExecutable; | ||
import spoon.reflect.declaration.CtField; | ||
import spoon.reflect.visitor.filter.TypeFilter; | ||
import spoon.support.sniper.SniperJavaPrettyPrinter; | ||
|
||
public class CtDeprecatedRefactoring { | ||
|
||
protected void removeDeprecatedMethods(String inputPath, String resultPath) { | ||
Launcher spoon = new Launcher(); | ||
spoon.addInputResource(inputPath); | ||
spoon.setSourceOutputDirectory(resultPath); | ||
doRefactor(spoon); | ||
} | ||
|
||
protected void removeDeprecatedMethods(String path) { | ||
removeDeprecatedMethods(path, path); | ||
} | ||
|
||
private void doRefactor(Launcher spoon) { | ||
MethodInvocationSearch processor = new MethodInvocationSearch(); | ||
spoon.getEnvironment().setPrettyPrinterCreator(() -> { | ||
return new SniperJavaPrettyPrinter(spoon.getEnvironment()); | ||
}); | ||
CtModel model = spoon.buildModel(); | ||
model.getElements(new TypeFilter<>(CtField.class)).forEach(processor::scan); | ||
model.getElements(new TypeFilter<>(CtExecutable.class)).forEach(processor::scan); | ||
|
||
Collection<MethodCallState> invocationsOfMethod = processor.getInvocationsOfMethod(); | ||
removeUncalledMethods(invocationsOfMethod); | ||
|
||
for (CtExecutable<?> method : model.getElements(new TypeFilter<>(CtExecutable.class))) { | ||
if (method.hasAnnotation(Deprecated.class) | ||
&& invocationsOfMethod.stream().noneMatch(v -> v.getMethod().equals(method))) { | ||
method.delete(); | ||
} | ||
} | ||
spoon.prettyprint(); | ||
} | ||
|
||
private void removeUncalledMethods(Collection<MethodCallState> invocationsOfMethod) { | ||
boolean changed = false; | ||
do { | ||
changed = false; | ||
Iterator<MethodCallState> iterator = invocationsOfMethod.iterator(); | ||
while (iterator.hasNext()) { | ||
MethodCallState entry = iterator.next(); | ||
if (!entry.getMethod().hasAnnotation(Deprecated.class)) { | ||
// only look at deprecated Methods | ||
continue; | ||
} | ||
if (entry.checkCallState()) { | ||
// removes never called and deprecated Methods | ||
changed = true; | ||
// remove the method for further lookups in value collections | ||
invocationsOfMethod.forEach(v -> v.remove(entry.getMethod())); | ||
// remove the method as key | ||
iterator.remove(); | ||
} | ||
if (entry.getCallerMethods().size() == 1 && entry.getCallerMethods().contains(entry.getMethod()) | ||
&& entry.getCallerFields().isEmpty()) { | ||
// removes deprecated methods, that are only called by itself | ||
changed = true; | ||
invocationsOfMethod.forEach(invocation -> invocation.remove(entry.getMethod())); | ||
iterator.remove(); | ||
} | ||
} | ||
} while (changed); | ||
} | ||
} |
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,117 @@ | ||
/** | ||
* Copyright (C) 2006-2019 INRIA and contributors | ||
* | ||
* Spoon is available either under the terms of the MIT License (see LICENSE-MIT.txt) of the Cecill-C License (see LICENSE-CECILL-C.txt). You as the user are entitled to choose the terms under which to adopt Spoon. | ||
*/ | ||
package spoon.refactoring; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Collection; | ||
|
||
import spoon.reflect.declaration.CtExecutable; | ||
import spoon.reflect.declaration.CtType; | ||
|
||
/** | ||
* This class is for the call state of a method. A method can be called by | ||
* fields in a type e.g. class or by methods. Both cases are handled in this | ||
* class. For checking calls by fields use methods using CtType, for fields use | ||
* the methods using CtExecutable. A method is never called if both collections | ||
* are empty. | ||
*/ | ||
public class MethodCallState { | ||
private CtExecutable<?> method; | ||
private Collection<CtExecutable<?>> callerMethods; | ||
private Collection<CtType<?>> callerFields; | ||
|
||
/** | ||
* | ||
* @param method for saving it's call state. | ||
*/ | ||
public MethodCallState(CtExecutable<?> method) { | ||
this.method = method; | ||
this.callerFields = new ArrayList<>(); | ||
this.callerMethods = new ArrayList<>(); | ||
} | ||
|
||
/** | ||
* Adds a CtExecutable to the methods invoking this method. Adding the same | ||
* method again doesn't change the state. | ||
* | ||
* @param method invoking the method. | ||
* @see java.util.Collection#add(java.lang.Object) | ||
*/ | ||
public void add(CtExecutable<?> method) { | ||
callerMethods.add(method); | ||
} | ||
|
||
/** | ||
* Adds a CtType to the fields invoking this method. Adding the same CtType | ||
* again doesn't change the state. | ||
* | ||
* @param type invoking the method with an initializer. | ||
* @see java.util.Collection#add(java.lang.Object) | ||
*/ | ||
public void add(CtType<?> type) { | ||
callerFields.add(type); | ||
} | ||
|
||
/** | ||
* Getter for the method, without saved call state. Returns the CtExecutable and | ||
* not a copy. | ||
* | ||
* @return method without saved call state. | ||
*/ | ||
public CtExecutable<?> getMethod() { | ||
return method; | ||
} | ||
|
||
/** | ||
* Returns a collection containing all types invoking the method with a field. | ||
* Even if a CtType invokes multiple times with different fields the method, the | ||
* type is only present once. Returns the collection and not a copy. Changes to | ||
* collection are directly backed in the state. | ||
* | ||
* @return Collection containing all types invoking the method with a field. | ||
*/ | ||
public Collection<CtType<?>> getCallerFields() { | ||
return callerFields; | ||
} | ||
|
||
/** | ||
* Returns a collection containing all CtExecutable invoking the method. Even if | ||
* a CtExecutable invokes multiple times the method, the CtExecutable is only | ||
* present once. Returns the collection and not a copy. Changes to collection | ||
* are directly backed in the state. | ||
* | ||
* @return Collection containing all CtExecutable invoking the method. | ||
*/ | ||
public Collection<CtExecutable<?>> getCallerMethods() { | ||
return callerMethods; | ||
} | ||
|
||
/** | ||
* Checks the call state for the method. | ||
* | ||
* @return True if the method has no known call, false otherwise. | ||
* @see java.util.Collection#isEmpty() | ||
*/ | ||
public boolean checkCallState() { | ||
return callerMethods.isEmpty() && callerFields.isEmpty(); | ||
} | ||
|
||
public boolean contains(CtType<?> o) { | ||
return callerFields.contains(o); | ||
} | ||
|
||
public boolean contains(CtExecutable<?> o) { | ||
return callerMethods.contains(o); | ||
} | ||
|
||
public void remove(CtType<?> o) { | ||
callerFields.remove(o); | ||
} | ||
|
||
public void remove(CtExecutable<?> o) { | ||
callerMethods.remove(o); | ||
} | ||
} |
96 changes: 96 additions & 0 deletions
96
src/main/java/spoon/refactoring/MethodInvocationSearch.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,96 @@ | ||
/** | ||
* Copyright (C) 2006-2019 INRIA and contributors | ||
* | ||
* Spoon is available either under the terms of the MIT License (see LICENSE-MIT.txt) of the Cecill-C License (see LICENSE-CECILL-C.txt). You as the user are entitled to choose the terms under which to adopt Spoon. | ||
*/ | ||
package spoon.refactoring; | ||
|
||
import java.util.HashSet; | ||
import java.util.Arrays; | ||
import java.util.Collection; | ||
import java.util.Collections; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Objects; | ||
import java.util.stream.Collectors; | ||
import java.util.stream.Stream; | ||
|
||
import spoon.reflect.code.CtConstructorCall; | ||
import spoon.reflect.code.CtInvocation; | ||
import spoon.reflect.code.CtLambda; | ||
import spoon.reflect.declaration.CtExecutable; | ||
import spoon.reflect.declaration.CtField; | ||
import spoon.reflect.declaration.CtMethod; | ||
import spoon.reflect.declaration.CtType; | ||
import spoon.reflect.visitor.CtScanner; | ||
import spoon.reflect.visitor.filter.TypeFilter; | ||
import spoon.support.reflect.code.CtLambdaImpl; | ||
|
||
/** | ||
* Class for creating a mapping from CtExecutable to all known calls from fields | ||
* and methods. | ||
*/ | ||
public class MethodInvocationSearch extends CtScanner { | ||
private Map<CtExecutable<?>, Collection<CtExecutable<?>>> invocationsOfMethod = new HashMap<>(); | ||
private Map<CtExecutable<?>, Collection<CtType<?>>> invocationsOfField = new HashMap<>(); | ||
|
||
@Override | ||
public <T> void visitCtMethod(CtMethod<T> method) { | ||
if (!method.getPosition().isValidPosition()) { | ||
return; | ||
} | ||
final CtExecutable<?> transformedMethod; | ||
if (method instanceof CtLambda) { | ||
// because lambdas are difficult we transform them | ||
// in a method public void foo(){ List.of("a").stream().forEach($method)} | ||
// we need the ref to foo() and not the ref to the lambda expression | ||
transformedMethod = method.getParent(CtExecutable.class); | ||
} else { | ||
transformedMethod = method; | ||
} | ||
List<CtInvocation<?>> invocations = method.getElements(new TypeFilter<>(CtInvocation.class)); | ||
|
||
List<CtConstructorCall<?>> constructors = method.getElements(new TypeFilter<>(CtConstructorCall.class)); | ||
if (!invocationsOfMethod.containsKey(method) && !method.isImplicit() && !(method instanceof CtLambdaImpl)) { | ||
// now every method should be key | ||
invocationsOfMethod.put(method, Collections.emptyList()); | ||
} | ||
invocations.stream().filter(v -> !v.isImplicit()).map(v -> v.getExecutable().getExecutableDeclaration()) | ||
.filter(Objects::nonNull).filter(v -> v.getPosition().isValidPosition()) | ||
.forEach(v -> invocationsOfMethod.merge(v, new HashSet<>(Arrays.asList(transformedMethod)), | ||
(o1, o2) -> Stream.concat(o1.stream(), o2.stream()).collect(Collectors.toCollection(HashSet::new)))); | ||
constructors.stream().filter(v -> !v.isImplicit()).map(v -> v.getExecutable().getExecutableDeclaration()) | ||
.filter(Objects::nonNull) | ||
.forEach(v -> invocationsOfMethod.merge(v, new HashSet<>(Arrays.asList(transformedMethod)), | ||
(o1, o2) -> Stream.concat(o1.stream(), o2.stream()).collect(Collectors.toCollection(HashSet::new)))); | ||
super.visitCtMethod(method); | ||
} | ||
|
||
public Collection<MethodCallState> getInvocationsOfMethod() { | ||
Collection<MethodCallState> transformedResult = new HashSet<>(); | ||
Stream.concat(invocationsOfMethod.keySet().stream(), invocationsOfField.keySet().stream()).map(MethodCallState::new) | ||
.forEach(transformedResult::add); | ||
for (MethodCallState methodCallState : transformedResult) { | ||
invocationsOfField.getOrDefault(methodCallState.getMethod(), Collections.emptyList()) | ||
.forEach(methodCallState::add); | ||
invocationsOfMethod.getOrDefault(methodCallState.getMethod(), Collections.emptyList()) | ||
.forEach(methodCallState::add); | ||
} | ||
return transformedResult; | ||
} | ||
|
||
@Override | ||
public <T> void visitCtField(CtField<T> field) { | ||
field.getElements(new TypeFilter<>(CtInvocation.class)).stream() | ||
.map(call -> call.getExecutable().getExecutableDeclaration()) | ||
.forEach(method -> invocationsOfField.merge(method, new HashSet<>(Arrays.asList(field.getDeclaringType())), | ||
(o1, o2) -> Stream.concat(o1.stream(), o2.stream()).collect(Collectors.toCollection(HashSet::new)))); | ||
field.getElements(new TypeFilter<>(CtConstructorCall.class)).stream() | ||
.map(call -> call.getExecutable().getExecutableDeclaration()) | ||
.forEach(method -> invocationsOfField.merge(method, new HashSet<>(Arrays.asList(field.getDeclaringType())), | ||
(o1, o2) -> Stream.concat(o1.stream(), o2.stream()).collect(Collectors.toCollection(HashSet::new)))); | ||
super.visitCtField(field); | ||
} | ||
|
||
} |
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
Oops, something went wrong.