![JAR search and dependency download from the Maven repository](/logo.png)
com.google.testing.threadtester.CallChecker Maven / Gradle / Ivy
/*
* Copyright 2009 Weaver authors
*
* 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.google.testing.threadtester;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtBehavior;
import javassist.CtClass;
import javassist.NotFoundException;
import javassist.expr.ExprEditor;
import javassist.expr.MethodCall;
/**
* Analyses the method calls made by a given class. Used by {@link
* AnnotatedTestWrapper} to determine which instrumented methods are invoked by
* a test case.
*
* @author [email protected] (Alasdair Mackintosh)
*/
class CallChecker {
/**
* Gets a list of the method calls made by a given class. See {@link #getCallers(Class, List)}
*/
Map getCallers(Class> caller, Class>... targets) {
ArrayList> target_list = new ArrayList>();
Collections.addAll(target_list, targets);
return getCallers(caller, target_list);
}
/**
* Gets the method calls made by a given class (or its superclasses) to
* methods defined in the target classes. Returns a map, keyed on the methods
* in the calling class. Each calling method maps onto the first target
* method invoked. For example, given:
*
* public void doSomething() {
* target.method1();
* target.method2();
* }
*
*
* Then the returned map will contain a mapping from doSomething
* to method1
.
*
* A method in the calling class that does not invoke any methods in any of
* the target classes will not be present in the map.
*/
Map getCallers(final Class> caller, List> targets) {
Set targetNames = new HashSet();
for (Class> target : targets) {
targetNames.add(target.getName());
}
Map result = new HashMap();
// If a subclass overrides a superclass' methods, then we only want to
// examine calls made by the subclass. To implement this, we create a set of
// all of the methods defined in the subclass. This will include inherited
// methods, but it will not include overridden ones. Then, in getCallers(),
// as we move up the class hierarchy, we only look at those methods which are
// members of this set.
Set methodsToProcess = new HashSet();
for (Method m : caller.getMethods()) {
methodsToProcess.add(m);
}
getCallers(caller, targetNames, methodsToProcess, result);
return result;
}
/**
* Gets the method calls made by the given caller to the given target classes.
*/
private void getCallers(final Class> caller, final Set targetNames,
final Set methodsToProcess, final Map result ) {
CtClass cl = null;
try {
cl = ClassPool.getDefault().get(caller.getName());
cl.instrument(new ExprEditor() {
@Override
public void edit(MethodCall called) {
try {
if (targetNames.contains(called.getClassName())) {
Method callingMethod = getMethod(caller.getName(), called.where());
if (callingMethod != null && !result.containsKey(callingMethod) &&
methodsToProcess.contains(callingMethod)) {
Method calledMethod = getMethod(called.getClassName(), called.getMethod());
// If we didn't find the called method in the target
// class, see if it's defined in one of the other
// target classes. (These may be superclasses of the
// target.)
if (calledMethod == null) {
for (String targetName : targetNames) {
if (!targetName.equals(called.getClassName())) {
calledMethod = getMethod(targetName, called.getMethod());
if (calledMethod != null) {
break;
}
}
}
}
result.put(callingMethod, calledMethod);
}
}
} catch (NotFoundException e) {
throw new RuntimeException(e);
}
}
});
} catch (NotFoundException e) {
throw new RuntimeException(e);
} catch (CannotCompileException e) {
throw new RuntimeException(e);
} finally {
if (cl != null) {
cl.detach();
}
}
Class> superclass = caller.getSuperclass();
if (superclass != Object.class) {
getCallers(superclass, targetNames, methodsToProcess, result);
}
}
/**
* Returns the method object defined by the given behaviour in the given
* class, or null if there is no such method. A return value of null
* indicates that although the bytecode contains a reference to a method,
* that method is actually defined by the superclass of the target. E.g.
* given the following class:
*
* public class MyList extends ArrayList {
* // no methods
* }
*
* then a class that uses MyList>
may have a reference to
* MyList.size()
, even though the actual implementation
* of the size()
method is defined in ArrayList
.
*/
private Method getMethod(String className, CtBehavior behavior) {
Class> definingClass = MethodCaller.getClass(className);
try {
CtClass[] ctParams = behavior.getParameterTypes();
Class>[] params = new Class>[ctParams.length];
for (int i = 0; i < ctParams.length; i++) {
params[i] = TestInstrumenter.getClass(ctParams[i]);
}
return definingClass.getDeclaredMethod(behavior.getName(), params);
} catch (NotFoundException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
return null;
}
}
}