/*
 * 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.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import org.mandarax.kernel.*;

/**
 * An implementation of function similar to JFunction. The difference is that a DynaBeanFunction
 * works only with methods with one parameter, and these parameter is a string. The value
 * of this string is fixed (propertyName). This is to support "dynamic beans" that keep the state in 
 * a map or a similar structure (and does not use instance variables for this purpose). 
 * The get methods to retrieve the properties by name (e.g., get() in java.util.Map) can then be wrapped
 * as functions using this class. 
 * @author <A HREF="mailto:mandarax@jbdietrich.com">Jens Dietrich</A>
 * @version 3.3.1 <9 August 2004>
 * @since 2.2.1
 */
public final class DynaBeanFunction extends JConstructor implements Function {
	// save as array for efficiency - see resolve
	private String[] propertyNameArray = new String[1];
    /**
     * Constructor.
     * @param aMethod the method used
     */
    public DynaBeanFunction() {
        super ();
    }
    
    /**
     * Constructor.
     * @param aMethod the method used
     * param propertyName the name of the property
     */
    public DynaBeanFunction(Method aMethod,String propertyName) {
        super (aMethod);
        setPropertyName(propertyName);
    }

    /**
     * Constructor.
     * @param aMethod the method used
     * @param propertyName the name of the property
     * @param aName the name of the object
     */
    public DynaBeanFunction(Method aMethod,String propertyName,String aName) {
        super (aMethod, aName);
        setPropertyName(propertyName);
    }

    /**
     * Indicates whether this function equals the object.
     * @param obj the object to compare this object with
     * @return true if the objects are equal, false otherwise
     */
    public boolean equals(Object obj) {
        if(obj instanceof DynaBeanFunction) {
            DynaBeanFunction f      = (DynaBeanFunction) obj;
            Class[]  c1     = f.getStructure ();
            Class[]  c2     = getStructure ();
            boolean  result = f.getName().equals(getName());
			result = result && f.propertyNameArray[0].equals(propertyNameArray[0]);
            result = result && (c1.length == c2.length);
			if (!result) return false;
            for(int i = 0; i < c1.length; i++) {
                if (c1[i] != c2[i]) return false;
            }
            return true;
        }

        return false;
    }

    /**
     * Get the return type.
     * @return the return type
     */
    public Class getReturnType() {
        return convertType (method.getReturnType ());
    }

    /**
     * Read the object from an object input stream.
     * @param in an input stream
     */
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject ();
        String methodName = (String)in.readObject ();
        Class   declaringClass = (Class)in.readObject ();
        Class[] parTypes = (Class[])in.readObject ();        

        try {
            Method m = declaringClass.getMethod (methodName, parTypes);
            method = m;
        } catch(NoSuchMethodException x) {
            throw new IOException ("Cannot find method " + name);
        }
    }

    /**
     * Write the object to an object output stream.
     * @param out an output stream
     */
    private void writeObject(ObjectOutputStream out) throws IOException, ClassNotFoundException {
        out.defaultWriteObject ();
        out.writeObject (method.getName ());
        out.writeObject (method.getDeclaringClass ());
        out.writeObject (method.getParameterTypes ());
    }
	/**
	 * Returns the propertyName.
	 * @return String
	 */
	public String getPropertyName() {
		return propertyNameArray[0];
	}

	/**
	 * Sets the propertyName.
	 * @param propertyName The propertyName to set
	 */
	public void setPropertyName(String propertyName) {
		propertyNameArray[0] = propertyName;
	}
	
	/**
     * 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  = parameter[0].resolve(session);
        try {
            return method.invoke (target, propertyNameArray);
        } catch(Throwable x) {
            throw new UnsupportedOperationException ("Perform failed for DynaBeanFunction with method " + method + " and property " + propertyNameArray[0]);
        }
    }
    
    /**
     * Get the structure, i.e. the parameter types.
     * The first element is the declaring class of the method!
     * @return the structure of this object
     */
    public java.lang.Class[] getStructure() {
        if(structure == null) {
            structure    = new Class[1];
            structure[0] = method.getDeclaringClass ();
        }

        return structure;
    }
    /**
     * Set the method.
     * @param m a method
     */
    public void setMethod(Method m) {        
        if (m!=null && m.getParameterTypes().length!=1) throw new IllegalArgumentException("DynaBeanFunction can wrap only methods with one parameter");
        if (m!=null && m.getParameterTypes()[0]!=String.class) throw new IllegalArgumentException("DynaBeanFunction can wrap only methods with one parameter of the type String");
        super.setMethod(m);
    }
}
