package fr.imag.adele.apam.apform.impl;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.apache.felix.ipojo.ConfigurationException;
import org.apache.felix.ipojo.parser.MethodMetadata;
import org.apache.felix.ipojo.parser.PojoMetadata;
import org.apache.felix.ipojo.util.Callback;
/**
* This is the base class of all APAM callbacks that are invoked on component instances.
*
* All callbacks are methods declared in the component implementation, with an optional
* parameter whose type depends on the kind of callback.
*
* To allow some basic type checking at invocation, we use the parameter type to represent
* the type of the argument.
*
* @author vega
*
*/
public abstract class InstanceCallback<T> extends Callback {
/**
* The associated Apam component instance to be injected
*/
protected final ApamInstanceManager instance;
/**
* Whether invocation requires an argument
*/
protected boolean requiresArgument;
/**
* The type of the required argument
*/
protected Class<?> argumentType;
protected InstanceCallback(ApamInstanceManager instance, String method) throws ConfigurationException {
super(method,(String[])null,false,instance);
this.instance = instance;
}
/**
* Uses introspection to search for the callback method in the instrumented class of the
* component implementation
*/
@Override
protected void searchMethod() throws NoSuchMethodException {
m_methodObj = null;
requiresArgument = false;
argumentType = null;
/*
* Try to find the declared method with the specified name that best matches the callback signature.
* Try first the locally declared methods, and only if not found search publicly declared methods
* of super classes.
*
* NOTE Notice that we force class loading of the implementation class (and its super classes) by using
* reflection to search for methods.
*/
Method[] candidates = instance.getClazz().getDeclaredMethods();
int match = searchMethod(candidates);
if (match == -1) {
candidates = instance.getClazz().getMethods();
match = searchMethod(candidates);
}
if (match == -1)
throw new NoSuchMethodException(getMethod());
m_methodObj = candidates[match];
if (! m_methodObj.isAccessible()) {
m_methodObj.setAccessible(true);
}
requiresArgument = m_methodObj.getParameterTypes().length == 1;
argumentType = requiresArgument ? m_methodObj.getParameterTypes()[0] : null;
}
/**
* Search the method associated with this callback in the specified array.
*
* We are looking for a method with an optional, single, parameter of the expected type.
*
* NOTE that if the same method name is defined with different signatures, we try to use the
* most specific version.
*
*/
protected int searchMethod(Method[] methods) {
Method candidate = null;
int candidateIndex = -1;
Class<?> candidateParameter = null;
for (int index = 0; index < methods.length; index++) {
Method method = methods[index];
/*
* Skip all methods not matching the name, or the number and type
* of parameter
*/
if (! method.getName().equals(getMethod()))
continue;
Class<?>[] parameters = method.getParameterTypes();
if (parameters.length > 1)
continue;
Class<?> parameter = parameters.length == 1 ? parameters[0] : null;
if (parameter != null && ! isExpectedParameter(parameter))
continue;
/*
* Register the candidate, but continue searching for a better match
*/
if (candidate == null) {
candidate = method;
candidateIndex = index;
candidateParameter = parameter;
continue;
}
/*
* If we find a method with a parameter, prefer it
*/
if (candidateParameter == null && parameter != null) {
candidate = method;
candidateIndex = index;
candidateParameter = parameter;
continue;
}
/*
* If we find a method with a parameter, and the current candidate also has a parameter
* prefer the method with the most specific type
*/
if (candidateParameter != null && parameter != null && candidateParameter.isAssignableFrom(parameter)) {
candidate = method;
candidateIndex = index;
candidateParameter = parameter;
continue;
}
}
return candidateIndex;
}
/**
* Whether invocation actually requires an argument or not
*/
public final boolean requiresArgument() {
return requiresArgument;
}
/**
* The actual type of the parameter, if required
*/
public final Class<?> getArgumentType() {
return argumentType;
}
/**
* Invokes the callback method with the specified optional argument.
*
* If the argument doesn't match the parameter type, we try to perform automatic
* casting (specifically defined for each kind of callback)
*/
public Object invoke(T argument) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
if (m_methodObj == null) {
searchMethod();
}
Object actualArgument = requiresArgument ? argumentType.isInstance(argument) ? argument : cast(argument): null;
return call(requiresArgument ? new Object[] {actualArgument} : new Object[0]);
}
/**
* Casts the argument of the callback, in cases where it doesn't match the parameter type of the method.
*
* NOTE IMPORTANT If casting is not possible, this method should return the unmodified argument. In this
* way if there are errors in the declarations the user will get a class cast exception.
*/
protected Object cast(T argument) {
return argument;
}
/**
* The iPOJO metadata associated with the callback method to be invoked
*/
public MethodMetadata getMethodMetadata() {
PojoMetadata metadata = instance.getFactory().getPojoMetadata();
String[] arguments = requiresArgument ? new String[] {m_methodObj.getParameterTypes()[0].getCanonicalName()}: new String[0];
return metadata.getMethod(m_methodObj.getName(),arguments);
}
/**
* Test whether the specified method is the one invoked by this callback
*/
public boolean invokes(Method method) {
return m_methodObj.equals(method);
}
/**
* Whether this is the expected parameter type of the callback.
*
* This method must be overridden by kinds of callbacks depending on the specific method
* signature required.
*/
protected abstract boolean isExpectedParameter(Class<?> parameterType);
}