/*
 * Copyright (C) 1999-2004 <a href="mailto:mandarax@jbdietrich.com">Jens Dietrich</a>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package org.mandarax.kernel.meta;

import java.lang.reflect.Method;
import org.mandarax.kernel.*;

/**
 * Abstract super class for JFunction and JPredicate.
 * <br>
 * In version 3.2, session support has been added. The last parameter (found in the wrapoped method) 
 * is treated separately if its type is Session (or a subclass of session). Then this parameter is used
 * to pass a session reference at query time to the function or predicate!
 * @see org.mandarax.kernel.Session
 * @author <A HREF="mailto:mandarax@jbdietrich.com">Jens Dietrich</A>
 * @version 3.3.1 <9 August 2004>
 * @since 1.0
 */
public abstract class JConstructor extends org.mandarax.kernel.LObject implements org.mandarax.kernel.Constructor {

    protected transient Method method = null;
    protected Class[] structure = null;
    protected String name = null;
    protected  boolean isSessionParameterSupported = false;


    /**
     * Default constructor.
     */
    JConstructor() {
        super ();
    }

    /**
     * Constructor.
     * @param aMethod the method used
     */
    public JConstructor(java.lang.reflect.Method aMethod) {
        super ();
        setMethod(aMethod);
    }

    /**
     * Constructor.
     * @param aMethod the method used
     * @param aString the name of the object
     */
    public JConstructor(java.lang.reflect.Method aMethod, String aName) {
        super ();
		setMethod(aMethod);
        setName(aName);
    }

    /**
     * Convert a type. This is used to convert
     * primitive types to their respective wrapper types.
     * @return the wrapper class
     * @param aType the primitive type
     */
    protected Class convertType(Class aType) {
        if(aType.isPrimitive ()) {
            if(aType == Integer.TYPE) {
                return Integer.class;
            }
            if(aType == Double.TYPE) {
                return Double.class;
            }
            if(aType == Float.TYPE) {
                return Float.class;
            }
            if(aType == Byte.TYPE) {
                return Byte.class;
            }
            if(aType == Long.TYPE) {
                return Long.class;
            }
            if(aType == Short.TYPE) {
                return Short.class;
            }
            if(aType == Boolean.TYPE) {
                return Boolean.class;
            }
        }

        return aType;
    }

    /**
     * Get the method.
     * @return the wrapped method
     */
    public Method getMethod() {
        return method;
    }

    /**
     * Set the method.
     * @param m a method
     */
    public void setMethod(Method m) {
        method = m;
        // force initialization of structure 
        getStructure();
    }

    /**
     * Get the name.
     * @return the name
     */
    public String getName() {
        return(name == null)
              ? ((method == null)
                 ? "?"
                 : method.getName ())
              : name;
    }

    /**
     * Set the name.
     * @param n a name
     */
    public void setName(String n) {
        name = n;
    }

    /**
     * Get the structure, i.e. the parameter types.
     * The first element is the declaring class of the method!
     * Note that the last parameter is treated separately if its type
     * is Session (or a subclass of session). Then this parameter is used
     * to pass a session reference at query time to the function or predicate!
     * @see org.mandarax.kernel.Session
     * @return the structure of this object
     */
    public java.lang.Class[] getStructure() {
        if(structure == null) {
            Class[] p = method.getParameterTypes();
			isSessionParameterSupported = p!=null && p.length>0 && Session.class.isAssignableFrom(p[p.length-1]);
			int size = isSessionParameterSupported?p.length:p.length+1;
            structure    = new Class[size];
            structure[0] = method.getDeclaringClass();
            for(int i = 1; i < size; i++) {
                structure[i] = convertType (p[i - 1]);
            }
        }
        return structure;
    }
	/**
	 * Indicates whether a session parameter is supported.
	 * This is the case iff the last parameter type is "Session".
	 * @see org.mandarax.kernel.Session
	 * @return a boolean
	 */
	public boolean isSessionParameterSupported() {
		return isSessionParameterSupported;
	}

    /**
     * Get the hash code.
     * @return the hash value
     */
    public int hashCode() {
        return getName ().hashCode ();
    }

    /**
     * Perform the function or predicate using an array of terms as parameters.
     * @return the result of the perform
     * @param parameter an array of terms
     * @param session a session object
     * @throws java.lang.UnsupportedOperationException
     * @throws java.lang.IllegalArgumentException
     */
    public Object perform(Term[] parameter,Session session) throws IllegalArgumentException, UnsupportedOperationException {
        Object   target  = null;
        int paramSize = isSessionParameterSupported?parameter.length:parameter.length-1;
        Object[] objects = new Object[paramSize];
        Term     t;

        for(int i = 0; i < parameter.length; i++) {
            t = parameter[i];
            if(t != null) {
                if(i == 0) {
                    target = t.resolve (session);
                } else {
                    objects[i - 1] = t.resolve (session);
                }
            } else {
                throw new IllegalArgumentException ();
            }
        }
        
        // if the session parameter is supported, pass session
        if (isSessionParameterSupported) objects[paramSize-1] = session;

        try {
            return method.invoke (target, objects);
        } catch(Throwable x) {
            throw new UnsupportedOperationException ("Perform failed for " + this + " with method " + method);
        }
    }

    /**
     * Indicates whether the object (usually a term or a clause set) can be performed
     * using the java semantics.
     * @return true
     */
    public boolean isExecutable() {
        return true;
    }

    /**
     * Get the string representation of the object.
     * @return the string representation of this object
     */
    public String toString() {
        return getName ();
    }
}
