Java tutorial
/* * AutoRefactor - Eclipse plugin to automatically refactor Java code bases. * * Copyright (C) 2013-2016 Jean-Nol Rouvignac - initial API and implementation * Copyright (C) 2016 Fabrice Tiercelin - Make sure we do not visit again modified nodes * 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 * <http://www.gnu.org/licenses/>. * * * 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.Assignment; import org.eclipse.jdt.core.dom.Block; import org.eclipse.jdt.core.dom.Expression; import org.eclipse.jdt.core.dom.ExpressionStatement; import org.eclipse.jdt.core.dom.FieldDeclaration; import org.eclipse.jdt.core.dom.IMethodBinding; import org.eclipse.jdt.core.dom.IfStatement; import org.eclipse.jdt.core.dom.InfixExpression; import org.eclipse.jdt.core.dom.MethodInvocation; import org.eclipse.jdt.core.dom.Modifier.ModifierKeyword; 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.TypeDeclaration; import org.eclipse.jdt.core.dom.VariableDeclarationFragment; import org.eclipse.jdt.core.dom.VariableDeclarationStatement; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.internal.corext.dom.ASTNodes; import static org.autorefactor.refactoring.ASTHelper.*; import static org.eclipse.jdt.core.dom.ASTNode.*; import java.util.LinkedList; import java.util.List; import org.autorefactor.refactoring.ASTBuilder; import org.autorefactor.refactoring.Refactorings; /* * TODO when findViewById is reusing a local variable, * the viewholderitem will create a new field with duplicate name. * Possible solution: use the id names instead of var names */ /** See {@link #getDescription()} method. */ public class ViewHolderRefactoring extends AbstractRefactoringRule { @Override public String getDescription() { return "Optimization for Android applications to optimize getView routines. " + "It allows reducing the calls to inflate and getViewById Android " + "API methods."; } @Override public String getName() { return "ViewHolderRefactoring"; } @Override public boolean visit(MethodDeclaration node) { final ASTBuilder b = this.ctx.getASTBuilder(); final Refactorings r = this.ctx.getRefactorings(); IMethodBinding methodBinding = node.resolveBinding(); if (methodBinding != null && isMethod(methodBinding, "android.widget.Adapter", "getView", "int", "android.view.View", "android.view.ViewGroup")) { GetViewVisitor visitor = new GetViewVisitor(); Block body = node.getBody(); if (body != null) { body.accept(visitor); if (!visitor.usesConvertView && visitor.viewVariable != null && !visitor.isInflateInsideIf()) { // Transform tree //Create If statement IfStatement ifStatement = b.getAST().newIfStatement(); // test-clause InfixExpression infixExpression = b.getAST().newInfixExpression(); infixExpression.setOperator(InfixExpression.Operator.EQUALS); infixExpression.setLeftOperand(b.simpleName("convertView")); infixExpression.setRightOperand(b.getAST().newNullLiteral()); ifStatement.setExpression(infixExpression); //then Assignment assignment = b.assign(b.simpleName("convertView"), Assignment.Operator.ASSIGN, b.copy(visitor.getInflateExpression())); Block thenBlock = b.block(b.getAST().newExpressionStatement(assignment)); ifStatement.setThenStatement(thenBlock); r.insertBefore(ifStatement, visitor.viewAssignmentStatement); // assign to local view variable when necessary if (!"convertView".equals(visitor.viewVariable.getIdentifier())) { Statement assignConvertViewToView = null; if (visitor.viewVariableDeclarationFragment != null) { assignConvertViewToView = b.declare(visitor.viewVariable.resolveTypeBinding().getName(), b.copy(visitor.viewVariable), b.simpleName("convertView")); } else if (visitor.viewVariableAssignment != null) { assignConvertViewToView = b.getAST() .newExpressionStatement(b.assign(b.copy(visitor.viewVariable), Assignment.Operator.ASSIGN, b.simpleName("convertView"))); } if (assignConvertViewToView != null) { r.insertBefore(assignConvertViewToView, visitor.viewAssignmentStatement); } } // make sure method returns the view to be reused DELETEME if (visitor.returnStatement != null) { r.insertAfter(b.return0(b.copy(visitor.viewVariable)), visitor.returnStatement); r.remove(visitor.returnStatement); } //Optimize findViewById calls FindViewByIdVisitor findViewByIdVisitor = new FindViewByIdVisitor(); body.accept(findViewByIdVisitor); if (findViewByIdVisitor.items.size() > 0) { //create ViewHolderItem class TypeDeclaration viewHolderItemDeclaration = b.getAST().newTypeDeclaration(); viewHolderItemDeclaration.setName(b.simpleName("ViewHolderItem")); List<ASTNode> viewItemsDeclarations = viewHolderItemDeclaration.bodyDeclarations(); for (FindViewByIdVisitor.FindViewByIdItem item : findViewByIdVisitor.items) { VariableDeclarationFragment declarationFragment = b.getAST() .newVariableDeclarationFragment(); SimpleName simpleName = b.simpleName(item.variable.getIdentifier()); declarationFragment.setName(simpleName); FieldDeclaration fieldDeclaration = b.getAST().newFieldDeclaration(declarationFragment); fieldDeclaration.setType(b.getAST() .newSimpleType(b.simpleName(item.variable.resolveTypeBinding().getName()))); viewItemsDeclarations.add(fieldDeclaration); } viewHolderItemDeclaration.modifiers() .add(b.getAST().newModifier(ModifierKeyword.STATIC_KEYWORD)); r.insertBefore(viewHolderItemDeclaration, node); // create viewhHolderItem object VariableDeclarationStatement viewHolderItemVariableDeclaration = b.declare("ViewHolderItem", b.simpleName("viewHolderItem"), null); r.insertAt(viewHolderItemVariableDeclaration, 0, Block.STATEMENTS_PROPERTY, body); //initialize viewHolderItem Assignment viewHolderItemInitialization = b.assign(b.simpleName("viewHolderItem"), Assignment.Operator.ASSIGN, b.new0("ViewHolderItem")); thenBlock.statements().add(b.getAST().newExpressionStatement(viewHolderItemInitialization)); // Assign findViewById to ViewHolderItem for (FindViewByIdVisitor.FindViewByIdItem item : findViewByIdVisitor.items) { //ensure we are accessing to convertView object QualifiedName qualifiedName = b.getAST().newQualifiedName(b.name("viewHolderItem"), b.simpleName(item.variable.getIdentifier())); item.findViewByIdInvocation.setExpression(b.simpleName("convertView")); Assignment itemAssignment = b.assign(qualifiedName, Assignment.Operator.ASSIGN, (Expression) ASTNode.copySubtree(b.getAST(), item.findViewByIdExpression)); thenBlock.statements().add(b.getAST().newExpressionStatement(itemAssignment)); //replace previous fidnviewbyid with accesses to viewHolderItem QualifiedName viewHolderItemFieldAccessQualifiedName = b.getAST().newQualifiedName( b.name("viewHolderItem"), b.simpleName(item.variable.getIdentifier())); r.replace(item.findViewByIdExpression, b.copy(qualifiedName)); } //store viewHolderItem in convertView MethodInvocation setTagInvocation = b.invoke("convertView", "setTag", b.simpleName("viewHolderItem")); thenBlock.statements().add(b.getAST().newExpressionStatement(setTagInvocation)); //retrieve viewHolderItem from convertView ifStatement .setElseStatement(b.block(b.getAST() .newExpressionStatement(b.assign(b.simpleName("viewHolderItem"), Assignment.Operator.ASSIGN, b.cast("ViewHolderItem", b.invoke("convertView", "getTag")))))); } r.remove(visitor.viewAssignmentStatement); return DO_NOT_VISIT_SUBTREE; } } } return VISIT_SUBTREE; } public static boolean isInflateMethod(MethodInvocation node) { return isMethod(node, "android.view.LayoutInflater", "inflate", "int", "android.view.ViewGroup") || isMethod(node, "android.view.LayoutInflater", "inflate", "int", "android.view.ViewGroup", "boolean") || isMethod(node, "android.view.LayoutInflater", "inflate", "org.xmlpull.v1.XmlPullParser", "android.view.ViewGroup") || isMethod(node, "android.view.LayoutInflater", "inflate", "org.xmlpull.v1.XmlPullParser", "android.view.ViewGroup", "boolean"); } public class GetViewVisitor extends ASTVisitor { public boolean usesConvertView = false; public SimpleName viewVariable = null; public Statement viewAssignmentStatement; public VariableDeclarationFragment viewVariableDeclarationFragment = null; public Assignment viewVariableAssignment = null; public ReturnStatement returnStatement = null; GetViewVisitor() { } @Override public boolean visit(SimpleName node) { if (node.getIdentifier() == "convertView" && ASTNodes.getParent(node, ASTNode.RETURN_STATEMENT) == null) { this.usesConvertView = true; return DO_NOT_VISIT_SUBTREE; } return VISIT_SUBTREE; } public boolean visit(MethodInvocation node) { if (isInflateMethod(node)) { this.viewVariableDeclarationFragment = (VariableDeclarationFragment) ASTNodes.getParent(node, ASTNode.VARIABLE_DECLARATION_FRAGMENT); if (viewVariableDeclarationFragment != null) { this.viewVariable = viewVariableDeclarationFragment.getName(); this.viewAssignmentStatement = (Statement) ASTNodes.getParent(viewVariableDeclarationFragment, ASTNode.VARIABLE_DECLARATION_STATEMENT); } else { this.viewVariableAssignment = (Assignment) ASTNodes.getParent(node, ASTNode.ASSIGNMENT); if (viewVariableAssignment != null) { this.viewVariable = (SimpleName) viewVariableAssignment.getLeftHandSide(); this.viewAssignmentStatement = (ExpressionStatement) ASTNodes .getParent(viewVariableAssignment, ASTNode.EXPRESSION_STATEMENT); } } return DO_NOT_VISIT_SUBTREE; } return VISIT_SUBTREE; } public boolean visit(ReturnStatement node) { this.returnStatement = node; return VISIT_SUBTREE; } public boolean isInflateInsideIf() { if (this.viewAssignmentStatement != null) { if (ASTNodes.getParent(this.viewAssignmentStatement, ASTNode.IF_STATEMENT) != null) { return true; } else if (ASTNodes.getParent(this.viewAssignmentStatement, ASTNode.SWITCH_STATEMENT) != null) { return true; } else { //check whether inflate is inside a conditional assignment Expression inflateExpression = this.getInflateExpression(); if (inflateExpression != null && inflateExpression.getNodeType() == ASTNode.CONDITIONAL_EXPRESSION) { return true; } } } return false; } public Expression getInflateExpression() { if (this.viewVariableDeclarationFragment != null) { return this.viewVariableDeclarationFragment.getInitializer(); } else if (this.viewVariableAssignment != null) { return this.viewVariableAssignment.getRightHandSide(); } return null; } } static class FindViewByIdVisitor extends ASTVisitor { public List<FindViewByIdItem> items = new LinkedList<FindViewByIdItem>(); FindViewByIdVisitor() { } static class FindViewByIdItem { SimpleName variable; Expression findViewByIdExpression; Statement findViewByIdAssignment; VariableDeclarationFragment findViewByIdDeclarationFragment; Assignment findViewByIdVariableAssignment; MethodInvocation findViewByIdInvocation; FindViewByIdItem(MethodInvocation node) { this.setAssignment(node); } public void setAssignment(MethodInvocation node) { this.findViewByIdInvocation = node; this.findViewByIdDeclarationFragment = (VariableDeclarationFragment) ASTNodes.getParent(node, ASTNode.VARIABLE_DECLARATION_FRAGMENT); if (this.findViewByIdDeclarationFragment != null) { this.variable = this.findViewByIdDeclarationFragment.getName(); this.findViewByIdAssignment = (Statement) ASTNodes.getParent( this.findViewByIdDeclarationFragment, ASTNode.VARIABLE_DECLARATION_STATEMENT); this.findViewByIdExpression = this.findViewByIdDeclarationFragment.getInitializer(); } else { this.findViewByIdVariableAssignment = (Assignment) ASTNodes.getParent(node, ASTNode.ASSIGNMENT); if (this.findViewByIdVariableAssignment != null) { this.variable = (SimpleName) this.findViewByIdVariableAssignment.getLeftHandSide(); this.findViewByIdAssignment = (ExpressionStatement) ASTNodes .getParent(this.findViewByIdVariableAssignment, ASTNode.EXPRESSION_STATEMENT); this.findViewByIdExpression = this.findViewByIdVariableAssignment.getRightHandSide(); } } } } @Override public boolean visit(MethodInvocation node) { if (isMethod(node, "android.view.View", "findViewById", "int")) { FindViewByIdItem item = new FindViewByIdItem(node); items.add(item); } return VISIT_SUBTREE; } } }