Java tutorial
/* * Copyright 2007 Johannes Rieken * Copyright 2010 Google Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ package com.google.java.contract.core.agent; import com.google.java.contract.AllowUnusedImport; import com.google.java.contract.Ensures; import com.google.java.contract.Invariant; import com.google.java.contract.Requires; import com.google.java.contract.core.model.ClassName; import com.google.java.contract.core.model.ContractKind; import com.google.java.contract.util.Iterables; import com.google.java.contract.util.Predicates; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.MethodNode; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Bytecode class visitor responsible for contract information * extraction. * * After a class has been visited, this visitor exposes the resulting * handles through filtering accessor methods such as * {@link #getClassHandles(ContractKind)}. * * @author nhat.minh.le@huoc.org (Nhat Minh L) * @author johannes.rieken@gmail.com (Johannes Rieken) */ @AllowUnusedImport({ ClassName.class, Iterables.class, Predicates.class }) @Invariant({ "className == null || ClassName.isBinaryName(className)", "classHandles != null", "!classHandles.contains(null)", "methodHandles != null", "!methodHandles.keySet().contains(null)", "Iterables.all(methodHandles.values(), " + "Predicates.<MethodContractHandle>all(Predicates.nonNull()))" }) class ContractAnalyzer extends ClassVisitor { protected List<ClassContractHandle> classHandles; protected Map<String, ArrayList<MethodContractHandle>> methodHandles; protected String className; protected MethodNode lastMethodNode; /** * Constructs an empty ContractAnalyzer. The ContractAnalyzer is * intended to be filled through its visitor interface. */ ContractAnalyzer() { super(Opcodes.ASM5); classHandles = new ArrayList<ClassContractHandle>(); methodHandles = new HashMap<String, ArrayList<MethodContractHandle>>(); } /** * Returns the ClassHandle objects matching the specified criteria. * * @param kind the kind of the handles * @return a list containing the requested handles */ @Requires("kind != null") @Ensures({ "result != null", "!result.contains(null)" }) List<ClassContractHandle> getClassHandles(ContractKind kind) { ArrayList<ClassContractHandle> matched = new ArrayList<ClassContractHandle>(); for (ClassContractHandle h : classHandles) { if (kind.equals(h.getKind())) { matched.add(h); } } return matched; } /** * Returns the MethodHandle objects matching the specified criteria. * * @param kind the kind of the handles * @param name the target method name * @param desc the target method descriptor * @param extraCount the number of extra parameters in the contract * method * @return a list containing the requested handles */ @Requires({ "kind != null", "name != null", "desc != null", "extraCount >= 0" }) @Ensures({ "result != null", "!result.contains(null)" }) List<MethodContractHandle> getMethodHandles(ContractKind kind, String name, String desc, int extraCount) { ArrayList<MethodContractHandle> candidates = methodHandles.get(name); if (candidates == null) { return Collections.emptyList(); } ArrayList<MethodContractHandle> matched = new ArrayList<MethodContractHandle>(); for (MethodContractHandle h : candidates) { if (kind.equals(h.getKind()) && descArgumentsMatch(desc, h.getContractMethod().desc, extraCount)) { matched.add(h); } } return matched; } /** * Returns the first ClassHandle object matching the specified * criteria. * * @param kind the kind of the handle * @return a handle matching the criteria, or {@code null} */ @Requires("kind != null") ClassContractHandle getClassHandle(ContractKind kind) { ArrayList<ClassContractHandle> matched = new ArrayList<ClassContractHandle>(); for (ClassContractHandle h : classHandles) { if (kind.equals(h.getKind())) { return h; } } return null; } /** * Returns the first MethodHandle object matching the specified * criteria. * * @param kind the kind of the handle * @param name the target method name * @param desc the target method descriptor * @param extraCount the number of extra parameters in the contract * method * @return a handle matching the criteria, or {@code null} */ @Requires({ "kind != null", "name != null", "desc != null", "extraCount >= 0" }) MethodContractHandle getMethodHandle(ContractKind kind, String name, String desc, int extraCount) { ArrayList<MethodContractHandle> candidates = methodHandles.get(name); if (candidates == null) { return null; } for (MethodContractHandle h : candidates) { if (kind.equals(h.getKind()) && descArgumentsMatch(desc, h.getContractMethod().desc, extraCount)) { return h; } } return null; } /** * Returns {@code true} if the argument descriptors {@code desc1} * and {@code desc2} are equal (return type is ignored), ignoring * the last {@code offset} parameters of {@code desc2}. */ @Requires({ "desc1 != null", "desc2 != null", "offset >= 0" }) private boolean descArgumentsMatch(String desc1, String desc2, int offset) { Type[] types1 = Type.getArgumentTypes(desc1); Type[] types2 = Type.getArgumentTypes(desc2); if (types2.length - types1.length != offset) { return false; } for (int i = 0; i < types1.length; ++i) { if (!types1[i].equals(types2[i])) { return false; } } return true; } /* * Visitor implementation. */ @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaceNames) { className = name; } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { captureLastMethodNode(); lastMethodNode = new MethodNode(access, name, desc, signature, exceptions); return lastMethodNode; } @Override public void visitEnd() { captureLastMethodNode(); } /** * Creates a contract handle for the method last visited, if it was * a contract method. */ @Ensures("lastMethodNode == null") protected void captureLastMethodNode() { if (lastMethodNode == null) { return; } ContractKind kind = ContractMethodSignatures.getKind(lastMethodNode); if (kind != null) { List<Long> lineNumbers = ContractMethodSignatures.getLineNumbers(lastMethodNode); if (kind.isClassContract() || kind.isHelperContract()) { ClassContractHandle ch = new ClassContractHandle(kind, className, lastMethodNode, lineNumbers); classHandles.add(ch); } else { MethodContractHandle mh = new MethodContractHandle(kind, className, lastMethodNode, lineNumbers); internMethod(mh.getMethodName()).add(mh); } } lastMethodNode = null; } /** * Returns the contract handle collection corresponding to the * method named {@code name}. */ @Requires("name != null") @Ensures({ "result != null", "result == methodHandles.get(name)" }) protected List<MethodContractHandle> internMethod(String name) { ArrayList<MethodContractHandle> handles = methodHandles.get(name); if (handles == null) { handles = new ArrayList<MethodContractHandle>(); methodHandles.put(name, handles); } return handles; } }