All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.googlecode.openbeans.EventHandler Maven / Gradle / Ivy

The newest version!
/*
 *  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 com.googlecode.openbeans;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.StringTokenizer;

import org.apache.harmony.beans.BeansUtils;
import org.apache.harmony.beans.internal.nls.Messages;

public class EventHandler implements InvocationHandler
{

	private Object						target;

	private String						action;

	private String						eventPropertyName;

	private String						listenerMethodName;

	final private AccessControlContext	context;

	public EventHandler(Object target, String action, String eventPropertyName, String listenerMethodName)
	{
		if (target == null || action == null)
		{
			throw new NullPointerException();
		}
		this.target = target;
		this.action = action;
		this.eventPropertyName = eventPropertyName;
		this.listenerMethodName = listenerMethodName;
		this.context = AccessController.getContext();
	}

	public Object invoke(final Object proxy, final Method method, final Object[] arguments)
	{
		return AccessController.doPrivileged(new PrivilegedAction() {
			public Object run()
			{
				return invokeImpl(proxy, method, arguments);
			}
		}, context);
	}

	private Object invokeImpl(Object proxy, Method method, Object[] arguments)
	{
		Class proxyClass = proxy.getClass();
		Object[] theArguments = arguments == null ? new Object[0] : arguments;
		Object result = null;

		// if a proxy
		if (Proxy.isProxyClass(proxyClass))
		{
			InvocationHandler handler = Proxy.getInvocationHandler(proxy);

			// if a valid object
			if (handler instanceof EventHandler)
			{
				// if the method from the Object class is called
				String methodName = method.getName();
				if (method.getDeclaringClass() == Object.class)
				{
					if (theArguments.length == 0)
					{
						if ("hashCode".equals(methodName)) { //$NON-NLS-1$
							result = Integer.valueOf(hashCode());
						}
						else if ("toString".equals(methodName)) { //$NON-NLS-1$
							result = proxy.getClass().getSimpleName() + toString().substring(getClass().getName().length());
						}
					}
					else if (theArguments.length == 1 && theArguments[0] != null && "equals".equals(methodName)) { //$NON-NLS-1$
						result = Boolean.valueOf(proxy == theArguments[0]);
					}
				}
				else if (isValidInvocation(method, theArguments))
				{
					// if listener method
					try
					{
						// extract value from event property name
						Object[] args = getArgs(theArguments);
						// extract method to be invoked on target
						Method m = getMethod(proxy, method, theArguments, args);

						// we have a valid listener method at this point
						result = m.invoke(target, args);
					}
					catch (RuntimeException e)
					{
						throw e;
					}
					catch (Throwable t)
					{
						throw new RuntimeException(t);
					}
				}
				else
				{
					// in order to be compatible with RI
					if (listenerMethodName.equals(methodName))
					{
						throw new IllegalArgumentException(Messages.getString("beans.4D")); //$NON-NLS-1$
					}
				}
			}
		}
		else
		{
			// HARMONY-2495
			if (null == method)
			{
				throw new NullPointerException(Messages.getString("beans.55")); //$NON-NLS-1$
			}
		}
		return result;
	}

	public String getListenerMethodName()
	{
		return listenerMethodName;
	}

	public String getEventPropertyName()
	{
		return eventPropertyName;
	}

	public String getAction()
	{
		return action;
	}

	public Object getTarget()
	{
		return target;
	}

	@SuppressWarnings("unchecked")
	public static  T create(Class listenerInterface, Object target, String action, String eventPropertyName, String listenerMethodName)
	{
		if (action == null || target == null || listenerInterface == null)
		{
			throw new NullPointerException();
		}
		return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), new Class[] { listenerInterface }, new EventHandler(target, action,
				eventPropertyName, listenerMethodName));
	}

	public static  T create(Class listenerInterface, Object target, String action, String eventPropertyName)
	{
		return create(listenerInterface, target, action, eventPropertyName, null);
	}

	public static  T create(Class listenerInterface, Object target, String action)
	{
		return create(listenerInterface, target, action, null, null);
	}

	private boolean isValidInvocation(Method method, Object[] arguments)
	{
		// all listener methods are valid
		if (listenerMethodName == null)
		{
			return true;
		}

		// method's name matches
		if (listenerMethodName.equals(method.getName()))
		{
			// no arguments in call are valid
			if (eventPropertyName == null && (arguments == null || arguments.length == 0))
			{
				return true;
			}
			// one-argument call is also valid
			if (arguments != null && arguments.length == 1)
			{
				return true;
			}
		}
		return false;
	}

	private Object[] getArgs(Object[] arguments) throws Exception
	{
		if (eventPropertyName == null)
		{
			return new Object[0];
		}
		else if ((arguments == null) || (arguments.length == 0))
		{
			return arguments;
		}
		else
		{
			Object arg = arguments[0];
			StringTokenizer st = new StringTokenizer(eventPropertyName, "."); //$NON-NLS-1$

			while (st.hasMoreTokens())
			{
				String propertyName = st.nextToken();
				PropertyDescriptor pd = findPropertyDescriptor(arg.getClass(), propertyName);

				Method getMethod = null;
				if (pd != null)
				{
					getMethod = pd.getReadMethod();

					if (getMethod != null)
					{
						arg = getMethod.invoke(arg, new Object[] {});
					}
					else
					{
						throw new IntrospectionException(Messages.getString("beans.11", propertyName)); //$NON-NLS-1$
					}
				}
				else
				{
					getMethod = findStaticGetter(arg.getClass(), propertyName);

					if (getMethod != null)
					{
						arg = getMethod.invoke(null, new Object[] {});
					}
					else
					{
						// cannot access property getter
						// RI throws NPE here so we should do the same
						throw new NullPointerException(Messages.getString("beans.12", propertyName)); //$NON-NLS-1$
					}
				}
			}
			return new Object[] { arg };
		}
	}

	private Method getMethod(Object proxy, Method method, Object[] arguments, Object[] args) throws Exception
	{
		// filtering - examine if the 'method' could be applied to proxy
		boolean found = false;

		if (listenerMethodName == null)
		{
			// can be invoke with any listener method
			Class[] proxyInterfaces = proxy.getClass().getInterfaces();
			for (Class proxyInstance : proxyInterfaces)
			{
				Method[] interfaceMethods = proxyInstance.getMethods();
				for (Method listenerMethod : interfaceMethods)
				{
					if (equalNames(listenerMethod, method) && canInvokeWithArguments(listenerMethod, arguments))
					{
						found = true;
						break;
					}
				}

				if (found)
				{
					break;
				}
			}
		}
		else if (listenerMethodName.equals(method.getName()))
		{
			// can be invoked with a specified listener method
			found = true;
		}

		if (found == false)
		{
			return null;
		}

		// 'Method' can be applied to proxy - filtering succeeded
		try
		{
			Method result = findMethod(target.getClass(), args);
			if (result == null)
			{
				PropertyDescriptor pd = findPropertyDescriptor(target.getClass(), action);

				if (pd != null)
				{
					result = pd.getWriteMethod();

					if (result == null)
					{
						throw new NoSuchMethodException(Messages.getString("beans.13", action)); //$NON-NLS-1$
					}
				}
				else
				{
					throw new IndexOutOfBoundsException(Messages.getString("beans.14")); //$NON-NLS-1$
				}
			}
			return result;
		}
		catch (IntrospectionException ie)
		{
			throw new IndexOutOfBoundsException(Messages.getString("beans.14")); //$NON-NLS-1$
		}
	}

	private PropertyDescriptor findPropertyDescriptor(Class theClass, String propertyName) throws IntrospectionException
	{
		BeanInfo beanInfo = Introspector.getBeanInfo(theClass);
		PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();

		for (PropertyDescriptor pd : pds)
		{
			if (pd.getName().equals(propertyName))
			{
				return pd;
			}
		}
		return null;
	}

	private Method findStaticGetter(Class theClass, String propertyName)
	{
		Method[] methods = theClass.getMethods();
		for (Method method : methods)
		{
			int modifiers = method.getModifiers();

			if (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers))
			{
				String methodName = method.getName();
				String postfix = null;

				if (methodName.startsWith("get")) { //$NON-NLS-1$
					postfix = methodName.substring(3);
				}
				else if (methodName.startsWith("is")) { //$NON-NLS-1$
					postfix = methodName.substring(2);
				}
				else
				{
					continue;
				}

				if ((method.getParameterTypes().length != 0) || (method.getReturnType() == void.class))
				{
					continue;
				}

				postfix = Introspector.decapitalize(postfix);
				if (postfix.equals(propertyName))
				{
					return method;
				}
			}
		}
		return null;
	}

	private Method findMethod(Class type, Object[] args)
	{
		Method[] methods = type.getMethods();
		for (Method method : methods)
		{
			if (action.equals(method.getName()) && canInvokeWithArguments(method, args))
			{
				return method;
			}
		}
		return null;
	}

	private static boolean canInvokeWithArguments(Method method, Object[] arguments)
	{
		Class[] parameterTypes = method.getParameterTypes();
		if (parameterTypes.length != arguments.length)
		{
			return false;
		}

		for (int index = 0; index < arguments.length; index++)
		{
			Class argumentType = (arguments[index] == null) ? null : arguments[index].getClass();
			if (argumentType == null || BeansUtils.isPrimitiveWrapper(argumentType, parameterTypes[index]))
			{
				continue;
			}
			if (!argumentType.isAssignableFrom(parameterTypes[index]))
			{
				return false;
			}
		}
		return true;
	}

	private static boolean equalNames(Method m1, Method m2)
	{
		return m1.getName().equals(m2.getName());
	}
}