Java tutorial
/** * Copyright (C) Mattia Gustarini * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.ide.eclipse.apt.internal.analysis; import java.util.Collection; import java.util.LinkedList; import java.util.List; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.InsnList; import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.MethodNode; /** * * This analyzer checks if PURE getters and setters are used to access and to set class fields internally in the same class * following the guideline:<br/> * @see <a href="http://developer.android.com/guide/practices/design/performance.html#internal_get_set">Avoid Internal Getters/Setters</a> * @author Mattia Gustarini * */ public final class InternalGetSetAnalyzer extends SpecificAnalyzer { private ClassNode mCurrentClass; private LinkedList<String> mSeenNormalMethods; private LinkedList<String> mSeenGetSetMethods; public InternalGetSetAnalyzer() { super("Internal Getters/Setters", "Avoid Internal Getters/Setters: set and access Class Fields in a direct way!"); mSeenNormalMethods = new LinkedList<String>(); mSeenGetSetMethods = new LinkedList<String>(); } /* (non-Javadoc) * @see com.android.ide.eclipse.apt.internal.analysis.SpecificAnalyzer#analyzeClass(org.objectweb.asm.tree.ClassNode) */ @Override protected Collection<Problem> analyzeClass(ClassNode classNode) { mCurrentClass = classNode; mSeenNormalMethods = new LinkedList<String>(); mSeenGetSetMethods = new LinkedList<String>(); return super.analyzeClass(classNode); } /* (non-Javadoc) * @see ch.usi.inf.gustarim.apt.analysis.SpecificAnalyzer#analyzeMethod(org.objectweb.asm.tree.MethodNode) */ @Override protected Collection<Problem> analyzeMethod(MethodNode methodNode) { final Collection<Problem> problems = new LinkedList<Problem>(); final InsnList instructions = methodNode.instructions; for (int i = 0; i < instructions.size(); i++) { final AbstractInsnNode instruction = instructions.get(i); final int op = instruction.getOpcode(); if (op == Opcodes.INVOKESTATIC || op == Opcodes.INVOKEVIRTUAL) { final MethodInsnNode method = (MethodInsnNode) instruction; if (isGetterOrSetter(method)) { final Problem problem = new Problem(instruction); problems.add(problem); } } } return problems; } /** * Checks if a method is a getter or a setter * @param method The method to be checked * @return True if a method is a getter/setter, false otherwise */ private boolean isGetterOrSetter(final MethodInsnNode method) { final String owner = method.owner; final String methodName = owner + method.name + method.desc; if (mSeenNormalMethods.contains(methodName)) { return false; } if (mSeenGetSetMethods.contains(methodName)) { return true; } boolean result = false; if (owner.equals(mCurrentClass.name)) { final MethodNode methodTest = findMethod(methodName); if (methodTest != null) { result = isGetter(methodTest); if (!result) { result = isSetter(methodTest); } } } if (result) { mSeenGetSetMethods.add(methodName); } else { mSeenNormalMethods.add(methodName); } return result; } /** * Finds a given method defined by its name encoded as a string * @param methodName The name of the method * @return The method node if exists, null otherwise */ @SuppressWarnings("unchecked") private MethodNode findMethod(final String methodName) { MethodNode method = null; final List<MethodNode> methodNodes = (List<MethodNode>) mCurrentClass.methods; for (final MethodNode methodNode : methodNodes) { final String name = mCurrentClass.name + methodNode.name + methodNode.desc; if (name.equals(methodName)) { method = methodNode; } } return method; } /** * Checks if a method is a getter * @param methodTest The method to test * @return True if the method is a getter, false otherwise */ private boolean isGetter(final MethodNode methodTest) { boolean getter = false; final String desc = methodTest.desc; final Type[] arguments = Type.getArgumentTypes(desc); final Type returnType = Type.getReturnType(desc); if (arguments.length == 0 && returnType.getSort() != Type.VOID) { final InsnList instructions = methodTest.instructions; //three next to skip label and line number instructions final AbstractInsnNode first = instructions.getFirst().getNext().getNext(); final int returnOp = returnType.getOpcode(Opcodes.IRETURN); final int firstOp = first.getOpcode(); //check for static getter if ((Opcodes.ACC_STATIC & methodTest.access) == 0) { if (firstOp == Opcodes.ALOAD) { final AbstractInsnNode second = first.getNext(); if (second.getOpcode() == Opcodes.GETFIELD) { final AbstractInsnNode third = second.getNext(); if (third.getOpcode() == returnOp) { getter = true; } } } } else { if (firstOp == Opcodes.GETSTATIC) { final AbstractInsnNode second = first.getNext(); if (second.getOpcode() == returnOp) { getter = true; } } } } return getter; } /** * Checks if a method is a setter * @param methodTest The method to be checked * @return True if the method is a setter, false otherwise */ private boolean isSetter(final MethodNode methodTest) { boolean setter = false; final String desc = methodTest.desc; final Type[] arguments = Type.getArgumentTypes(desc); final Type returnType = Type.getReturnType(desc); if (arguments.length == 1 && returnType.getSort() == Type.VOID) { final InsnList instructions = methodTest.instructions; //skip label and line number instructions final AbstractInsnNode first = instructions.getFirst().getNext().getNext(); final int loadOp = arguments[0].getOpcode(Opcodes.ILOAD); final int firstOp = first.getOpcode(); //check for static setter if ((Opcodes.ACC_STATIC & methodTest.access) == 0) { if (firstOp == Opcodes.ALOAD) { final AbstractInsnNode second = first.getNext(); if (second.getOpcode() == loadOp) { final AbstractInsnNode third = second.getNext(); if (third.getOpcode() == Opcodes.PUTFIELD) { //three next to skip label and line number instructions final AbstractInsnNode fourth = third.getNext().getNext().getNext(); if (fourth.getOpcode() == Opcodes.RETURN) { setter = true; } } } } } else { if (firstOp == loadOp) { final AbstractInsnNode second = first.getNext(); if (second.getOpcode() == Opcodes.PUTSTATIC) { final AbstractInsnNode third = second.getNext().getNext().getNext(); if (third.getOpcode() == Opcodes.RETURN) { setter = true; } } } } } return setter; } }