Skip to content

Commit 7f6cfc3

Browse files
committed
Android Recycle Rule. Closes #212
fixed audit reviews
1 parent 9387fc2 commit 7f6cfc3

File tree

4 files changed

+602
-0
lines changed

4 files changed

+602
-0
lines changed

plugin/src/main/java/org/autorefactor/refactoring/rules/AllRefactoringRules.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ public static List<RefactoringRule> getAllRefactoringRules() {
116116
new ReplaceQualifiedNamesBySimpleNamesRefactoring(),
117117
new RemoveEmptyLinesRefactoring(),
118118
new AndroidWakeLockRefactoring(),
119+
new AndroidRecycleRefactoring(),
119120
new SwitchRefactoring());
120121
}
121122

Lines changed: 336 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,336 @@
1+
/*
2+
* AutoRefactor - Eclipse plugin to automatically refactor Java code bases.
3+
*
4+
* Copyright (C) 2016 Luis Cruz - Android Refactoring Rules
5+
*
6+
* This program is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU General Public License as published by
8+
* the Free Software Foundation, either version 3 of the License, or
9+
* any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU General Public License
17+
* along with this program under LICENSE-GNUGPL. If not, see
18+
* <http://www.gnu.org/licenses/>.
19+
*
20+
*
21+
* All rights reserved. This program and the accompanying materials
22+
* are made available under the terms of the Eclipse Public License v1.0
23+
* which accompanies this distribution under LICENSE-ECLIPSE, and is
24+
* available at http://www.eclipse.org/legal/epl-v10.html
25+
*/
26+
package org.autorefactor.refactoring.rules;
27+
28+
import org.eclipse.jdt.core.dom.ASTNode;
29+
import org.eclipse.jdt.core.dom.ASTVisitor;
30+
import org.eclipse.jdt.core.dom.Assignment;
31+
import org.eclipse.jdt.core.dom.Block;
32+
import org.eclipse.jdt.core.dom.ExpressionStatement;
33+
import org.eclipse.jdt.core.dom.IMethodBinding;
34+
import org.eclipse.jdt.core.dom.ITypeBinding;
35+
import org.eclipse.jdt.core.dom.MethodInvocation;
36+
import org.eclipse.jdt.core.dom.SimpleName;
37+
import org.eclipse.jdt.core.dom.Statement;
38+
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
39+
import static org.autorefactor.refactoring.ASTHelper.*;
40+
import static org.eclipse.jdt.core.dom.ASTNode.*;
41+
import org.autorefactor.refactoring.ASTBuilder;
42+
import org.autorefactor.refactoring.ASTHelper;
43+
import org.autorefactor.refactoring.Refactorings;
44+
45+
/*
46+
* TODO when the last use of resource is as arg of a method invocation,
47+
* it should be assumed that the given method will take care of the release.
48+
* TODO Track local variables. E.g., when a TypedArray a is assigned to variable b,
49+
* release() should be called only in one variable.
50+
* TODO (low priority) check whether resources are being used after release.
51+
* TODO add support for FragmentTransaction.beginTransaction(). It can use method
52+
* chaining (which means local variable might not be present) and it can be released
53+
* by two methods: commit() and commitAllowingStateLoss().
54+
*/
55+
56+
/** See {@link #getDescription()} method. */
57+
public class AndroidRecycleRefactoring extends AbstractRefactoringRule {
58+
59+
@Override
60+
public String getDescription() {
61+
return "Many resources, such as TypedArrays, VelocityTrackers, etc., should be "
62+
+ "recycled (with a recycle()/close() call) after use. " + "Inspired from "
63+
+ "https://android.googlesource.com/platform/tools/base/+/master/lint/libs/lint-checks/src/main/"
64+
+ "java/com/android/tools/lint/checks/CleanupDetector.java";
65+
}
66+
67+
@Override
68+
public String getName() {
69+
return "RecycleRefactoring";
70+
}
71+
72+
private static boolean isMethodIgnoringParameters(MethodInvocation node, String typeQualifiedName,
73+
String methodName) {
74+
if (node == null) {
75+
return false;
76+
}
77+
final IMethodBinding methodBinding = node.resolveMethodBinding();
78+
if (methodBinding == null || !methodName.equals(methodBinding.getName())) {
79+
return false;
80+
}
81+
final ITypeBinding declaringClazz = methodBinding.getDeclaringClass();
82+
return instanceOf(declaringClazz, typeQualifiedName);
83+
}
84+
85+
private static boolean isMethodIgnoringParameters(MethodInvocation node, String typeQualifiedName,
86+
String[] methodNames) {
87+
boolean isSameMethod;
88+
for (String methodName : methodNames) {
89+
isSameMethod = isMethodIgnoringParameters(node, typeQualifiedName, methodName);
90+
if (isSameMethod) {
91+
return true;
92+
}
93+
}
94+
return false;
95+
}
96+
97+
private String methodNameToCleanupResource(MethodInvocation node) {
98+
if (isMethodIgnoringParameters(
99+
node,
100+
"android.database.sqlite.SQLiteDatabase",
101+
new String[]{"query", "rawQuery", "queryWithFactory", "rawQueryWithFactory"})
102+
) {
103+
return "close";
104+
} else if (isMethodIgnoringParameters(
105+
node,
106+
"android.content.ContentProvider",
107+
new String[]{"query" , "rawQuery", "queryWithFactory", "rawQueryWithFactory"})
108+
) {
109+
return "close";
110+
} else if (isMethodIgnoringParameters(
111+
node,
112+
"android.content.ContentResolver",
113+
new String[]{"query", "rawQuery", "queryWithFactory", "rawQueryWithFactory"})
114+
) {
115+
return "close";
116+
} else if (isMethodIgnoringParameters(
117+
node,
118+
"android.content.ContentProviderClient",
119+
new String[]{"query", "rawQuery", "queryWithFactory", "rawQueryWithFactory"})
120+
) {
121+
return "close";
122+
} else if (isMethodIgnoringParameters(
123+
node,
124+
"android.content.Context",
125+
"obtainStyledAttributes")
126+
) {
127+
return "recycle";
128+
} else if (isMethodIgnoringParameters(
129+
node,
130+
"android.content.res.Resources",
131+
new String[]{"obtainTypedArray", "obtainAttributes", "obtainStyledAttributes"})
132+
) {
133+
return "recycle";
134+
} else if (isMethod(
135+
node,
136+
"android.view.VelocityTracker",
137+
"obtain")
138+
) {
139+
return "recycle";
140+
} else if (isMethodIgnoringParameters(
141+
node,
142+
"android.os.Handler",
143+
"obtainMessage")
144+
) {
145+
return "recycle";
146+
} else if (isMethodIgnoringParameters(
147+
node,
148+
"android.os.Message",
149+
"obtain")
150+
) {
151+
return "recycle";
152+
} else if (isMethod(
153+
node,
154+
"android.view.MotionEvent",
155+
"obtainNoHistory", "android.view.MotionEvent")
156+
) {
157+
return "recycle";
158+
} else if (isMethodIgnoringParameters(
159+
node,
160+
"android.view.MotionEvent",
161+
"obtain")
162+
) {
163+
return "recycle";
164+
} else if (isMethod(
165+
node,
166+
"android.os.Parcel",
167+
"obtain")
168+
) {
169+
return "recycle";
170+
} else if (isMethodIgnoringParameters(
171+
node,
172+
"android.content.ContentResolver",
173+
"acquireContentProviderClient")
174+
) {
175+
return "release";
176+
}
177+
return null;
178+
}
179+
180+
@Override
181+
public boolean visit(MethodInvocation node) {
182+
String recycleMethodName = methodNameToCleanupResource(node);
183+
if (recycleMethodName != null) {
184+
SimpleName cursorExpression = null;
185+
ASTNode variableAssignmentNode = null;
186+
VariableDeclarationFragment variableDeclarationFragment =
187+
(VariableDeclarationFragment) getFirstAncestorOrNull(node, VariableDeclarationFragment.class);
188+
if (variableDeclarationFragment != null) {
189+
cursorExpression = variableDeclarationFragment.getName();
190+
variableAssignmentNode = variableDeclarationFragment;
191+
} else {
192+
Assignment variableAssignment = getAncestor(node, Assignment.class);
193+
cursorExpression = (SimpleName) variableAssignment.getLeftHandSide();
194+
variableAssignmentNode = variableAssignment;
195+
}
196+
// Check whether it has been closed
197+
ClosePresenceChecker closePresenceChecker = new ClosePresenceChecker(cursorExpression, recycleMethodName);
198+
VisitorDecorator visitor = new VisitorDecorator(variableAssignmentNode, cursorExpression,
199+
closePresenceChecker);
200+
Block block = getAncestor(node, Block.class);
201+
block.accept(visitor);
202+
if (!closePresenceChecker.isClosePresent()) {
203+
final ASTBuilder b = this.ctx.getASTBuilder();
204+
MethodInvocation closeInvocation = b.invoke(b.copy(cursorExpression), recycleMethodName);
205+
ExpressionStatement expressionStatement = b.getAST().newExpressionStatement(closeInvocation);
206+
Statement lastCursorAccess = closePresenceChecker.getLastCursorStatementInBlock(block);
207+
if (lastCursorAccess.getNodeType() != ASTNode.RETURN_STATEMENT) {
208+
final Refactorings r = this.ctx.getRefactorings();
209+
r.insertAfter(expressionStatement, lastCursorAccess);
210+
return DO_NOT_VISIT_SUBTREE;
211+
}
212+
}
213+
}
214+
return VISIT_SUBTREE;
215+
}
216+
217+
private class ClosePresenceChecker extends ASTVisitor {
218+
private boolean closePresent;
219+
private SimpleName lastCursorUse;
220+
private SimpleName cursorSimpleName;
221+
private String recycleMethodName;
222+
223+
/**
224+
* @param cursorSimpleName Variable name of cursor
225+
* @param recycleMethodName Recycle method name
226+
*/
227+
public ClosePresenceChecker(SimpleName cursorSimpleName, String recycleMethodName) {
228+
super();
229+
this.closePresent = false;
230+
this.lastCursorUse = null;
231+
this.cursorSimpleName = cursorSimpleName;
232+
this.recycleMethodName = recycleMethodName;
233+
}
234+
235+
236+
/**
237+
* @return the closePresent
238+
*/
239+
public boolean isClosePresent() {
240+
return closePresent;
241+
}
242+
243+
244+
/**
245+
* Returns the last statement in the block where the cursor was accessed
246+
* before being assigned again or destroyed.
247+
* @param block Block with the Statement that we want to find
248+
* @return
249+
*/
250+
public Statement getLastCursorStatementInBlock(Block block) {
251+
ASTNode lastCursorStatement = this.lastCursorUse.getParent();
252+
while (lastCursorStatement != null && !block.statements().contains(lastCursorStatement)) {
253+
lastCursorStatement = getAncestor(lastCursorStatement, Statement.class);
254+
}
255+
return (Statement) lastCursorStatement;
256+
}
257+
258+
@Override
259+
public boolean visit(MethodInvocation node) {
260+
if (isSameLocalVariable(cursorSimpleName, node.getExpression())) {
261+
if (this.recycleMethodName.equals(node.getName().getIdentifier())) {
262+
this.closePresent = true;
263+
return DO_NOT_VISIT_SUBTREE;
264+
}
265+
}
266+
return VISIT_SUBTREE;
267+
}
268+
269+
@Override
270+
public boolean visit(SimpleName node) {
271+
if (ASTHelper.isSameLocalVariable(node, cursorSimpleName)) {
272+
this.lastCursorUse = node;
273+
}
274+
return VISIT_SUBTREE;
275+
}
276+
277+
@Override
278+
public boolean visit(Assignment node) {
279+
if (ASTHelper.isSameLocalVariable(node.getLeftHandSide(), cursorSimpleName)) {
280+
return DO_NOT_VISIT_SUBTREE;
281+
}
282+
return VISIT_SUBTREE;
283+
}
284+
}
285+
286+
/*
287+
* This visitor selects a partial part of the block to make the visit I.e.,
288+
* it will only analyze the visitor from the variable assignment until the
289+
* next assignment or end of the block startNode is a Assignment or
290+
* VariableDeclarationFragment
291+
*/
292+
private class VisitorDecorator extends ASTVisitor {
293+
private ASTVisitor visitor;
294+
private SimpleName cursorSimpleName;
295+
private ASTVisitor specialVisitor;
296+
private ASTNode startNode;
297+
298+
private class NopVisitor extends ASTVisitor {
299+
}
300+
301+
VisitorDecorator(ASTNode startNode, SimpleName cursorSimpleName, ASTVisitor specialVisitor) {
302+
this.cursorSimpleName = cursorSimpleName;
303+
this.specialVisitor = specialVisitor;
304+
this.startNode = startNode;
305+
this.visitor = new NopVisitor();
306+
}
307+
308+
@Override
309+
public boolean visit(Assignment node) {
310+
if (node.equals(startNode)) {
311+
visitor = specialVisitor;
312+
} else if (visitor != null && ASTHelper.isSameLocalVariable(node.getLeftHandSide(), cursorSimpleName)) {
313+
visitor = new NopVisitor();
314+
}
315+
return visitor.visit(node);
316+
}
317+
318+
@Override
319+
public boolean visit(VariableDeclarationFragment node) {
320+
if (node.equals(startNode)) {
321+
visitor = specialVisitor;
322+
}
323+
return visitor.visit(node);
324+
}
325+
326+
@Override
327+
public boolean visit(SimpleName node) {
328+
return visitor.visit(node);
329+
}
330+
331+
@Override
332+
public boolean visit(MethodInvocation node) {
333+
return visitor.visit(node);
334+
}
335+
}
336+
}

0 commit comments

Comments
 (0)