blob: 880eba27037919325973662e82c6aa860b19da27 [file] [log] [blame]
Pierre De Rop3a00a212015-03-01 09:27:46 +00001/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19package org.apache.felix.dm.impl;
20
21import java.lang.reflect.InvocationTargetException;
22import java.lang.reflect.Method;
23import java.lang.reflect.Modifier;
24import java.lang.reflect.Proxy;
25import java.util.Arrays;
26import java.util.LinkedHashMap;
27import java.util.Map;
28
29/**
30 * Utility methods for invoking callbacks. Lookups of callbacks are accellerated by using a LRU cache.
31 *
32 * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
33 */
34public class InvocationUtil {
35 private static final Map<Key, Method> m_methodCache;
36 static {
37 int size = 2048;
38 // TODO enable this again
39// try {
40// String value = System.getProperty(DependencyManager.METHOD_CACHE_SIZE);
41// if (value != null) {
42// size = Integer.parseInt(value);
43// }
44// }
45// catch (Exception e) {}
46 m_methodCache = new LRUMap(Math.max(size, 64));
47 }
48
49 /**
50 * Invokes a callback method on an instance. The code will search for a callback method with
51 * the supplied name and any of the supplied signatures in order, invoking the first one it finds.
52 *
53 * @param instance the instance to invoke the method on
54 * @param methodName the name of the method
55 * @param signatures the ordered list of signatures to look for
56 * @param parameters the parameter values to use for each potential signature
57 * @return whatever the method returns
58 * @throws NoSuchMethodException when no method could be found
59 * @throws IllegalArgumentException when illegal values for this methods arguments are supplied
60 * @throws IllegalAccessException when the method cannot be accessed
61 * @throws InvocationTargetException when the method that was invoked throws an exception
62 */
63 public static Object invokeCallbackMethod(Object instance, String methodName, Class<?>[][] signatures, Object[][] parameters) throws NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
64 Class<?> currentClazz = instance.getClass();
65 while (currentClazz != null && currentClazz != Object.class) {
66 try {
67 return invokeMethod(instance, currentClazz, methodName, signatures, parameters, false);
68 }
69 catch (NoSuchMethodException nsme) {
70 // ignore
71 }
72 currentClazz = currentClazz.getSuperclass();
73 }
74 throw new NoSuchMethodException(methodName);
75 }
76
77 /**
78 * Invoke a method on an instance.
79 *
80 * @param object the instance to invoke the method on
81 * @param clazz the class of the instance
82 * @param name the name of the method
83 * @param signatures the signatures to look for in order
84 * @param parameters the parameter values for the signatures
85 * @param isSuper <code>true</code> if this is a superclass and we should therefore not look for private methods
86 * @return whatever the method returns
87 * @throws NoSuchMethodException when no method could be found
88 * @throws IllegalArgumentException when illegal values for this methods arguments are supplied
89 * @throws IllegalAccessException when the method cannot be accessed
90 * @throws InvocationTargetException when the method that was invoked throws an exception
91 */
92 public static Object invokeMethod(Object object, Class<?> clazz, String name, Class<?>[][] signatures, Object[][] parameters, boolean isSuper) throws NoSuchMethodException, InvocationTargetException, IllegalArgumentException, IllegalAccessException {
93 if (object == null) {
94 throw new IllegalArgumentException("Instance cannot be null");
95 }
96 if (clazz == null) {
97 throw new IllegalArgumentException("Class cannot be null");
98 }
99
100 // if we're talking to a proxy here, dig one level deeper to expose the
101 // underlying invocation handler (we do the same for injecting instances)
102 if (Proxy.isProxyClass(clazz)) {
103 object = Proxy.getInvocationHandler(object);
104 clazz = object.getClass();
105 }
106
107 Method m = null;
108 for (int i = 0; i < signatures.length; i++) {
109 Class<?>[] signature = signatures[i];
110 m = getDeclaredMethod(clazz, name, signature, isSuper);
111 if (m != null) {
112 return m.invoke(object, parameters[i]);
113 }
114 }
115 throw new NoSuchMethodException(name);
116 }
117
118 private static Method getDeclaredMethod(Class<?> clazz, String name, Class<?>[] signature, boolean isSuper) {
119 // first check our cache
120 Key key = new Key(clazz, name, signature);
121 Method m = null;
122 synchronized (m_methodCache) {
123 m = (Method) m_methodCache.get(key);
124 if (m != null) {
125 return m;
126 }
127 else if (m_methodCache.containsKey(key)) {
128 // the key is in our cache, it just happens to have a null value
129 return null;
130 }
131 }
132 // then do a lookup
133 try {
134 m = clazz.getDeclaredMethod(name, signature);
135 if (!(isSuper && Modifier.isPrivate(m.getModifiers()))) {
136 m.setAccessible(true);
137 }
138 }
139 catch (NoSuchMethodException e) {
140 }
141 synchronized (m_methodCache) {
142 m_methodCache.put(key, m);
143 }
144 return m;
145 }
146
147 public static class Key {
148 private final Class<?> m_clazz;
149 private final String m_name;
150 private final Class<?>[] m_signature;
151
152 public Key(Class<?> clazz, String name, Class<?>[] signature) {
153 m_clazz = clazz;
154 m_name = name;
155 m_signature = signature;
156 }
157
158 public int hashCode() {
159 final int prime = 31;
160 int result = 1;
161 result = prime * result + ((m_clazz == null) ? 0 : m_clazz.hashCode());
162 result = prime * result + ((m_name == null) ? 0 : m_name.hashCode());
163 result = prime * result + Arrays.hashCode(m_signature);
164 return result;
165 }
166
167 public boolean equals(Object obj) {
168 if (this == obj)
169 return true;
170 if (obj == null)
171 return false;
172 if (getClass() != obj.getClass())
173 return false;
174 Key other = (Key) obj;
175 if (m_clazz == null) {
176 if (other.m_clazz != null)
177 return false;
178 }
179 else if (!m_clazz.equals(other.m_clazz))
180 return false;
181 if (m_name == null) {
182 if (other.m_name != null)
183 return false;
184 }
185 else if (!m_name.equals(other.m_name))
186 return false;
187 if (!Arrays.equals(m_signature, other.m_signature))
188 return false;
189 return true;
190 }
191 }
192
193 @SuppressWarnings("serial")
194 public static class LRUMap extends LinkedHashMap<Key, Method> {
195 private final int m_size;
196
197 public LRUMap(int size) {
198 m_size = size;
199 }
200
201 protected boolean removeEldestEntry(java.util.Map.Entry<Key, Method> eldest) {
202 return size() > m_size;
203 }
204 }
205}