org.autorefactor.refactoring.rules.WakeLockRefactoring.java Source code

Java tutorial

Introduction

Here is the source code for org.autorefactor.refactoring.rules.WakeLockRefactoring.java

Source

/*
 * 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
 *
 * 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.Annotation;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.BodyDeclaration;
import org.eclipse.jdt.core.dom.ChildListPropertyDescriptor;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.ExpressionStatement;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.IfStatement;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.Modifier.ModifierKeyword;
import org.eclipse.jdt.core.dom.NormalAnnotation;
import org.eclipse.jdt.core.dom.PostfixExpression;
import org.eclipse.jdt.core.dom.PrefixExpression;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.core.dom.SuperMethodInvocation;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
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.List;

import org.autorefactor.refactoring.ASTBuilder;
import org.autorefactor.refactoring.Refactorings;

/** See {@link #getDescription()} method. */
public class WakeLockRefactoring extends AbstractRefactoringRule {

    @Override
    public String getDescription() {
        return "Failing to release a wakelock properly can keep the Android device in a high "
                + "power mode, which reduces battery life. There are several causes of this, such "
                + "as releasing the wake lock in onDestroy() instead of in onPause(), failing to "
                + "call release() in all possible code paths after an acquire(), and so on.";
    }

    @Override
    public String getName() {
        return "WakeLockRefactoring";
    }

    @Override
    public boolean visit(MethodInvocation node) {
        if (isMethod(node, "android.os.PowerManager.WakeLock", "release")) {
            // check whether it is being called in onDestroy
            final Refactorings r = this.ctx.getRefactorings();
            final ASTBuilder b = this.ctx.getASTBuilder();
            MethodDeclaration enclosingMethod = (MethodDeclaration) ASTNodes.getParent(node,
                    ASTNode.METHOD_DECLARATION);
            if (isMethod(enclosingMethod.resolveBinding(), "android.app.Activity", "onDestroy")) {
                TypeDeclaration typeDeclaration = (TypeDeclaration) ASTNodes.getParent(enclosingMethod,
                        TypeDeclaration.class);
                MethodDeclaration onPauseMethod = findMethodOfType("onPause", typeDeclaration);
                if (onPauseMethod != null && node.getParent().getNodeType() == ASTNode.EXPRESSION_STATEMENT) {
                    Statement releaseNode = createWakelockReleaseNode(node);
                    r.remove(node.getParent());
                    r.insertAt(releaseNode, onPauseMethod.getBody().statements().size(), Block.STATEMENTS_PROPERTY,
                            onPauseMethod.getBody());
                    return DO_NOT_VISIT_SUBTREE;
                }
                /* If it reaches this part of the code, it
                 * means it did not find onPause method in the class.
                 */
                MethodDeclaration onPauseDeclaration = createOnPauseDeclaration();

                // add onPause declaration to the Activity
                r.insertAfter(onPauseDeclaration, enclosingMethod);
                return DO_NOT_VISIT_SUBTREE;

            }
        } else if (isMethod(node, "android.os.PowerManager.WakeLock", "acquire")) {
            final Refactorings r = this.ctx.getRefactorings();
            final ASTBuilder b = this.ctx.getASTBuilder();
            TypeDeclaration typeDeclaration = (TypeDeclaration) ASTNodes.getParent(node, ASTNode.TYPE_DECLARATION);
            ReleasePresenceChecker releasePresenceChecker = new ReleasePresenceChecker();
            typeDeclaration.accept(releasePresenceChecker);
            if (!releasePresenceChecker.releasePresent) {
                Statement releaseNode = createWakelockReleaseNode(node);
                MethodDeclaration onPauseMethod = findMethodOfType("onPause", typeDeclaration);
                if (onPauseMethod != null && node.getParent().getNodeType() == ASTNode.EXPRESSION_STATEMENT) {
                    r.insertAt(releaseNode, onPauseMethod.getBody().statements().size(), Block.STATEMENTS_PROPERTY,
                            onPauseMethod.getBody());
                    return DO_NOT_VISIT_SUBTREE;
                } else {
                    MethodDeclaration onPauseDeclaration = createOnPauseDeclaration();
                    /* TODO @JnRouvignac, instead of using insertAt, calling 
                     * typeDeclaration.bodyDeclarations().add(onPauseDeclaration);
                     * would be more intuitive.
                     */
                    r.insertAt(onPauseDeclaration, typeDeclaration.bodyDeclarations().size(),
                            typeDeclaration.getBodyDeclarationsProperty(), typeDeclaration);
                    return DO_NOT_VISIT_SUBTREE;
                }
            }
        }
        return VISIT_SUBTREE;
    }

