/*
 * 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.jena.permissions.impl;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;

import org.apache.jena.permissions.SecuredItem;


/**
 * A generic InvocationHandler that handles the general invocation of the
 * security methods.
 */
public class SecuredItemInvoker implements InvocationHandler
{
	// the equals() method
	private static Method EQUALS;
	// the toString() method
	private static Method TO_STRING;
	// the hashCode() method.
	private static Method HASH_CODE;
	// the instance of SecuredItem that this proxy is using. Must be
	// package-private for ItemHolder use.
	/* package-private */final SecuredItem securedItem;

	final Class<?> securedClass;
	
	// populate the static fields.
	static
	{
		try
		{
			SecuredItemInvoker.EQUALS = Object.class.getMethod("equals",
					Object.class);
			SecuredItemInvoker.TO_STRING = Object.class.getMethod("toString");
			SecuredItemInvoker.HASH_CODE = Object.class.getMethod("hashCode");
		}
		catch (final SecurityException e)
		{
			throw new RuntimeException(e);
		}
		catch (final NoSuchMethodException e)
		{
			throw new RuntimeException(e);
		}
	}

	/**
	 * Constructor.
	 * 
	 * @param securedClass
	 *            The class of the object that is being protected.
	 * @param securedItem
	 *            The implementation of the SecuredItem version of the object.
	 */
	public SecuredItemInvoker( final Class<?> securedClass,
			final SecuredItem securedItem )
	{
		this.securedItem = securedItem;
		this.securedClass = securedClass;
	}

	@Override
	public Object invoke( final Object proxy, final Method method,
			final Object[] args ) throws Throwable
	{

		// check for the special case methods
		if (SecuredItemInvoker.EQUALS.equals(method))
		{
			if (Proxy.isProxyClass(args[0].getClass()))
			{
				return args[0].equals(securedItem);
			}
			else
			{
				return securedItem.equals(args[0]);
			}
		}

		if (SecuredItemInvoker.HASH_CODE.equals(method))
		{
			return securedItem.hashCode();
		}

		if (SecuredItemInvoker.TO_STRING.equals(method))
		{
			return securedItem.toString();
		}

		try
		{
			final Method m = securedItem.getClass().getMethod(method.getName(),
					method.getParameterTypes());
			if (!Modifier.isAbstract(m.getModifiers()))
			{
				try
				{
					SecuredItemImpl.incrementUse();
					try
					{
						return m.invoke(securedItem, args);
					}
					finally
					{
						SecuredItemImpl.decrementUse();
					}

				}
				catch (final java.lang.reflect.InvocationTargetException e2)
				{
					if (e2.getTargetException() instanceof RuntimeException)
					{
						throw e2.getTargetException();
					}
					throw e2;
				}
			}
		}
		catch (final NoSuchMethodException e2)
		{
			// acceptable
		}

		// if we get here then the method is not being proxied so call the
		// original method on the base item.
		return method.invoke(securedItem.getBaseItem(), args);

	}
}
