forked from thoriumrobot/VGRTool
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathNestedNullRefactoring.java
More file actions
180 lines (153 loc) · 6.14 KB
/
NestedNullRefactoring.java
File metadata and controls
180 lines (153 loc) · 6.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
import java.lang.reflect.Modifier;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.List;
import org.checkerframework.checker.nullness.qual.*;
import org.eclipse.text.edits.TextEditGroup;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.FieldAccess;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.InfixExpression;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.NullLiteral;
import org.eclipse.jdt.core.dom.ParenthesizedExpression;
import org.eclipse.jdt.core.dom.PrefixExpression;
import org.eclipse.jdt.core.dom.PrimitiveType;
import org.eclipse.jdt.core.dom.QualifiedName;
import org.eclipse.jdt.core.dom.ReturnStatement;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
/**
* This class represents a refactoring in which invocations to private one line
* methods which return the nullness of a value are replaced with the method's
* one-line null check. Preserves semantics by copying the invoked code
* precisely.
*/
public class NestedNullRefactoring extends Refactoring {
public static final String NAME = "NestedNullRefactoring";
private final Dictionary<IMethodBinding, @NonNull Expression> applicableMethods;
public NestedNullRefactoring() {
applicableMethods = new Hashtable<>();
}
@Override
public boolean isApplicable(@NonNull ASTNode node) {
if (node instanceof MethodInvocation invocation) {
return isApplicableImpl(invocation);
}
if (node instanceof MethodDeclaration declaration) {
return isApplicableImpl(declaration);
}
return false;
}
/*
* Returns true iff the provided invocation is of a registered one-line method
* that returns the result of a null check
*/
private boolean isApplicableImpl(@NonNull MethodInvocation invocation) {
if (applicableMethods.get(invocation.resolveMethodBinding()) != null) {
System.out.println("[DEBUG] Invocation of applicable method found");
return true;
}
return false;
}
/*
* Returns true iff Node is a one-line private method that returns the result of
* a null check
*/
private boolean isApplicableImpl(@NonNull MethodDeclaration declaration) {
// getReturnType() is deprecated and replaced by getReturnType2()
Type retType = declaration.getReturnType2();
boolean returnsBoolean = (retType.isPrimitiveType()
&& ((PrimitiveType) retType).getPrimitiveTypeCode() == PrimitiveType.BOOLEAN);
if (!(returnsBoolean)) {
return false;
}
// Ensure the method declaration is private
if (!((declaration.getModifiers() & Modifier.PRIVATE) == Modifier.PRIVATE)) {
return false;
}
// Checks if there are any parameters
// TODO: Make work with parameters in case of methods that take variables to
// check as inputs, such as `checkObject(Object o) { return o !=null }}`
boolean hasParams = declaration.parameters().size() > 0;
if (hasParams) {
return false;
}
Block body = declaration.getBody();
if (body == null) {
return false;
}
@SuppressWarnings("unchecked") // Silence type warnings; statements() documentation guarantees type is valid.
List<Statement> stmts = (List<Statement>) body.statements();
boolean isOneLine = stmts.size() == 1;
if (!isOneLine) {
return false;
}
Statement stmt = stmts.get(0);
if (!(stmt instanceof ReturnStatement)) {
return false;
}
// Checks that the return statement is of a single equality check
Expression retExpr = ((ReturnStatement) stmt).getExpression();
if (retExpr == null || !(retExpr instanceof InfixExpression)) {
return false;
}
InfixExpression infix = (InfixExpression) retExpr;
if ((infix.getOperator() == InfixExpression.Operator.NOT_EQUALS
|| infix.getOperator() == InfixExpression.Operator.EQUALS)) {
Expression leftOperand = infix.getLeftOperand();
Expression rightOperand = infix.getRightOperand();
if ((isValidOperand(leftOperand) && rightOperand instanceof NullLiteral)
|| (isValidOperand(rightOperand) && leftOperand instanceof NullLiteral)) {
System.out.println("[DEBUG] Found one line null check method: " + declaration.getName());
IMethodBinding binding = declaration.resolveBinding();
if (binding == null) {
return false;
}
applicableMethods.put((binding), retExpr);
}
}
return false;
}
/*
* Returns true iff the provided expression can be on one side of a refactorable
* null equality check, i.e. it represents a valid variable or constant.
*/
private boolean isValidOperand(@NonNull Expression operand) {
return (operand instanceof SimpleName || operand instanceof FieldAccess || operand instanceof QualifiedName);
}
@Override
public void apply(@NonNull ASTNode node, @NonNull ASTRewrite rewriter) {
// Check if Method Invocation is in applicableMethods
if (node instanceof MethodInvocation invocation) {
replace(node, rewriter, invocation);
} else if (node instanceof PrefixExpression prefix && prefix.getOperator() == PrefixExpression.Operator.NOT
&& prefix.getOperand() instanceof MethodInvocation invocation) {
replace(node, rewriter, invocation);
}
}
private void replace(@NonNull ASTNode node, @NonNull ASTRewrite rewriter, @NonNull MethodInvocation invocation) {
IMethodBinding binding = invocation.resolveMethodBinding();
if (binding == null) {
return;
}
Expression expr = (applicableMethods.get(binding));
if (expr == null) {
System.err.println("Cannot find applicable method for refactoring. ");
return;
}
AST ast = node.getAST();
ParenthesizedExpression pExpr = ast.newParenthesizedExpression();
Expression copiedExpression = (Expression) ASTNode.copySubtree(ast, expr);
System.out.println("Expression: " + expr + "\nCopied expression: " + copiedExpression);
pExpr.setExpression(copiedExpression);
System.out.println("Refactoring " + node + "\n\tReplacing \n\t" + invocation + "\n\tWith \n\t" + pExpr);
rewriter.replace(invocation, pExpr, new TextEditGroup(""));
}
}