/*
 * 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.Predicate;
import org.mandarax.util.PredicateUtils;

/**
 * A JPredicate is a predicate defined by a java method. Despite it is not
 * enforced here, the method should return a <code>boolean</boolean>,
 * e.g. <code>equals()</code>.
 * Note that the predicate can be negated. E.g., if the <code>equals()</code>
 * associates objects that are equal, the negated <code>equals()</code> predicate
 * associates objects that are <b>not</b> equal. In case the predicate is
 * negated and no name is given, the prefix <i>not</i> is automatically
 * added to the generated name, i.e. to the name of the method.
 * This is important since <code>equals()</code> invokes the name of the predicate
 * (and not the method)!!
 * <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 final class JPredicate extends JConstructor implements Predicate {
	private String[] slotNames = null;
    private boolean isNegated = false;

    /**
     * Constructor.
     */
    public JPredicate() {
        super ();
    }
    
    /**
     * Constructor.
     * @param aMethod the method used
     */
    public JPredicate(Method aMethod) {
        super (aMethod);
    }

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

    /**
     * Constructor.
     * @param aMethod the method used
     * @param aName the name of the object
     * @param boolean true if the predicate is negated, false otherwise
     */
    public JPredicate(Method aMethod, String aName, boolean negated) {
        super (aMethod, aName);

        isNegated = negated;
    }

    /**
     * Constructor.
     * @param aMethod the method used
     * @param boolean true if the predicate is negated, false otherwise
     */
    public JPredicate(Method aMethod, boolean negated) {
        super (aMethod);

        isNegated = negated;
    }

    /**
     * Indicates whether the two objects are equal. This method invokes
     * comparing the names and checking the structure types for compatibility (and not for
     * identity!).
     * @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 org.mandarax.kernel.Predicate) {
            Predicate p      = (Predicate) obj;
            Class[]   c1     = p.getStructure ();
            Class[]   c2     = getStructure ();
            boolean   result = p.getName ().equals (getName ());

            result = result && (c1.length == c2.length);

            for(int i = 0; i < c1.length; i++) {
                result = result
                         && ((c1[i] == c2[i])
                             || (c1[i].isAssignableFrom (c2[i]))
                             || (c2[i].isAssignableFrom (c1[i])));
            }

            return result;
        }

        return false;
    }

    /**
     * Get the name.
     * @return the name of the predicate
     */
    public String getName() {
        if(name == null) {
            String m = (method == null)
                       ? null
                       : method.getName ();

            if(m == null) {
                return "?";
            } else {
                return isNegated
                       ? "not " + m
                       : m;
            }
        } else {
        		// bugfix 1.9
                return isNegated
                   ? "not " + name
                   : name;
        }
    }

    /**
     * Get the hash code of the object.
     * @return the hash value
     */
    public int hashCode() {
        if(getName () == null) {
            return 0;
        } else {
            return getName ().hashCode () + getStructure ().length;
        }
    }

    /**
     * Indicates whether the predicate is negated.
     * @return true if the predicate is negated, false otherwise
     */
    public boolean isNegated() {
        return isNegated;
    }

    /**
     * Set the negated property.
     * @param neg a boolean
     */
    public void setNegated(boolean neg) {
        isNegated = neg;
    }

    /**
     * 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 ());
    }
	/**
	 * Get the slot names.
	 * @return an array of strings, the length of the array is the same as
	 * the length of the array of terms (the structure of the predicate)
	 */
	public String[] getSlotNames() {
		if (slotNames==null) {
			slotNames = new String[getStructure().length];
			for (int i=0;i<slotNames.length;i++) slotNames[i] = PredicateUtils.getSlotName(this,i); 
		}
		return slotNames;
	}
	/**
	 * Set the slot names.
	 * @param names an array of strings, the length of the array is the same as
	 * the length of the array of terms (the structure of the predicate)
	 */
	public void setSlotNames(String[] names) {
		if (names!=null && names.length!=getStructure().length) throw new IllegalArgumentException("Number of slot names and number of slots must match - cannot set slot names for predicate " + this);
		this.slotNames = names;
	}
	/**
	 * Indicates whether the slot names can be modified.
	 * @return a boolean
	 */
	public boolean slotNamesCanBeEdited() {
		return true;
	}
}
