001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package java.beans;
019:
020: import java.lang.reflect.InvocationHandler;
021: import java.lang.reflect.Method;
022: import java.lang.reflect.Modifier;
023: import java.lang.reflect.Proxy;
024: import java.security.AccessControlContext;
025: import java.security.AccessController;
026: import java.security.PrivilegedAction;
027: import java.util.StringTokenizer;
028:
029: import org.apache.harmony.beans.internal.nls.Messages;
030:
031: public class EventHandler implements InvocationHandler {
032:
033: private Object target;
034:
035: private String action;
036:
037: private String eventPropertyName;
038:
039: private String listenerMethodName;
040:
041: final private AccessControlContext context;
042:
043: public EventHandler(Object target, String action,
044: String eventPropertyName, String listenerMethodName) {
045: if (target == null || action == null) {
046: throw new NullPointerException();
047: }
048: this .target = target;
049: this .action = action;
050: this .eventPropertyName = eventPropertyName;
051: this .listenerMethodName = listenerMethodName;
052: this .context = AccessController.getContext();
053: }
054:
055: public Object invoke(final Object proxy, final Method method,
056: final Object[] arguments) {
057: return AccessController.doPrivileged(
058: new PrivilegedAction<Object>() {
059: public Object run() {
060: return invokeImpl(proxy, method, arguments);
061: }
062: }, context);
063: }
064:
065: private Object invokeImpl(Object proxy, Method method,
066: Object[] arguments) {
067: Class<?> proxyClass;
068: Object result = null;
069:
070: Object[] theArguments = arguments;
071: // XXX
072: if (arguments == null) {
073: theArguments = new Object[0];
074: }
075:
076: proxyClass = proxy.getClass();
077:
078: // if a proxy
079: if (Proxy.isProxyClass(proxyClass)) {
080: InvocationHandler handler = Proxy
081: .getInvocationHandler(proxy);
082:
083: // if a valid object
084: if (handler instanceof EventHandler) {
085: // if the method from the Object class is called
086: if (method.getDeclaringClass().equals(Object.class)) {
087: if (method.getName().equals("hashCode") && //$NON-NLS-1$
088: theArguments.length == 0) {
089: result = Integer.valueOf(hashCode());
090: } else if (method.getName().equals("equals") && //$NON-NLS-1$
091: theArguments.length == 1
092: && theArguments[0] != null) {
093: result = Boolean
094: .valueOf(proxy == theArguments[0]);
095: } else if (method.getName().equals("toString") && //$NON-NLS-1$
096: theArguments.length == 0) {
097: result = proxy.getClass().getSimpleName()
098: + toString().substring(
099: getClass().getName().length());
100: }
101: } else if (isValidInvocation(method, theArguments)) {
102: // if listener method
103: try {
104: // extract value from event property name
105: Object[] args = getArgs(theArguments);
106: // extract method to be invoked on target
107: Method m = getMethod(proxy, method,
108: theArguments, args);
109:
110: // we have a valid listener method at this point
111: result = m.invoke(target, args);
112: } catch (RuntimeException e) {
113: throw e;
114: } catch (Throwable t) {
115: System.out.println(t.getClass()
116: + ": " + t.getMessage()); //$NON-NLS-1$
117: }
118: } else {
119: // in order to be compatible with RI
120: if (listenerMethodName.equals(method.getName())) {
121: throw new IllegalArgumentException(Messages
122: .getString("beans.4D")); //$NON-NLS-1$
123: }
124: }
125: }
126: } else {
127: // HARMONY-2495
128: if (null == method) {
129: throw new NullPointerException(Messages
130: .getString("beans.55")); //$NON-NLS-1$
131: }
132: }
133:
134: return result;
135: }
136:
137: public String getListenerMethodName() {
138: return listenerMethodName;
139: }
140:
141: public String getEventPropertyName() {
142: return eventPropertyName;
143: }
144:
145: public String getAction() {
146: return action;
147: }
148:
149: public Object getTarget() {
150: return target;
151: }
152:
153: @SuppressWarnings("unchecked")
154: public static <T> T create(Class<T> listenerInterface,
155: Object target, String action, String eventPropertyName,
156: String listenerMethodName) {
157: if (action == null || target == null
158: || listenerInterface == null) {
159: throw new NullPointerException();
160: }
161: return (T) Proxy.newProxyInstance(target.getClass()
162: .getClassLoader(), new Class[] { listenerInterface },
163: new EventHandler(target, action, eventPropertyName,
164: listenerMethodName));
165: }
166:
167: @SuppressWarnings("unchecked")
168: public static <T> T create(Class<T> listenerInterface,
169: Object target, String action, String eventPropertyName) {
170: return create(listenerInterface, target, action,
171: eventPropertyName, null);
172: }
173:
174: @SuppressWarnings("unchecked")
175: public static <T> T create(Class<T> listenerInterface,
176: Object target, String action) {
177: return create(listenerInterface, target, action, null, null);
178: }
179:
180: private boolean isValidInvocation(Method method, Object[] arguments) {
181: boolean result = false;
182:
183: if (listenerMethodName == null) { // all listener methods are valid
184: result = true;
185: } else if (listenerMethodName.equals(method.getName())) {
186: // method's name matches
187: // no arguments in call are valid
188: if (eventPropertyName == null
189: && (arguments == null || arguments.length == 0)) {
190: result = true;
191: // one-argument call is also valid
192: } else if (arguments != null && arguments.length == 1) {
193: result = true;
194: } else {
195: result = false;
196: }
197: } else {
198: result = false;
199: }
200:
201: return result;
202: }
203:
204: private Object[] getArgs(Object[] arguments) throws Exception {
205: if (eventPropertyName == null) {
206: return new Object[] {};
207: } else if ((arguments == null) || (arguments.length == 0)) {
208: // || (arguments[0] == null)) {
209: return arguments;
210: } else {
211: Object arg = arguments[0];
212: StringTokenizer st = new StringTokenizer(eventPropertyName,
213: "."); //$NON-NLS-1$
214:
215: while (st.hasMoreTokens()) {
216: String propertyName = st.nextToken();
217: PropertyDescriptor pd = findPropertyDescriptor(arg
218: .getClass(), propertyName);
219:
220: if (pd != null) {
221: Method getter = pd.getReadMethod();
222:
223: if (getter != null) {
224: arg = getter.invoke(arg, new Object[] {});
225: } else {
226: throw new IntrospectionException(Messages
227: .getString("beans.11", propertyName)); //$NON-NLS-1$
228: }
229: } else {
230: Method method = findStaticGetter(arg.getClass(),
231: propertyName);
232:
233: if (method != null) {
234: arg = method.invoke(null, new Object[] {});
235: } else {
236: // cannot access property getter
237: // RI throws NPE here so we should do the same
238: throw new NullPointerException(Messages
239: .getString("beans.12", propertyName)); //$NON-NLS-1$
240: }
241: }
242: }
243: return new Object[] { arg };
244: }
245: }
246:
247: private Method getMethod(Object proxy, Method method,
248: Object[] arguments, Object[] args) throws Exception {
249: Method result = null;
250:
251: // filtering - examine if the 'method' could be applied to proxy
252:
253: boolean found = false;
254:
255: // can be invoke with any listener method
256: if (listenerMethodName == null) {
257: Class<?>[] proxyInterfaces = proxy.getClass()
258: .getInterfaces();
259:
260: for (Class<?> proxyInstance : proxyInterfaces) {
261: Method[] interfaceMethods = proxyInstance.getMethods();
262:
263: for (Method listenerMethod : interfaceMethods) {
264: if (equalNames(listenerMethod, method)
265: && canInvokeWithArguments(listenerMethod,
266: arguments)) {
267: found = true;
268: break;
269: }
270: }
271:
272: if (found) {
273: break;
274: }
275: }
276:
277: // can be invoked with a specified listener method
278: } else if (method.getName().equals(listenerMethodName)) {
279: found = true;
280: }
281:
282: if (found == false) {
283: return null;
284: }
285:
286: // 'Method' can be applied to proxy - filtering succeeded
287: try {
288: result = findMethod(target.getClass(), args);
289: if (result == null) {
290: PropertyDescriptor pd = findPropertyDescriptor(target
291: .getClass(), action);
292:
293: if (pd != null) {
294: result = pd.getWriteMethod();
295:
296: if (result == null) {
297: throw new NoSuchMethodException(Messages
298: .getString("beans.13", action)); //$NON-NLS-1$
299: }
300: } else {
301: throw new IndexOutOfBoundsException(Messages
302: .getString("beans.14")); //$NON-NLS-1$
303: }
304: } else {
305: return result;
306: }
307: } catch (IntrospectionException ie) {
308: throw new IndexOutOfBoundsException(Messages
309: .getString("beans.14")); //$NON-NLS-1$
310: }
311:
312: return result;
313: }
314:
315: private PropertyDescriptor findPropertyDescriptor(
316: Class<?> theClass, String propertyName)
317: throws IntrospectionException {
318: PropertyDescriptor result = null;
319: BeanInfo beanInfo = Introspector.getBeanInfo(theClass);
320: PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
321:
322: for (PropertyDescriptor element : pds) {
323: if (element.getName().equals(propertyName)) {
324: result = element;
325: break;
326: }
327: }
328: return result;
329: }
330:
331: private Method findStaticGetter(Class<?> theClass,
332: String propertyName) {
333: Method result = null;
334: Method[] methods = theClass.getMethods();
335:
336: for (Method element : methods) {
337: int modifiers = element.getModifiers();
338:
339: if (Modifier.isStatic(modifiers)
340: && Modifier.isPublic(modifiers)) {
341: String methodName = element.getName();
342: String postfix = null;
343:
344: if (methodName.startsWith("get")) { //$NON-NLS-1$
345: postfix = methodName.substring(3);
346: } else if (methodName.startsWith("is")) { //$NON-NLS-1$
347: postfix = methodName.substring(2);
348: } else {
349: continue;
350: }
351:
352: if ((element.getParameterTypes().length != 0)
353: || (element.getReturnType() == void.class)) {
354: continue;
355: }
356:
357: postfix = Introspector.decapitalize(postfix);
358: if (postfix.equals(propertyName)) {
359: result = element;
360: break;
361: }
362: }
363: }
364:
365: return result;
366: }
367:
368: private Method findMethod(Class<?> type, Object[] args) {
369: Method[] methods = type.getMethods();
370:
371: for (Method element : methods) {
372: if (action.equals(element.getName())
373: && canInvokeWithArguments(element, args)) {
374: return element;
375: }
376: }
377:
378: return null;
379: }
380:
381: private static boolean isPrimitiveWrapper(Class<?> wrapper,
382: Class<?> base) {
383: return (base == boolean.class) && (wrapper == Boolean.class)
384: || (base == byte.class) && (wrapper == Byte.class)
385: || (base == char.class) && (wrapper == Character.class)
386: || (base == short.class) && (wrapper == Short.class)
387: || (base == int.class) && (wrapper == Integer.class)
388: || (base == long.class) && (wrapper == Long.class)
389: || (base == float.class) && (wrapper == Float.class)
390: || (base == double.class) && (wrapper == Double.class);
391: }
392:
393: private static boolean canInvokeWithArguments(Method m,
394: Object[] arguments) {
395: Class<?>[] parameterTypes = m.getParameterTypes();
396:
397: if (parameterTypes.length == arguments.length) {
398: for (int i = 0; i < arguments.length; ++i) {
399: Class<?> argumentType = (arguments[i] == null) ? null
400: : arguments[i].getClass();
401:
402: if ((argumentType == null)
403: || isPrimitiveWrapper(argumentType,
404: parameterTypes[i])) {
405: // ... nothing to do - just not to break the cycle
406: } else if (!argumentType
407: .isAssignableFrom(parameterTypes[i])) {
408: return false;
409: }
410: }
411: } else {
412: return false;
413: }
414:
415: return true;
416:
417: }
418:
419: private static boolean equalNames(Method m1, Method m2) {
420: return m1.getName().equals(m2.getName());
421: }
422: }
|