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.tree.AbstractInsnNode; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.FieldInsnNode; import org.objectweb.asm.tree.InsnList; import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.MethodNode; import org.objectweb.asm.tree.VarInsnNode; import com.android.ide.eclipse.apt.internal.crawler.ClassCrawler; /** * This analyzer checks if private class fields are accessed by inner classes * following the guideline:<br/> * @see <a href="http://developer.android.com/guide/practices/design/performance.html#package_inner">Use Package Scope with Inner Classes</a> * @author Mattia Gustarini * */ final class InnerClassAnalyzer extends SpecificAnalyzer { private ClassNode mInnerClass; private ClassNode mOuterClass; public InnerClassAnalyzer() { super("Inner Class", "Use Package Scope with Inner Classes: parent Class field accessed " + "by an inner Class should be protected instead of private!"); } /* (non-Javadoc) * @see com.android.ide.eclipse.apt.internal.analysis.SpecificAnalyzer#analyse(org.objectweb.asm.tree.ClassNode) */ @Override public void analyse(final ClassNode classNode) { if (setOuterClassNode(classNode)) { mInnerClass = classNode; super.analyse(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 LinkedList<Problem> problems = new LinkedList<Problem>(); final InsnList instructions = methodNode.instructions; for (int i = 0; i < instructions.size(); i++) { final AbstractInsnNode insnNode = instructions.get(i); if (insnNode.getOpcode() == Opcodes.INVOKESTATIC) { final MethodInsnNode method = (MethodInsnNode) insnNode; if (isStaticAccess(method)) { if (confirmParentClassAccess(methodNode, method)) { final AbstractInsnNode inst = retrieveMethodOrField(method); final int type = inst.getType(); if (type == AbstractInsnNode.FIELD_INSN || type == AbstractInsnNode.METHOD_INSN) { final Problem problem = new Problem(inst); problems.add(problem); } } } } } return problems; } /** * Set the outer class node of the given class if the given class is an inner class * @param classNode The class node * @return True if the class node has an outer class, false otherwise */ private boolean setOuterClassNode(final ClassNode classNode) { boolean inner = false; final String name = classNode.name; final int dollar = name.lastIndexOf('$'); if (dollar > -1) { String outerClassName = ""; final String n = name.substring(dollar); if (n.matches("[0-9]+")) { //must be anonymous inner class if (classNode.outerClass != null) { outerClassName = classNode.outerClass; } else { outerClassName = name.substring(0, dollar); } } else { outerClassName = name.substring(0, dollar); } mOuterClass = findOuterClassNode(outerClassName); if (mOuterClass != null) { inner = true; } } return inner; } /**Tries to find an outer class for a given class node described by the name of its possible * outer class. * @param className The outer class name to be searched * @return Null if no outer class is found or the class node representing the outer class */ private ClassNode findOuterClassNode(final String className) { ClassNode parentClass = null; final Collection<ClassNode> classes = ClassCrawler.sClassNodes; for (ClassNode classNode : classes) { if (classNode.name.equals(className)) { parentClass = classNode; break; } } return parentClass; } /** * Checks if a given method is a static access generated by the compiler to access outer class * private fields or methods * @param method The method that needs to be checked * @return True if the method is generated by the compiler, false otherwise */ private boolean isStaticAccess(final MethodInsnNode method) { final String owner = method.owner; final String name = method.name; final String desc = method.desc; return owner.equals(mOuterClass.name) && name.matches("access\\$\\d+") && desc.startsWith("(L" + mOuterClass.name + ";"); } /** * Checks if a method of an inner class accesses the private content of an outer class (field or method) * @param methodNode * @param method * @return True if the outer access is confirmed, false otherwise. */ private boolean confirmParentClassAccess(final MethodNode methodNode, final MethodInsnNode method) { final String methodNodeName = methodNode.name; AbstractInsnNode prev = method.getPrevious(); boolean result = false; while (!result) { if (methodNodeName.startsWith("<init>")) { if (prev.getOpcode() == Opcodes.ALOAD) { final VarInsnNode varInsn = (VarInsnNode) prev; result = varInsn.var == 1; } } else { if (prev.getOpcode() == Opcodes.GETFIELD) { final FieldInsnNode getField = (FieldInsnNode) prev; final String field = getField.owner + getField.name; final String testField = mInnerClass.name + "this$0"; result = field.equals(testField); } } if (prev.getType() == AbstractInsnNode.LINE) { break; } else { prev = prev.getPrevious(); } } return result; } @SuppressWarnings("unchecked") private AbstractInsnNode retrieveMethodOrField(final MethodInsnNode method) { final String name = method.name; final List<MethodNode> meInsnNodes = (List<MethodNode>) mOuterClass.methods; AbstractInsnNode result = null; for (final MethodNode methodNode : meInsnNodes) { if (methodNode.name.equals(name)) { final InsnList instructions = methodNode.instructions; result = instructions.getLast().getPrevious(); } } return result; } }