/*******************************************************************************
 * Copyright (c) 2001, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
/*


 */
package org.eclipse.jem.internal.proxy.remote;

import java.lang.reflect.AccessibleObject;

import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jem.internal.proxy.core.*;
import org.eclipse.jem.internal.proxy.common.remote.Commands;
import org.eclipse.jem.internal.proxy.common.remote.TransmitableArray;

/**
 * Factory for creating method proxies. Creation date: (12/3/99 6:29:40 PM)
 * 
 * @author: Joe Winchester
 */
public class REMMethodProxyFactory implements IMethodProxyFactory {

	final protected REMProxyFactoryRegistry fFactoryRegistry;

	// Cached copy of a few typical method type proxies.
	IREMBeanTypeProxy accessibleType;

	REMMethodTypeProxy methodType;

	REMFieldTypeProxy fieldType;

	REMConstructorTypeProxy ctorType;

	REMMethodProxy getMethodProxy;

	REMMethodProxyFactory(REMProxyFactoryRegistry aRegistry) {
		fFactoryRegistry = aRegistry;
		aRegistry.registerMethodProxyFactory(this);
		REMStandardBeanTypeProxyFactory typeFactory = (REMStandardBeanTypeProxyFactory) aRegistry.getBeanTypeProxyFactory();

		accessibleType = typeFactory.objectClass.newBeanTypeForClass(new Integer(Commands.ACCESSIBLEOBJECT_CLASS), AccessibleObject.class.getName(),
				false);
		methodType = new REMMethodTypeProxy(aRegistry, accessibleType);
		fieldType = new REMFieldTypeProxy(aRegistry, accessibleType);
		ctorType = new REMConstructorTypeProxy(aRegistry, accessibleType);
		aRegistry.getBeanTypeProxyFactory().registerBeanTypeProxy(accessibleType, true);
		aRegistry.getBeanTypeProxyFactory().registerBeanTypeProxy(methodType, true);
		aRegistry.getBeanTypeProxyFactory().registerBeanTypeProxy(fieldType, true);
		aRegistry.getBeanTypeProxyFactory().registerBeanTypeProxy(ctorType, true);

		getMethodProxy = (REMMethodProxy) methodType.newBeanProxy(new Integer(Commands.GET_METHOD_ID));
		((REMStandardBeanProxyFactory) aRegistry.getBeanProxyFactory()).registerProxy(getMethodProxy);

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.jem.internal.proxy.core.IMethodProxyFactory#getMethodProxy(java.lang.String, java.lang.String, java.lang.String[])
	 */
	public IMethodProxy getMethodProxy(String className, String methodName, String[] parameterTypes) {
		IStandardBeanTypeProxyFactory typeFactory = fFactoryRegistry.getBeanTypeProxyFactory();
		IBeanTypeProxy clsProxy = typeFactory.getBeanTypeProxy(className);
		if (clsProxy == null)
			return null;
		// This will redirect to the beantype, which will go through the method cache.
		return clsProxy.getMethodProxy(methodName, parameterTypes);
	}
	
	/**
	 * A helper method to get the method proxy for the expression. This is used by registries and beantype proxies. It will go through
	 * the method cache to make sure we get only one.
	 * 
	 * @param expression
	 * @param aBeanTypeProxy
	 * @param methodName
	 * @param parmTypes
	 * @return
	 * 
	 * @since 1.1.0
	 */
	public IProxyMethod getMethodProxy(IExpression expression, IProxyBeanType aBeanTypeProxy, String methodName, IProxyBeanType[] parmTypes){
		return ((REMStandardBeanTypeProxyFactory) fFactoryRegistry.getBeanTypeProxyFactory()).proxyConstants.getMethodProxy(expression, aBeanTypeProxy, methodName, parmTypes);
	}
	
	/**
	 * A helper method to get the field proxy for the expression. This is used by registries and beantype proxies. It will go through
	 * the method cache to make sure we get only one.
	 * @param expression
	 * @param aBeanTypeProxy
	 * @param fieldName
	 * 
	 * @return
	 * 
	 * @since 1.1.0
	 */
	public IProxyField getFieldProxy(IExpression expression, IProxyBeanType aBeanTypeProxy, String fieldName){
		return ((REMStandardBeanTypeProxyFactory) fFactoryRegistry.getBeanTypeProxyFactory()).proxyConstants.getFieldProxy(expression, aBeanTypeProxy, fieldName);
	}
	

	/* (non-Javadoc)
	 * @see org.eclipse.jem.internal.proxy.core.IMethodProxyFactory#getFieldProxy(org.eclipse.jem.internal.proxy.core.IExpression, java.lang.String, java.lang.String)
	 */
	public IProxyField getFieldProxy(IExpression expression, String className, String fieldName) {
		// We are getting the class resolved through the expression. Might as well because it probably will
		// be needed again and this way when the expression is finished it will be resolved for later usage.
		IStandardBeanTypeProxyFactory beanTypeProxyFactory = fFactoryRegistry.getBeanTypeProxyFactory();
		IProxyBeanType beanType = beanTypeProxyFactory.getBeanTypeProxy(expression, className);
		return beanType.getFieldProxy(expression, fieldName);
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.jem.internal.proxy.core.IMethodProxyFactory#getMethodProxy(org.eclipse.jem.internal.proxy.core.IExpression, java.lang.String, java.lang.String, java.lang.String[])
	 */
	public IProxyMethod getMethodProxy(IExpression expression, String className, String methodName, String[] parameterTypes) {
		// We are getting the class and parmtypes resolved through the expression. Might as well because they probably will
		// be needed again and this way when the expression is finished they will be resolved for later usage.
		IStandardBeanTypeProxyFactory beanTypeProxyFactory = fFactoryRegistry.getBeanTypeProxyFactory();
		IProxyBeanType beanType = beanTypeProxyFactory.getBeanTypeProxy(expression, className);
		IProxyBeanType[] parmTypes = getParameterTypes(expression, parameterTypes, beanTypeProxyFactory);
		return beanType.getMethodProxy(expression, methodName, parmTypes);
	}
	
	/**
	 * Helper method for BeanTypes and proxy Beantypes. So they don't need to have common code to convert string to proxy bean type.
	 * @param expression
	 * @param classType
	 * @param methodName
	 * @param parameterTypes
	 * @return
	 * 
	 * @since 1.1.0
	 */
	public IProxyMethod getMethodProxy(IExpression expression, IProxyBeanType classType, String methodName, String[] parameterTypes) {
		// We are getting the class and parmtypes resolved through the expression. Might as well because they probably will
		// be needed again and this way when the expression is finished they will be resolved for later usage.
		IStandardBeanTypeProxyFactory beanTypeProxyFactory = fFactoryRegistry.getBeanTypeProxyFactory();
		IProxyBeanType[] parmTypes = getParameterTypes(expression, parameterTypes, beanTypeProxyFactory);
		return classType.getMethodProxy(expression, methodName, parmTypes);
	}

	/**
	 * @param expression
	 * @param parameterTypes
	 * @param beanTypeProxyFactory
	 * @return
	 * 
	 * @since 1.1.0
	 */
	protected IProxyBeanType[] getParameterTypes(IExpression expression, String[] parameterTypes, IStandardBeanTypeProxyFactory beanTypeProxyFactory) {
		IProxyBeanType[] parmTypes;
		if (parameterTypes == null || parameterTypes.length == 0)
			parmTypes = null;
		else {
			parmTypes = new IProxyBeanType[parameterTypes.length];
			for (int i = 0; i < parameterTypes.length; i++) {
				parmTypes[i] = beanTypeProxyFactory.getBeanTypeProxy(expression, parameterTypes[i]);
			}
		}
		return parmTypes;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.jem.internal.proxy.core.IMethodProxyFactory#getInvokable(java.lang.String, java.lang.String, java.lang.String[])
	 */
	public IInvokable getInvokable(String className, String methodName, String[] parameterTypes) {
		IStandardBeanTypeProxyFactory typeFactory = fFactoryRegistry.getBeanTypeProxyFactory();
		IBeanTypeProxy clsProxy = typeFactory.getBeanTypeProxy(className);
		if (clsProxy == null)
			return null;
		return clsProxy.getInvokable(methodName, parameterTypes);
	}

	/**
	 * Get the method id from the remote system and create the method proxy.
	 * 
	 * NOTE: It is public ONLY so that IBeanTypeProxy implementations can call it. It must not be used by anyone else.
	 * 
	 * @param beanType
	 * @param methodName
	 * @param parameterTypes
	 * @return
	 * 
	 * @since 1.0.0
	 */
	public IInvokable getInvokable(IREMBeanTypeProxy beanType, String methodName, String[] parameterTypes) {
		// First convert the parameter types into IBeanTypeProxy[].
		IREMBeanTypeProxy[] types = null;
		if (parameterTypes != null) {
			IStandardBeanTypeProxyFactory typeFactory = fFactoryRegistry.getBeanTypeProxyFactory();
			types = new IREMBeanTypeProxy[parameterTypes.length];
			for (int i = 0; i < parameterTypes.length; i++) {
				IBeanTypeProxy type = types[i] = (IREMBeanTypeProxy) typeFactory.getBeanTypeProxy(parameterTypes[i]);
				if (type == null)
					return null; // One of the parm types doesn't exist.
			}
		}

		return getInvokable(beanType, methodName, types);		
	}
	
	/**
	 * Get the method id from the remote system and create the method proxy.
	 * This does not go through the method cache. It goes direct to the remote vm.
	 * 
	 * NOTE: It is public ONLY so that IBeanTypeProxy implementations can call it. It must not be used by anyone else.
	 */
	public IMethodProxy getMethodProxy(IREMBeanTypeProxy beanType, String methodName, String[] parameterTypes) {
		// First convert the parameter types into IBeanTypeProxy[].
		IBeanTypeProxy[] types = null;
		if (parameterTypes != null) {
			IStandardBeanTypeProxyFactory typeFactory = fFactoryRegistry.getBeanTypeProxyFactory();
			types = new IBeanTypeProxy[parameterTypes.length];
			for (int i = 0; i < parameterTypes.length; i++) {
				IBeanTypeProxy type = types[i] = typeFactory.getBeanTypeProxy(parameterTypes[i]);
				if (type == null)
					return null; // One of the parm types doesn't exist.
			}
		}

		return getMethodProxy(beanType, methodName, types);
	}

	
	/**
	 * Get the invokable. 
	 * 
	 * NOTE: It is public ONLY so that IBeanTypeProxy implementations can call it. It must not be used by anyone else.
	 * @param beanType
	 * @param methodName
	 * @param parameterTypes
	 * @return
	 * 
	 * @since 1.0.0
	 */
	public IInvokable getInvokable(IREMBeanTypeProxy beanType, String methodName, IBeanTypeProxy[] parameterTypes) {
		return new REMInvokable(beanType, methodName, parameterTypes);
	}
	
	/**
	 * Get the method id from the remote system and create the method proxy.
	 * <p>
	 * This does not go through the method cache. It goes direct to the remote vm.
	 * 
	 * NOTE: It is public ONLY so that IBeanTypeProxy implementations can call it. It must not be used by anyone else.
	 */
	public IMethodProxy getMethodProxy(IREMBeanTypeProxy beanType, String methodName, IBeanTypeProxy[] parameterTypes) {
		// The parms to getMethod are:
		// parm[0] = String - method name
		// parm[1] = Class[] - Parameter Classes.
		Object[] parms = new Object[] { methodName, parameterTypes != null ? (new TransmitableArray(Commands.CLASS_CLASS, parameterTypes)) : null};
		IBeanProxy result = null;
		try {
			result = getMethodProxy.invokeWithParms(beanType, parms);
			return (IMethodProxy) result;
		} catch (ThrowableProxy e) {
			fFactoryRegistry.releaseProxy(e); // Since it's not needed, get rid of now instead of GC time.
			return null;
		} catch (ClassCastException e) {
			// Some trace msgs because we keep getting into a mode after awhile (though not reproducible) that returns non-method proxies and we need
			// more info.
			StringBuffer buf = new StringBuffer("Method requested is \"" + methodName + "("); //$NON-NLS-1$	//$NON-NLS-2$
			for (int i = 0; i < parameterTypes.length; i++) {
				if (i > 0)
					buf.append(','); //$NON-NLS-1$
				buf.append(parameterTypes[i].getTypeName());
			}
			buf.append(")\""); //$NON-NLS-1$
			ProxyPlugin.getPlugin().getLogger().log(
					new Status(IStatus.ERROR, ProxyPlugin.getPlugin().getBundle().getSymbolicName(), 0, buf.toString(), null));
			try {
				if (result != null) {
					ProxyPlugin.getPlugin().getLogger().log(
							new Status(IStatus.ERROR, ProxyPlugin.getPlugin().getBundle().getSymbolicName(), 0,
									"ClassCastException of result. Return type is \"" + result.getTypeProxy().getTypeName() + "\"\n" + //$NON-NLS-1$	//$NON-NLS-2$
											"Return type bean type proxy class=\"" + result.getTypeProxy().getClass().getName() + "\"\n" + //$NON-NLS-1$	//$NON-NLS-2$
											"GetMethodProxy valid=" + getMethodProxy.isValid() + "\n" + //$NON-NLS-1$	//$NON-NLS-2$
											"GetMethodProxy real name is \"" + getMethodProxy.getName() + "\"\n", null)); //$NON-NLS-1$	//$NON-NLS-2$			
				}
			} catch (Exception e2) {
				ProxyPlugin.getPlugin().getLogger().log(new Status(IStatus.ERROR, ProxyPlugin.getPlugin().getBundle().getSymbolicName(), 0, "", e2)); //$NON-NLS-1$
			}
			throw e; // Rethrow it.
		}
	}

	/*
	 * Terminate the factory. Don't need to do anything because of the proxies being held onto are registered any will be cleaned up themselves.
	 */
	public void terminateFactory(boolean wait) {
	}

}
