blob: 76d3cd031c96ff97973fd86b2ec51437ebb242c3 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.felix.dm.impl;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.concurrent.FutureTask;
import java.util.function.Supplier;
import org.apache.felix.dm.DependencyManager;
import org.osgi.service.cm.ConfigurationException;
/**
* Utility methods for invoking callbacks. Lookups of callbacks are accellerated by using a LRU cache.
*
* @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
*/
public class InvocationUtil {
/**
* Constant Used to get empty constructor by reflection.
*/
private static final Class<?>[] VOID = new Class[] {};
private static final Map<Key, Method> m_methodCache;
static {
int size = 4096;
try {
String value = System.getProperty(DependencyManager.METHOD_CACHE_SIZE);
if (value != null) {
size = Integer.parseInt(value);
}
}
catch (Exception e) {}
m_methodCache = new LRUMap(Math.max(size, 64));
}
/**
* Interface internally used to handle a ConfigurationAdmin update synchronously, in a component executor queue.
*/
@FunctionalInterface
public interface ConfigurationHandler {
public void handle() throws Exception;
}
/**
* Represents a component instance
*/
public static final class ComponentInstance {
/**
* The component instance
*/
public final Object m_instance;
/**
* The index of the constructor used when creating the component instance
*/
public final int m_ctorIndex;
/**
* creates a component instance
* @param instance the component instance
* @param ctorIndex the index of the CallbackTypeDef used when creating the component instance
*/
public ComponentInstance(Object instance, int ctorIndex) {
m_instance = instance;
m_ctorIndex = ctorIndex;
}
}
/**
* Invokes a callback method on an instance. The code will search for a callback method with
* the supplied name and any of the supplied signatures in order, invoking the first one it finds.
*
* @param instance the instance to invoke the method on
* @param methodName the name of the method
* @param signatures the ordered list of signatures to look for
* @param parameters the parameter values to use for each potential signature
* @return whatever the method returns
* @throws NoSuchMethodException when no method could be found
* @throws IllegalArgumentException when illegal values for this methods arguments are supplied
* @throws IllegalAccessException when the method cannot be accessed
* @throws InvocationTargetException when the method that was invoked throws an exception
*/
public static Object invokeCallbackMethod(Object instance, String methodName, Class<?>[][] signatures, Object[][] parameters) throws NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
Class<?> currentClazz = instance.getClass();
while (currentClazz != null && currentClazz != Object.class) {
try {
return invokeMethod(instance, currentClazz, methodName, signatures, parameters, false);
}
catch (NoSuchMethodException nsme) {
// ignore
}
currentClazz = currentClazz.getSuperclass();
}
throw new NoSuchMethodException(methodName);
}
/**
* Invoke a method on an instance.
*
* @param object the instance to invoke the method on
* @param clazz the class of the instance
* @param name the name of the method
* @param signatures the signatures to look for in order
* @param parameters the parameter values for the signatures
* @param isSuper <code>true</code> if this is a superclass and we should therefore not look for private methods
* @return whatever the method returns
* @throws NoSuchMethodException when no method could be found
* @throws IllegalArgumentException when illegal values for this methods arguments are supplied
* @throws IllegalAccessException when the method cannot be accessed
* @throws InvocationTargetException when the method that was invoked throws an exception
*/
public static Object invokeMethod(Object object, Class<?> clazz, String name, Class<?>[][] signatures, Object[][] parameters, boolean isSuper) throws NoSuchMethodException, InvocationTargetException, IllegalArgumentException, IllegalAccessException {
if (object == null) {
throw new IllegalArgumentException("Instance cannot be null");
}
if (clazz == null) {
throw new IllegalArgumentException("Class cannot be null");
}
// if we're talking to a proxy here, dig one level deeper to expose the
// underlying invocation handler (we do the same for injecting instances)
if (Proxy.isProxyClass(clazz)) {
object = Proxy.getInvocationHandler(object);
clazz = object.getClass();
}
Method m = null;
for (int i = 0; i < signatures.length; i++) {
Class<?>[] signature = signatures[i];
m = getDeclaredMethod(clazz, name, signature, isSuper);
if (m != null) {
return m.invoke(object, parameters[i]);
}
}
throw new NoSuchMethodException(name);
}
/**
* Invokes a callback method on an instance. The code will search for a callback method with
* the supplied name and any of the supplied signatures in order, invoking the first one it finds.
*
* @param instance the instance to invoke the method on
* @param methodName the name of the method
* @param signatures the ordered list of signatures to look for
* @param parameters the parameter values to use for each potential signature
* @return whatever the method returns
* @throws NoSuchMethodException when no method could be found
* @throws IllegalArgumentException when illegal values for this methods arguments are supplied
* @throws IllegalAccessException when the method cannot be accessed
* @throws InvocationTargetException when the method that was invoked throws an exception
*/
public static Object invokeCallbackMethod(Object instance, String methodName, Class<?>[][] signatures, Supplier<?>[][] parameters) throws NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
Class<?> currentClazz = instance.getClass();
while (currentClazz != null && currentClazz != Object.class) {
try {
return invokeMethod(instance, currentClazz, methodName, signatures, parameters, false);
}
catch (NoSuchMethodException nsme) {
// ignore
}
currentClazz = currentClazz.getSuperclass();
}
throw new NoSuchMethodException(methodName);
}
/**
* Invoke a method on an instance.
*
* @param object the instance to invoke the method on
* @param clazz the class of the instance
* @param name the name of the method
* @param signatures the signatures to look for in order
* @param paramsSupplier the parameter values for the signatures
* @param isSuper <code>true</code> if this is a superclass and we should therefore not look for private methods
* @return whatever the method returns
* @throws NoSuchMethodException when no method could be found
* @throws IllegalArgumentException when illegal values for this methods arguments are supplied
* @throws IllegalAccessException when the method cannot be accessed
* @throws InvocationTargetException when the method that was invoked throws an exception
*/
public static Object invokeMethod(Object object, Class<?> clazz, String name, Class<?>[][] signatures, Supplier<?>[][] paramsSupplier, boolean isSuper) throws NoSuchMethodException, InvocationTargetException, IllegalArgumentException, IllegalAccessException {
if (object == null) {
throw new IllegalArgumentException("Instance cannot be null");
}
if (clazz == null) {
throw new IllegalArgumentException("Class cannot be null");
}
// if we're talking to a proxy here, dig one level deeper to expose the
// underlying invocation handler (we do the same for injecting instances)
if (Proxy.isProxyClass(clazz)) {
object = Proxy.getInvocationHandler(object);
clazz = object.getClass();
}
Method m = null;
for (int i = 0; i < signatures.length; i++) {
Class<?>[] signature = signatures[i];
m = getDeclaredMethod(clazz, name, signature, isSuper);
if (m != null) {
Object[] params = new Object[paramsSupplier[i].length];
for (int j = 0; j < params.length; j ++) {
params[j] = paramsSupplier[i][j].get();
}
return m.invoke(object, params);
}
}
throw new NoSuchMethodException(name);
}
/**
* Gets a callback method on an instance. The code will search for a callback method with
* the supplied name and any of the supplied signatures in order, get the first one it finds.
*
* @param instance the instance to invoke the method on
* @param methodName the name of the method
* @param signatures the ordered list of signatures to look for
* @return the method found, or null
*/
public static Method getCallbackMethod(Object instance, String methodName, Class<?>[][] signatures) {
Class<?> currentClazz = instance.getClass();
while (currentClazz != null && currentClazz != Object.class) {
Method m = getMethod(instance, currentClazz, methodName, signatures, false);
if (m != null) {
return m;
}
currentClazz = currentClazz.getSuperclass();
}
return null;
}
/**
* Gets a callback method on a class. The code will search for a callback method with
* the supplied name and any of the supplied signatures in order, get the first one it finds.
*
* @param instance the instance to invoke the method on
* @param methodName the name of the method
* @param signatures the ordered list of signatures to look for
* @return the method found, or null
*/
public static Method getCallbackMethod(Class<?> type, String methodName, Class<?>[][] signatures) {
Class<?> currentClazz = type;
while (currentClazz != null && currentClazz != Object.class) {
Method m = getMethod(currentClazz, methodName, signatures, false);
if (m != null) {
return m;
}
currentClazz = currentClazz.getSuperclass();
}
return null;
}
/**
* Get a method on an instance.
* TODO: rework this class to avoid code duplication with invokeMethod !
*
* @param object the instance to invoke the method on
* @param clazz the class of the instance
* @param name the name of the method
* @param signatures the signatures to look for in order
* @param isSuper <code>true</code> if this is a superclass and we should therefore not look for private methods
* @return the found method, or null if not found
*/
public static Method getMethod(Object object, Class<?> clazz, String name, Class<?>[][] signatures, boolean isSuper) {
if (object == null) {
throw new IllegalArgumentException("Instance cannot be null");
}
if (clazz == null) {
throw new IllegalArgumentException("Class cannot be null");
}
// if we're talking to a proxy here, dig one level deeper to expose the
// underlying invocation handler (we do the same for injecting instances)
if (Proxy.isProxyClass(clazz)) {
object = Proxy.getInvocationHandler(object);
clazz = object.getClass();
}
return getMethod(clazz, name, signatures, isSuper);
}
/**
* Get a method on an instance.
* TODO: rework this class to avoid code duplication with invokeMethod !
*
* @param object the instance to invoke the method on
* @param clazz the class of the instance
* @param name the name of the method
* @param signatures the signatures to look for in order
* @param isSuper <code>true</code> if this is a superclass and we should therefore not look for private methods
* @return the found method, or null if not found
*/
private static Method getMethod(Class<?> clazz, String name, Class<?>[][] signatures, boolean isSuper) {
if (clazz == null) {
throw new IllegalArgumentException("Class cannot be null");
}
Method m = null;
for (int i = 0; i < signatures.length; i++) {
Class<?>[] signature = signatures[i];
m = getDeclaredMethod(clazz, name, signature, isSuper);
if (m != null) {
break;
}
}
return m;
}
public static ComponentInstance createInstance(Class<?> clazz, CallbackTypeDef ctorArgs) throws Exception {
Constructor<?>[] ctors = clazz.getConstructors();
for (int index = 0; index < ctorArgs.m_sigs.length; index ++) {
Class<?>[] sigs = ctorArgs.m_sigs[index];
for (Constructor<?> ctor : ctors) {
Class<?>[] ctorParamTypes = ctor.getParameterTypes();
if (Arrays.equals(sigs, ctorParamTypes)) {
ctor.setAccessible(true);
Object instance = ctor.newInstance(ctorArgs.m_args[index]);
return new ComponentInstance(instance, index);
}
}
}
throw new InstantiationException("No suitable constructor found for class " + clazz.getName());
}
/**
* Instantiates a component. The first public constructor which contains any of the specified arguments is used.
* @param clazz the class name used to instantiate the component
* @param ctorArgs constructor arguments. An empty map means the first public constructor is used.
* @return the component instance
*/
public static Object createInstance(Class<?> clazz, Map<Class<?>, Object> ctorArgs) throws SecurityException, NoSuchMethodException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
if (ctorArgs.size() == 0) {
Constructor<?> ctor = clazz.getConstructor(VOID);
ctor.setAccessible(true);
return ctor.newInstance();
}
Constructor<?>[] ctors = clazz.getConstructors();
for (Constructor<?> ctor : ctors) {
Class<?>[] ctorParamTypes = ctor.getParameterTypes();
boolean match = true;
for (Class<?> ctopParamType : ctorParamTypes) {
if (ctorArgs.get(ctopParamType) == null) {
match = false;
break;
}
}
if (match) {
Object[] paramValues = new Object[ctorParamTypes.length];
int index = 0;
for (Class<?> ctorParamType : ctorParamTypes) {
paramValues[index ++] = ctorArgs.get(ctorParamType);
}
ctor.setAccessible(true);
return ctor.newInstance(paramValues);
}
}
throw new InstantiationException("No suitable constructor found for class " + clazz.getName());
}
private static Method getDeclaredMethod(Class<?> clazz, String name, Class<?>[] signature, boolean isSuper) {
// first check our cache
Key key = new Key(clazz, name, signature);
Method m = null;
synchronized (m_methodCache) {
m = (Method) m_methodCache.get(key);
if (m != null) {
return m;
}
else if (m_methodCache.containsKey(key)) {
// the key is in our cache, it just happens to have a null value
return null;
}
}
// then do a lookup
try {
m = clazz.getDeclaredMethod(name, signature);
if (!(isSuper && Modifier.isPrivate(m.getModifiers()))) {
m.setAccessible(true);
}
}
catch (NoSuchMethodException e) {
}
synchronized (m_methodCache) {
m_methodCache.put(key, m);
}
return m;
}
public static class Key {
private final Class<?> m_clazz;
private final String m_name;
private final Class<?>[] m_signature;
public Key(Class<?> clazz, String name, Class<?>[] signature) {
m_clazz = clazz;
m_name = name;
m_signature = signature;
}
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((m_clazz == null) ? 0 : m_clazz.hashCode());
result = prime * result + ((m_name == null) ? 0 : m_name.hashCode());
result = prime * result + Arrays.hashCode(m_signature);
return result;
}
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Key other = (Key) obj;
if (m_clazz == null) {
if (other.m_clazz != null)
return false;
}
else if (!m_clazz.equals(other.m_clazz))
return false;
if (m_name == null) {
if (other.m_name != null)
return false;
}
else if (!m_name.equals(other.m_name))
return false;
if (!Arrays.equals(m_signature, other.m_signature))
return false;
return true;
}
}
@SuppressWarnings("serial")
public static class LRUMap extends LinkedHashMap<Key, Method> {
private final int m_size;
public LRUMap(int size) {
m_size = size;
}
protected boolean removeEldestEntry(java.util.Map.Entry<Key, Method> eldest) {
return size() > m_size;
}
}
/**
* Invokes a configuration update callback synchronously, but through the component executor queue.
*/
public static void invokeUpdated(Executor queue, ConfigurationHandler handler) throws ConfigurationException {
Callable<Exception> result = () -> {
try {
handler.handle();
} catch (Exception e) {
return e;
}
return null;
};
FutureTask<Exception> ft = new FutureTask<>(result);
queue.execute(ft);
try {
Exception err = ft.get();
if (err != null) {
throw err;
}
}
catch (ConfigurationException e) {
throw e;
}
catch (Throwable error) {
Throwable rootCause = error.getCause();
if (rootCause != null) {
if (rootCause instanceof ConfigurationException) {
throw (ConfigurationException) rootCause;
}
throw new ConfigurationException("", "Configuration update error, unexpected exception.", rootCause);
} else {
throw new ConfigurationException("", "Configuration update error, unexpected exception.", error);
}
}
}
}