blob: 46ddf606f05508f73e6007122b1494d393328712 [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;
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;
/**
* 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 {
private static final Map /* <Key, Method> */ m_methodCache;
static {
int size = 2048;
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));
}
/**
* 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);
}
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) {
// ignore
}
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;
}
}
public static class LRUMap extends LinkedHashMap {
private final int m_size;
public LRUMap(int size) {
m_size = size;
}
protected boolean removeEldestEntry(java.util.Map.Entry eldest) {
return size() > m_size;
}
}
}