    private Statement createWakelockReleaseNode(MethodInvocation methodInvocation) {
        final ASTBuilder b = this.ctx.getASTBuilder();
        IfStatement ifStatement = b.getAST().newIfStatement();
        // test-clause
        PrefixExpression prefixExpression = b.getAST().newPrefixExpression();
        prefixExpression.setOperator(PrefixExpression.Operator.NOT);
        MethodInvocation isHeldInvocation = b.getAST().newMethodInvocation();
        isHeldInvocation.setName(b.simpleName("isHeld"));
        isHeldInvocation.setExpression(b.copyExpression(methodInvocation));
        prefixExpression.setOperand(isHeldInvocation);
        ifStatement.setExpression(prefixExpression);
        // then
        MethodInvocation releaseInvocation = b.getAST().newMethodInvocation();
        releaseInvocation.setName(b.simpleName("release"));
        releaseInvocation.setExpression(b.copyExpression(methodInvocation));
        ExpressionStatement releaseExpressionStatement = b.getAST().newExpressionStatement(releaseInvocation);
        ifStatement.setThenStatement(b.block(releaseExpressionStatement));
        return ifStatement;
    }

    private MethodDeclaration createOnPauseDeclaration() {
        final ASTBuilder b = this.ctx.getASTBuilder();
        MethodDeclaration onPauseDeclaration = b.getAST().newMethodDeclaration();
        onPauseDeclaration.setName(b.simpleName("onPause"));
        //
        NormalAnnotation annotation = b.getAST().newNormalAnnotation();
        annotation.setTypeName(b.name("Override"));
        onPauseDeclaration.modifiers().add(annotation);
        //
        Modifier protectedModifier = b.getAST().newModifier(ModifierKeyword.PROTECTED_KEYWORD);
        onPauseDeclaration.modifiers().add(protectedModifier);
        //
        SuperMethodInvocation superMethodInvocation = b.getAST().newSuperMethodInvocation();
        superMethodInvocation.setName(b.simpleName("onPause"));

        //
        ASTRewrite rewriter = ASTRewrite.create(b.getAST());
        onPauseDeclaration.setBody(b.block(b.newlinePlaceholder(),
                b.getAST().newExpressionStatement(superMethodInvocation), b.newlinePlaceholder()));
        return onPauseDeclaration;
    }

    private MethodDeclaration findMethodOfType(String methodToFind, TypeDeclaration typeDeclaration) {
        MethodDeclaration[] methods = typeDeclaration.getMethods();

        for (MethodDeclaration method : methods) {
            IMethodBinding methodBinding = method.resolveBinding();
            if (methodBinding != null && methodToFind.equals(methodBinding.getName())) {
                return method;
            }
        }
        return null;
    }

    public class ReleasePresenceChecker extends ASTVisitor {
        public boolean releasePresent = false;

        @Override
        public boolean visit(MethodInvocation node) {
            if (isMethod(node, "android.os.PowerManager.WakeLock", "release")) {
                this.releasePresent = true;
                return DO_NOT_VISIT_SUBTREE;
            }
            return VISIT_SUBTREE;
        }
    }
}