Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright 2008, Unitils.org
*
* 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 org.unitils.mock.argumentmatcher;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Type;
import static org.objectweb.asm.Type.getMethodDescriptor;
import org.objectweb.asm.tree.*;
import org.objectweb.asm.tree.analysis.*;
import org.unitils.core.UnitilsException;
import org.unitils.mock.annotation.ArgumentMatcher;
import org.unitils.mock.annotation.MatchStatement;
import org.unitils.mock.core.proxy.ProxyInvocation;
import static org.unitils.thirdparty.org.apache.commons.io.IOUtils.closeQuietly;
import static org.unitils.util.ReflectionUtils.getClassWithName;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Utility class for locating argument matchers in method invocations.
*
* @author Tim Ducheyne
* @author Filip Neven
* @author Kenny Claes
*/
public class ArgumentMatcherPositionFinder {
/**
* Locates the argument matchers for the given proxy method invocation.
*
* @param proxyInvocation The method invocation, not null
* @param fromLineNr The begin line-nr of the invocation
* @param toLineNr The end line-nr of the invocation (could be different from the begin line-nr if the invocation is written on more than 1 line)
* @param index The index of the matcher on that line, 1 for the first, 2 for the second etc
* @return The argument indexes, empty if there are no matchers
*/
public static List getArgumentMatcherIndexes(ProxyInvocation proxyInvocation, int fromLineNr, int toLineNr, int index) {
Class> testClass = getClassWithName(proxyInvocation.getInvokedAt().getClassName());
String testMethodName = proxyInvocation.getInvokedAt().getMethodName();
Method method = proxyInvocation.getMethod();
return getArgumentMatcherIndexes(testClass, testMethodName, method, fromLineNr, toLineNr, index);
}
/**
* Locates the argument matchers for the method invocation on the given line.
* An exception is raised when the given method cannot be found.
*
* @param clazz The class containing the method invocation, not null
* @param methodName The method containing the method invocation, not null
* @param invokedMethod The invocation to look for, not null
* @param fromLineNr The begin line-nr of the invocation
* @param toLineNr The end line-nr of the invocation (could be different from the begin line-nr if the invocation is written on more than 1 line)
* @param index The index of the matcher on that line, 1 for the first, 2 for the second etc
* @return The argument indexes, empty if there are no matchers
*/
@SuppressWarnings({"unchecked"})
public static List getArgumentMatcherIndexes(Class> clazz, String methodName, Method invokedMethod, int fromLineNr, int toLineNr, int index) {
// read the bytecode of the test class
ClassNode restClassNode = readClass(clazz);
// find the correct test method
List testMethodNodes = restClassNode.methods;
for (MethodNode testMethodNode : testMethodNodes) {
// another method with the same name may exist
// if no result was found it could be that the line nr was for the other method, so continue with the search
if (methodName.equals(testMethodNode.name)) {
List result = findArgumentMatcherIndexes(restClassNode, testMethodNode, clazz, methodName, invokedMethod, fromLineNr, toLineNr, index);
if (result != null) {
return result;
}
}
}
throw new UnitilsException("Unable to find indexes of argument matcher. Method not found: " + methodName);
}
/**
* Uses ASM to read the byte code of the given class. This will access the class file and create some sort
* of DOM tree for the structure of the bytecode.
*
* @param clazz The class to read, not null
* @return The structure of the class, not null
*/
protected static ClassNode readClass(Class> clazz) {
InputStream inputStream = null;
try {
inputStream = clazz.getClassLoader().getResourceAsStream(clazz.getName().replace('.', '/') + ".class");
ClassReader classReader = new ClassReader(inputStream);
ClassNode classNode = new ClassNode();
classReader.accept(classNode, 0);
return classNode;
} catch (Exception e) {
throw new UnitilsException("Unable to read class file for " + clazz, e);
} finally {
closeQuietly(inputStream);
}
}
/**
* Locates the argument matchers for the method invocation on the given line.
*
* @param classNode The class containing the method invocation, not null
* @param methodNode The method containing the method invocation, not null
* @param interpretedClass The current class, not null
* @param interpretedMethodName The current method name, not null
* @param invokedMethod The invocation to look for, not null
* @param fromLineNr The begin line-nr of the invocation
* @param toLineNr The end line-nr of the invocation (could be different from the begin line-nr if the invocation is written on more than 1 line)
* @param index The index of the matcher on that line, 1 for the first, 2 for the second etc
* @return The argument indexes, null if method was not found, empty if method found but there are no matchers
*/
protected static List findArgumentMatcherIndexes(ClassNode classNode, MethodNode methodNode, Class> interpretedClass, String interpretedMethodName, Method invokedMethod, int fromLineNr, int toLineNr, int index) {
String invokedMethodName = invokedMethod.getName();
String invokedMethodDescriptor = getMethodDescriptor(invokedMethod);
try {
// analyze the instructions in the method
MethodInterpreter methodInterpreter = new MethodInterpreter(interpretedClass, interpretedMethodName, invokedMethodName, invokedMethodDescriptor, fromLineNr, toLineNr, index);
Analyzer analyzer = new MethodAnalyzer(methodNode, methodInterpreter);
analyzer.analyze(classNode.name, methodNode);
// retrieve the found matcher indexes, if any
return methodInterpreter.getResultArgumentMatcherIndexes();
} catch (AnalyzerException e) {
if (e.getCause() instanceof UnitilsException) {
throw (UnitilsException) e.getCause();
}
throw new UnitilsException("Unable to find argument matchers for method invocation. Method name: " + invokedMethodName + ", method description; " + invokedMethodDescriptor + ", line nr; " + fromLineNr, e);
}
}
/**
* Analyzer that passes the line nrs to the given interpreter.
* By default an analyzer filters out the line number instructions. This analyzer intercepts these instructions and
* sets the current line nr on the interpreter.
*/
protected static class MethodAnalyzer extends Analyzer {
/* The method to analyze */
protected MethodNode methodNode;
/* The interpreter to use during the analysis */
protected MethodInterpreter methodInterpreter;
/**
* Creates an analyzer.
*
* @param methodNode The method to analyze, not null
* @param methodInterpreter The interpreter to use during the analysis, not null
*/
public MethodAnalyzer(MethodNode methodNode, MethodInterpreter methodInterpreter) {
super(methodInterpreter);
this.methodNode = methodNode;
this.methodInterpreter = methodInterpreter;
}
/**
* Overridden to handle the line number instructions.
*
* @param instructionIndex The current index
* @param nextInstructionIndex The next index
*/
protected void newControlFlowEdge(int instructionIndex, int nextInstructionIndex) {
AbstractInsnNode insnNode = methodNode.instructions.get(instructionIndex);
if (insnNode instanceof LineNumberNode) {
LineNumberNode lineNumberNode = (LineNumberNode) insnNode;
methodInterpreter.setCurrentLineNr(lineNumberNode.line);
}
}
}
/**
* Interpreter that implements the argument matcher finder behavior.
* The analyzer simulates the processing of instructions by the VM and calls methods on this class to determine the
* result of the processing of an instruction. During this processing, the analyzer simulates the maintenance of
* the operand stack. For example:
*
* Suppose you have following statement: 1 + 2
* The analyzer will first simulate the instruction to load constant 1 on the operand stack,
* then it does the same for constant 2, finally it simulates the sum instruction on both operands, removes both
* operands from the operand stack and puts the result back on the stack.
*
* All these instructions will pass through this interpreter to determine the result values to put on the
* operand stack.
*
* This interpreter works as follows to find the argument matchers: if a method call instruction is found that is
* an argument matcher we return an ArugmentMatcherValue. For other instructions an ArugmentMatcherValue is returned if
* one of its operands was a an ArugmentMatcherValue. When the actual invoked method is found, we then just
* have to look at the operands: if one of the operands is an ArugmentMatcherValue, we've found the index of the argument matcher.
*
* For example:
* mock.methodCall(0, gt(2)) would give
* 1) load 0
* .... stack ( NotAnArgumentMatcherValue )
* 2) load 2
* .... stack ( NotAnArgumentMatcherValue, NotAnArgumentMatcherValue)
* 3) invoke argment matcher (pops last operand)
* .... stack ( NotAnArgumentMatcherValue, ArgumentMatcherValue)
* 4) invoke mock method using last 2 operands => we've found an argument matcher as second operand
*/
protected static class MethodInterpreter extends BasicInterpreter {
protected Class> interpretedClass;
protected String interpretedMethodName;
/* The name of the method to look for */
protected String invokedMethodName;
/* The signature of the method to look for */
protected String invokedMethodDescriptor;
/* The line nrs between which the invocation can be found */
protected int fromLineNr, toLineNr;
protected int index;
/* The line that is currently being analyzed */
protected int currentLineNr = 0;
protected int currentIndex = 1;
protected Method currentMatcherMethod;
protected Set handledMethodInsnNodes = new HashSet();
/* The resulting indexes or null if method was not found */
protected List resultArgumentMatcherIndexes;
/**
* Creates an interpreter.
*
* @param interpretedClass The current class, not null
* @param interpretedMethodName The current method name, not null
* @param invokedMethodName The method to look for, not null
* @param invokedMethodDescriptor The signature of the method to look for, not null
* @param fromLineNr The begin line-nr of the invocation
* @param toLineNr The end line-nr of the invocation (could be different from the begin line-nr if the invocation is written on more than 1 line)
* @param index The index of the matcher on that line, 1 for the first, 2 for the second etc
*/
public MethodInterpreter(Class> interpretedClass, String interpretedMethodName, String invokedMethodName, String invokedMethodDescriptor, int fromLineNr, int toLineNr, int index) {
this.interpretedClass = interpretedClass;
this.interpretedMethodName = interpretedMethodName;
this.invokedMethodName = invokedMethodName;
this.invokedMethodDescriptor = invokedMethodDescriptor;
this.fromLineNr = fromLineNr;
this.toLineNr = toLineNr;
this.index = index;
}
/**
* Gets the result after the analysis was performed.
*
* @return The argument indexes, null if method was not found, empty if method found but there are no matchers
*/
public List getResultArgumentMatcherIndexes() {
return resultArgumentMatcherIndexes;
}
/**
* Sets the line nr that is being analyzed.
*
* @param currentLineNr The line nr
*/
public void setCurrentLineNr(int currentLineNr) {
this.currentLineNr = currentLineNr;
}
@Override
public Value copyOperation(AbstractInsnNode insn, Value value) throws AnalyzerException {
Value resultValue = super.copyOperation(insn, value);
return getValue(resultValue, value);
}
@Override
public Value unaryOperation(AbstractInsnNode insn, Value value) throws AnalyzerException {
Value resultValue = super.unaryOperation(insn, value);
return getValue(resultValue, value);
}
@Override
public Value binaryOperation(AbstractInsnNode insn, Value value1, Value value2) throws AnalyzerException {
Value resultValue = super.binaryOperation(insn, value1, value2);
return getValue(resultValue, value1, value2);
}
@Override
public Value ternaryOperation(AbstractInsnNode insn, Value value1, Value value2, Value value3) throws AnalyzerException {
Value resultValue = super.ternaryOperation(insn, value1, value2, value3);
return getValue(resultValue, value1, value2, value3);
}
/**
* Handles an instruction of a method call.
*
* @param instructionNode The instruction
* @param values The operands
* @return The merged values or an ArugmentMatcherValue if an argument matcher method was found
*/
@Override
@SuppressWarnings({"unchecked"})
public Value naryOperation(AbstractInsnNode instructionNode, List values) throws AnalyzerException {
Value resultValue = super.naryOperation(instructionNode, values);
if (!(instructionNode instanceof MethodInsnNode)) {
return getValue(resultValue, values);
}
// check whether we are interested in the instruction
MethodInsnNode methodInsnNode = (MethodInsnNode) instructionNode;
if (instructionOutOfRange() || instructionAlreadyHandled(methodInsnNode)) {
return getValue(resultValue, values);
}
if (isInvokedMethod(methodInsnNode)) {
if (currentIndex++ != index) {
return getValue(resultValue, values);
}
if (resultArgumentMatcherIndexes != null) {
throwUnitilsException("Method invocation occurs more than once within the same clause. Method name: " + invokedMethodName);
}
// we've found the method, now check which operands are argument matchers
resultArgumentMatcherIndexes = getArgumentMatcherIndexes(methodInsnNode, values);
currentMatcherMethod = null;
return createArgumentMatcherValue(resultValue);
}
Method method = getMethod(methodInsnNode);
if (method != null) {
// check whether the method is a match statement
if (isMatcherMethod(method)) {
currentMatcherMethod = method;
}
// check whether the method is an argument matcher (i.e. has the @ArgumentMatcher annotation)
if (isArgumentMatcherMethod(method)) {
if (currentMatcherMethod == null) {
throwUnitilsException("An argument matcher cannot be used outside the context of a match statement.");
}
// we've found an argument matcher
return createArgumentMatcherValue(resultValue);
}
}
// nothing special found
return getValue(resultValue, values);
}
protected boolean instructionOutOfRange() {
return currentLineNr < fromLineNr || toLineNr < currentLineNr;
}
protected boolean instructionAlreadyHandled(MethodInsnNode methodInsnNode) {
return !handledMethodInsnNodes.add(methodInsnNode);
}
protected boolean isInvokedMethod(MethodInsnNode methodInsnNode) {
return invokedMethodName.equals(methodInsnNode.name) && invokedMethodDescriptor.equals(methodInsnNode.desc);
}
protected List getArgumentMatcherIndexes(MethodInsnNode methodInsnNode, List values) {
List result = new ArrayList();
// for non-static invocations the first operand is always 'this'
boolean isStatic = methodInsnNode.getOpcode() == INVOKESTATIC;
for (int i = 0; i < values.size(); i++) {
if (values.get(i) instanceof ArgumentMatcherValue) {
result.add(isStatic ? i : i - 1);
}
}
return result;
}
protected boolean isMatcherMethod(Method method) {
return method.getAnnotation(MatchStatement.class) != null;
}
protected boolean isArgumentMatcherMethod(Method method) {
return method.getAnnotation(ArgumentMatcher.class) != null;
}
/**
* Throws a {@link org.unitils.core.UnitilsException} with the given error message. The stacktrace is modified, to make
* it point to the line of code that was analyzed by this class.
*
* @param errorMessage The error message
*/
protected void throwUnitilsException(String errorMessage) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(errorMessage);
stringBuilder.append("\n at ");
stringBuilder.append(new StackTraceElement(interpretedClass.getName(), interpretedMethodName, interpretedClass.getName(), currentLineNr).toString());
stringBuilder.append("\n");
throw new UnitilsException(stringBuilder.toString());
}
/**
* Merges two values.
*
* @param value1 The first value
* @param value2 The second value
* @return The merged value
*/
@Override
public Value merge(Value value1, Value value2) {
Value resultValue = super.merge(value1, value2);
if (value1 instanceof ArgumentMatcherValue || value2 instanceof ArgumentMatcherValue) {
return createArgumentMatcherValue(resultValue);
}
return resultValue;
}
/**
* Finds a method using the ASM method node
*
* @param methodNode The ASM method node, not null
* @return The method, null if not found
*/
protected Method getMethod(MethodInsnNode methodNode) {
String internalClassName = methodNode.owner;
String className = internalClassName.replace('/', '.');
String methodName = methodNode.name;
String methodDescriptor = methodNode.desc;
Class> clazz = getClassWithName(className);
Method[] methods = clazz.getMethods();
for (Method method : methods) {
if (methodName.equals(method.getName()) && methodDescriptor.equals(getMethodDescriptor(method))) {
return method;
}
}
return null;
}
/**
* @param resultValue The result value
* @param values The values that can be ArgumentMatcherValues
* @return The result value, or a ArgumentMatcherValue of the same type if one of the values is an ArgumentMatcherValue
*/
protected Value getValue(Value resultValue, Value... values) {
if (values != null) {
for (Value value : values) {
if (value instanceof ArgumentMatcherValue) {
return createArgumentMatcherValue(resultValue);
}
}
}
return resultValue;
}
/**
* @param resultValue The result value
* @param values The values that can be ArgumentMatcherValues
* @return The result value, or a ArgumentMatcherValue of the same type if one of the values is an ArgumentMatcherValue
*/
protected Value getValue(Value resultValue, List values) {
int nrOfArgumentMatcherValues = getNrOfArgumentMacherValues(values);
if (nrOfArgumentMatcherValues > 1) {
throwUnitilsException("An argument matcher cannot be used in an expression.");
}
if (nrOfArgumentMatcherValues == 1) {
return createArgumentMatcherValue(resultValue);
}
return resultValue;
}
/**
* @param values The values that can be ArgumentMatcherValues
* @return The nr of values that are an ArgumentMatcherValue
*/
protected int getNrOfArgumentMacherValues(List values) {
if (values == null) {
return 0;
}
int count = 0;
for (Value value : values) {
if (value instanceof ArgumentMatcherValue) {
count++;
}
}
return count;
}
/**
* @param resultValue The result value
* @return An ArgumentMatcherValue of the same type
*/
protected ArgumentMatcherValue createArgumentMatcherValue(Value resultValue) {
if (resultValue == null || !(resultValue instanceof BasicValue)) {
return new ArgumentMatcherValue(null);
}
Type type = ((BasicValue) resultValue).getType();
return new ArgumentMatcherValue(type);
}
}
/**
* A value representing a found argument matcher invocation
*/
protected static class ArgumentMatcherValue extends BasicValue {
public ArgumentMatcherValue(Type type) {
super(type);
}
}
}