From b25b9db390abd3afeb7661c2a9b34c0ce2945fd9 Mon Sep 17 00:00:00 2001 From: Luis Cruz Date: Tue, 18 Oct 2016 19:37:08 +0100 Subject: [PATCH] Android DrawAllocation. Closes #211 --- .../rules/AllRefactoringRules.java | 1 + .../AndroidDrawAllocationRefactoring.java | 166 ++++++++++++++++++ .../AndroidDrawAllocationSample.java | 97 ++++++++++ .../AndroidDrawAllocationSample.java | 100 +++++++++++ 4 files changed, 364 insertions(+) create mode 100644 plugin/src/main/java/org/autorefactor/refactoring/rules/AndroidDrawAllocationRefactoring.java create mode 100644 samples/src/test/java/org/autorefactor/refactoring/rules/samples_in/AndroidDrawAllocationSample.java create mode 100644 samples/src/test/java/org/autorefactor/refactoring/rules/samples_out/AndroidDrawAllocationSample.java diff --git a/plugin/src/main/java/org/autorefactor/refactoring/rules/AllRefactoringRules.java b/plugin/src/main/java/org/autorefactor/refactoring/rules/AllRefactoringRules.java index 08c3ea646..2d2adc43e 100644 --- a/plugin/src/main/java/org/autorefactor/refactoring/rules/AllRefactoringRules.java +++ b/plugin/src/main/java/org/autorefactor/refactoring/rules/AllRefactoringRules.java @@ -112,6 +112,7 @@ public static List getAllRefactoringRules() { new TestNGAssertRefactoring(), new ReplaceQualifiedNamesBySimpleNamesRefactoring(), new RemoveEmptyLinesRefactoring(), + new AndroidDrawAllocationRefactoring(), new AndroidWakeLockRefactoring(), new AndroidViewHolderRefactoring(), new SwitchRefactoring()); diff --git a/plugin/src/main/java/org/autorefactor/refactoring/rules/AndroidDrawAllocationRefactoring.java b/plugin/src/main/java/org/autorefactor/refactoring/rules/AndroidDrawAllocationRefactoring.java new file mode 100644 index 000000000..c6ba123d6 --- /dev/null +++ b/plugin/src/main/java/org/autorefactor/refactoring/rules/AndroidDrawAllocationRefactoring.java @@ -0,0 +1,166 @@ +/* + * AutoRefactor - Eclipse plugin to automatically refactor Java code bases. + * + * Copyright (C) 2016 Luis Cruz - Android Refactoring Rules + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program under LICENSE-GNUGPL. If not, see + * . + * + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution under LICENSE-ECLIPSE, and is + * available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.autorefactor.refactoring.rules; + +import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.ASTVisitor; +import org.eclipse.jdt.core.dom.CastExpression; +import org.eclipse.jdt.core.dom.ClassInstanceCreation; +import org.eclipse.jdt.core.dom.Expression; +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.MethodInvocation; +import org.eclipse.jdt.core.dom.SimpleName; +import org.eclipse.jdt.core.dom.SimpleType; +import org.eclipse.jdt.core.dom.Statement; +import org.eclipse.jdt.core.dom.VariableDeclarationFragment; +import org.eclipse.jdt.core.dom.VariableDeclarationStatement; +import org.eclipse.jdt.core.dom.MethodDeclaration; + +import static org.autorefactor.refactoring.ASTHelper.*; +import static org.eclipse.jdt.core.dom.ASTNode.*; + +import java.util.Arrays; +import java.util.List; + +import org.autorefactor.refactoring.ASTBuilder; +import org.autorefactor.refactoring.Refactorings; + +/** See {@link #getDescription()} method. */ +public class AndroidDrawAllocationRefactoring extends AbstractRefactoringRule { + + @Override + public String getDescription() { + return "Optimization for Android applications to avoid the allocation of" + + " objects inside drawing routines. "; + } + + @Override + public String getName() { + return "Android DrawAllocation"; + } + + @Override + public boolean visit(MethodDeclaration node) { + if (isMethod(node, "android.widget.TextView", "onDraw", "android.graphics.Canvas")) { + node.accept(new OnDrawTransformer(this.ctx, node)); + } + return VISIT_SUBTREE; + } + + static class OnDrawTransformer extends ASTVisitor { + private RefactoringContext ctx; + private MethodDeclaration onDrawDeclaration; + + public OnDrawTransformer(RefactoringContext ctx, MethodDeclaration onDrawDeclaration) { + this.ctx = ctx; + this.onDrawDeclaration = onDrawDeclaration; + } + + public boolean isTypeBindingSubclassOf(ITypeBinding typeBinding, List superClassStrings) { + ITypeBinding superClass = typeBinding; + while (superClass != null) { + if (superClassStrings.contains(superClass.getErasure().getName())) { + return true; + } + superClass = superClass.getSuperclass(); + } + return false; + } + + @Override + public boolean visit(VariableDeclarationFragment node) { + Expression initializer = node.getInitializer(); + if (initializer != null) { + if (initializer.getNodeType() == ASTNode.CAST_EXPRESSION) { + initializer = ((CastExpression) initializer).getExpression(); + } + if (initializer.getNodeType() == ASTNode.CLASS_INSTANCE_CREATION) { + ClassInstanceCreation classInstanceCreation = (ClassInstanceCreation) initializer; + InitializerVisitor initializerVisitor = new InitializerVisitor(); + classInstanceCreation.accept(initializerVisitor); + if (initializerVisitor.initializerCanBeExtracted) { + Statement declarationStatement = getAncestor(node, VariableDeclarationStatement.class); + if (declarationStatement != null) { + final ASTBuilder b = this.ctx.getASTBuilder(); + final Refactorings r = this.ctx.getRefactorings(); + // Deal with collections + if (isTypeBindingSubclassOf(node.getName().resolveTypeBinding(), + Arrays.asList("AbstractCollection", "Collection", "List", "AbstractMap", "Map"))) { + // It should only works with allocations of + // empty collections. + // Approximation: work only for 0 args + if (classInstanceCreation.arguments().size() == 0) { + // allocate object outside onDraw + r.insertBefore(b.move(declarationStatement), onDrawDeclaration); + // call collection.clear() in the end of onDraw + ASTNode clearNode = b.toStmt(b.invoke(node.getName().getIdentifier(), "clear")); + List bodyStatements = statements(onDrawDeclaration.getBody()); + Statement lastStatement = bodyStatements.get(bodyStatements.size() - 1); + if (ASTNode.RETURN_STATEMENT == lastStatement.getNodeType()) { + r.insertBefore(clearNode, lastStatement); + } else { + r.insertAfter(clearNode, lastStatement); + } + return DO_NOT_VISIT_SUBTREE; + } + } else { + r.insertBefore(b.move(declarationStatement), onDrawDeclaration); + return DO_NOT_VISIT_SUBTREE; + } + } + } + } + } + return VISIT_SUBTREE; + } + } + + // This visitor intends to make sure that our initializer does not use variables. + // We assume that, if they are not present, initializer is constant. + static class InitializerVisitor extends ASTVisitor { + private boolean initializerCanBeExtracted = true; + + @Override + public boolean visit(MethodInvocation node) { + initializerCanBeExtracted = false; + return DO_NOT_VISIT_SUBTREE; + } + + // Skip SimpleNames inside SimpleType like the following + // HashMap(); + //(this should keep initializerCanBeExtracted=true) + @Override + public boolean visit(SimpleType node) { + return DO_NOT_VISIT_SUBTREE; + } + + @Override + public boolean visit(SimpleName node) { + initializerCanBeExtracted = false; + return DO_NOT_VISIT_SUBTREE; + } + } +} diff --git a/samples/src/test/java/org/autorefactor/refactoring/rules/samples_in/AndroidDrawAllocationSample.java b/samples/src/test/java/org/autorefactor/refactoring/rules/samples_in/AndroidDrawAllocationSample.java new file mode 100644 index 000000000..7a19f7c4b --- /dev/null +++ b/samples/src/test/java/org/autorefactor/refactoring/rules/samples_in/AndroidDrawAllocationSample.java @@ -0,0 +1,97 @@ +/* + * AutoRefactor - Eclipse plugin to automatically refactor Java code bases. + * + * Copyright (C) 2016 Luis Cruz - Android Refactoring + * Copyright (C) 2016 Jean-Noël Rouvignac - code cleanups + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program under LICENSE-GNUGPL. If not, see + * . + * + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution under LICENSE-ECLIPSE, and is + * available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.autorefactor.refactoring.rules.samples_in; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import android.content.Context; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.widget.Button; + +public class AndroidDrawAllocationSample extends Button { + + private Rect cachedRect; + + public AndroidDrawAllocationSample(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected void onDraw(android.graphics.Canvas canvas) { + super.onDraw(canvas); + + // Various allocations: + new String("foo"); + String s = new String("bar"); + + Integer i = new Integer(5); + + // Cached object initialized lazily: should not complain about these + if (cachedRect == null) { + cachedRect = new Rect(0, 0, 100, 100); + } + if (cachedRect == null || cachedRect.width() != 50) { + cachedRect = new Rect(0, 0, 50, 100); + } + + boolean b = Boolean.valueOf(true); // auto-boxing + + Integer i2 = new Integer(i); + Integer i3 = (Integer) new Integer(2); + Map myOtherMap = new HashMap(); + + // Non-allocations + super.animate(); + int x = 4 + '5'; + + // This will involve allocations, but we don't track + // inter-procedural stuff here + someOtherMethod(); + } + + void someOtherMethod() { + // Allocations are accepted here + new String("foo"); + String s = new String("bar"); + boolean b = Boolean.valueOf(true); + } + + public class DrawAllocationSampleTwo extends Button { + public DrawAllocationSampleTwo(Context context) { + super(context); + } + @Override + protected void onDraw(android.graphics.Canvas canvas) { + super.onDraw(canvas); + List array = new ArrayList(); + return; + } + } +} diff --git a/samples/src/test/java/org/autorefactor/refactoring/rules/samples_out/AndroidDrawAllocationSample.java b/samples/src/test/java/org/autorefactor/refactoring/rules/samples_out/AndroidDrawAllocationSample.java new file mode 100644 index 000000000..7df66e5d5 --- /dev/null +++ b/samples/src/test/java/org/autorefactor/refactoring/rules/samples_out/AndroidDrawAllocationSample.java @@ -0,0 +1,100 @@ +/* + * AutoRefactor - Eclipse plugin to automatically refactor Java code bases. + * + * Copyright (C) 2016 Luis Cruz - Android Refactoring + * Copyright (C) 2016 Jean-Noël Rouvignac - code cleanups + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program under LICENSE-GNUGPL. If not, see + * . + * + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution under LICENSE-ECLIPSE, and is + * available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.autorefactor.refactoring.rules.samples_out; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import android.content.Context; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.widget.Button; + +public class AndroidDrawAllocationSample extends Button { + + private Rect cachedRect; + + public AndroidDrawAllocationSample(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + String s = new String("bar"); + + Integer i = new Integer(5); + + Integer i3 = (Integer) new Integer(2); + + Map myOtherMap = new HashMap(); + + @Override + protected void onDraw(android.graphics.Canvas canvas) { + super.onDraw(canvas); + + // Various allocations: + new String("foo"); + // Cached object initialized lazily: should not complain about these + if (cachedRect == null) { + cachedRect = new Rect(0, 0, 100, 100); + } + if (cachedRect == null || cachedRect.width() != 50) { + cachedRect = new Rect(0, 0, 50, 100); + } + + boolean b = Boolean.valueOf(true); // auto-boxing + + Integer i2 = new Integer(i); + // Non-allocations + super.animate(); + int x = 4 + '5'; + + // This will involve allocations, but we don't track + // inter-procedural stuff here + someOtherMethod(); + myOtherMap.clear(); + } + + void someOtherMethod() { + // Allocations are accepted here + new String("foo"); + String s = new String("bar"); + boolean b = Boolean.valueOf(true); + } + + public class DrawAllocationSampleTwo extends Button { + public DrawAllocationSampleTwo(Context context) { + super(context); + } + List array = new ArrayList(); + @Override + protected void onDraw(android.graphics.Canvas canvas) { + super.onDraw(canvas); + array.clear(); + return; + } + } +}