/**
 * ***************************************************************
 * JADE - Java Agent DEvelopment Framework is a framework to develop
 * multi-agent systems in compliance with the FIPA specifications.
 * Copyright (C) 2000 CSELT S.p.A.
 * 
 * GNU Lesser General Public License
 * 
 * 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,
 * version 2.1 of the License.
 * 
 * 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 jade.content.onto;

//#MIDP_EXCLUDE_FILE

import jade.content.abs.*;
import jade.content.schema.*;
import jade.util.leap.ArrayList;
import jade.util.leap.Collection;
import jade.util.leap.List;
import jade.util.leap.Iterator;
import java.lang.reflect.*;

/**
 * Backward Compatible reflective introspector. This Introspector 
 * uses Java Reflection to translate java objects to/from abstract
 * descriptors as the <code>ReflectiveIntrospector</code> does, but 
 * it assumes the accessors methods for aggregate slots to be in the 
 * "old JADE style" i.e.
 * <i> For every aggregate <b>slot</b> named <code>XXX</code>,
 * with elements of type <code>T</code>, the Java class must have 
 * two accessible methods, with the following signature:</i>
 * <ul>
 *  	<li> <code>Iterator getAllXXX()</code>
 *  	<li> <code>void addXXX(T t)</code>
 * </ul> 
 * <br>
 * <b>NOT available in MIDP</b>
 * <br>
 * @author Giovanni Caire - TILAB
 */
public class BCReflectiveIntrospector extends ReflectiveIntrospector {
	
	protected boolean isAggregateObject(Object obj) {
		//#J2ME_EXCLUDE_BEGIN
		return obj instanceof java.util.Iterator;
		//#J2ME_EXCLUDE_END
		/*#J2ME_INCLUDE_BEGIN
		return obj instanceof Iterator;
		#J2ME_INCLUDE_END*/
	}
	
	public Object getSlotValue(String slotName, Object obj, ObjectSchema schema) throws OntologyException {
		ObjectSchema slotSchema = schema.getSchema(slotName);
		if (slotSchema != null) {
			if (slotSchema instanceof AggregateSchema) {
				return getAggregateSlotValue(slotName, obj);
			}
			else {
				return getScalarSlotValue(slotName, obj);
			}
		}
		else {
			throw new OntologyException("No slot named "+slotName+" found in schema "+schema.getTypeName());
		}
	}
	
	private Object getScalarSlotValue(String slotName, Object obj) throws OntologyException {
		String methodName = "get" + translateName(slotName);
		Method getMethod = findMethodCaseInsensitive(methodName, obj.getClass());
		return invokeAccessorMethod(getMethod, obj);
	}

	private Object getAggregateSlotValue(String slotName, Object obj) throws OntologyException {
		String methodName = "getAll" + translateName(slotName);
		Method getMethod = findMethodCaseInsensitive(methodName, obj.getClass());
		return invokeAccessorMethod(getMethod, obj);
	}
	
	public void setSlotValue(String slotName, Object slotValue, Object obj, ObjectSchema schema) throws OntologyException {
		ObjectSchema slotSchema = schema.getSchema(slotName);
		if (slotSchema != null) {
			String methodName;
			// Note that here checking if absSlotValue is an AbsAggregate would be wrong as we have add methods only if the schema of the slot is AggregateSchema
			if (slotSchema instanceof AggregateSchema) {
				// FIXME: Here we should check for Long --> Integer casting, but how?
				methodName = "add" + translateName(slotName);
				Method addMethod = findMethodCaseInsensitive(methodName, obj.getClass());
				invokeAddMethod(addMethod, obj, slotValue);
			}
			else {
				methodName = "set" + translateName(slotName);
				Method setMethod = findMethodCaseInsensitive(methodName, obj.getClass());
				invokeSetterMethod(setMethod, obj, slotValue);
			}
		}
		else {
			throw new OntologyException("No slot named "+slotName+" found in schema "+schema.getTypeName());
		}
	}
	
	private void invokeAddMethod(Method method, Object obj, 
			Object value) throws OntologyException {
		try {
			Collection c = (Collection) value;
			
			Iterator it = c.iterator();
			while (it.hasNext()) {
				Object ithValue = it.next();
				invokeSetterMethod(method, obj, ithValue);
			}
		} 
		catch (ClassCastException cce) {
			throw new OntologyException("Can't apply recursively method "+method.getName()+" to object "+obj+" as value "+value+" is not a List", cce);
		} 
	} 
	
	/**
	 Check the structure of a java class associated to an ontological element 
	 to ensure that translations to/from abstract descriptors and java objects
	 (instances of that class) can be accomplished by this introspector.
	 @param schema The schema of the ontological element
	 @param javaClass The java class associated to the ontologcal element
	 @param onto The Ontology that uses this Introspector
	 @throws OntologyException if the java class does not have the correct 
	 structure
	 */
	public void checkClass(ObjectSchema schema, Class javaClass, Ontology onto) throws OntologyException {
		String[] slotNames = schema.getNames();
		
		for (int i = 0; i < slotNames.length; ++i) {
			String sName = slotNames[i];
			ObjectSchema slotSchema = schema.getSchema(sName);
			String mName = translateName(sName);
			try {
				// Check for correct set and get methods for the current
				// slot and retrieve the implementation type for values.
				Class slotGetSetClass;
				if (slotSchema instanceof AggregateSchema)
					slotGetSetClass = checkGetAndSet2(mName, javaClass);
				else
					slotGetSetClass = checkGetAndSet(mName, javaClass);
				// If slotSchema is a complex schema and some class C is registered 
				// for that schema, then the implementation class must be a supertype 
				// of C.
				if(!(slotSchema instanceof PrimitiveSchema)) { 
					Class slotClass = onto.getClassForElement(slotSchema.getTypeName());
					if (slotClass != null) {
						if(!slotGetSetClass.isAssignableFrom(slotClass)) {
							throw new OntologyException("Wrong class for schema: "+schema.getTypeName()+". Slot "+sName+": expected class="+slotClass+", Get/Set method class="+slotGetSetClass);
						}
					}
				} 
				else {	
					// The slot has a primitive type
					String type = slotSchema.getTypeName();
					if (type.equals(BasicOntology.STRING)) {
						if (!slotGetSetClass.isAssignableFrom(String.class)) { 
							throw new OntologyException("Wrong class for schema: "+schema.getTypeName()+". Slot "+sName+": expected class="+String.class+", Get/Set method class="+slotGetSetClass);
						}
					}
					else if (type.equals(BasicOntology.INTEGER)) {
						if ((!slotGetSetClass.equals(Integer.TYPE)) &&
								(!slotGetSetClass.equals(Integer.class)) &&
								(!slotGetSetClass.equals(Long.TYPE)) &&
								(!slotGetSetClass.equals(Long.class)) ) { 
							throw new OntologyException("Wrong class for schema: "+schema.getTypeName()+". Slot "+sName+": expected class=INTEGER, Get/Set method class="+slotGetSetClass);
						}
					}
				}
			}
			catch(Exception e) {
				throw new OntologyException("Wrong class for schema: "+schema.getTypeName()+". Slot "+sName+": unexpected error. "+e.getMessage()); 
			}
		}
	}
	
	/**
	 */
	private Class checkGetAndSet(String name, Class c) throws OntologyException {
		Class result;
		Method getMethod = findMethodCaseInsensitive("get" + name, c);
		Method setMethod = findMethodCaseInsensitive("set" + name, c);
		
		// Make sure "get" method takes no arguments.
		Class[] getParams = getMethod.getParameterTypes();
		if(getParams.length > 0)
			throw new OntologyException("Wrong class: method " +  getMethod.getName() + "() must take no arguments.");
		
		// Now find a matching set method.
		result = getMethod.getReturnType();
		
		Class[] setParams = setMethod.getParameterTypes();
		if((setParams.length != 1) || (!setParams[0].equals(result)))
			throw new OntologyException("Wrong class: method " +  setMethod.getName() + "() must take a single argument of type " + result.getName() + ".");
		Class setReturn = setMethod.getReturnType();
		if(!setReturn.equals(Void.TYPE))
			throw new OntologyException("Wrong class: method " +  setMethod.getName() + "() must return void.");
		
		return result;
	}
	
	/**
	 */
	private Class checkGetAndSet2(String name, Class c) throws OntologyException {
		Method getMethod = findMethodCaseInsensitive("getAll" + name, c);
		Method addMethod = findMethodCaseInsensitive("add" + name, c);
		Class result = getArgumentType(addMethod,0);  
		
		// check "get" method 
		if (getArgumentLength(getMethod) != 0)
			throw new OntologyException("Wrong class: method " +  getMethod.getName() + "() must take no arguments.");
		// MODIFIED by GC
		// The return value of the getAllXXX() method of the user defined class 
		// must be a jade.util.leap.Iterator or a super-class/interface of it -->
		// OK if it is a java.util.Iterator.
		if (!(getReturnType(getMethod)).isAssignableFrom(jade.util.leap.Iterator.class))
			throw new OntologyException("Wrong class: method " +  getMethod.getName() + "() must return a jade.util.leap.Iterator." + getReturnType(getMethod).toString());
		
		// check 'add' method 
		if (getArgumentLength(addMethod) != 1)
			throw new OntologyException("Wrong class: method " +  addMethod.getName() + "() must take one argument.");
		if (!getArgumentType(addMethod,0).equals(result))
			throw new OntologyException("Wrong class: method " +  addMethod.getName() + "() has the wrong argument type.");
		if (!getReturnType(addMethod).equals(Void.TYPE))
			throw new OntologyException("Wrong class: method " +  addMethod.getName() + "() must return a void.");
		
		return result;
	}
	
	/**
	 @ return the Class of the argument type number no. of the method m
	 */
	private Class getArgumentType(Method m, int no) {
		Class[] setParams = m.getParameterTypes();
		return setParams[no];
	}
	
	/**
	 * @return the number of arguments of the method m
	 */
	private int getArgumentLength(Method m) {
		Class[] getParams = m.getParameterTypes();
		return getParams.length;
	}
	
	/**
	 @ return the Class of the return type of the method m
	 */
	private Class getReturnType(Method m) {
		return m.getReturnType();
	}
	
	public AbsAggregate externalizeAggregate(String slotName, Object obj, ObjectSchema schema, Ontology referenceOnto) throws OntologyException {
		if (!isAggregateObject(obj)) {
			throw new NotAnAggregate();
		}
		
		AbsAggregate absAggregateValue = null;
		//#J2ME_EXCLUDE_BEGIN
		java.util.Iterator it = (java.util.Iterator) obj;
		//#J2ME_EXCLUDE_END
		/*#J2ME_INCLUDE_BEGIN
		Iterator it = (Iterator) obj;
		#J2ME_INCLUDE_END*/
		if (it.hasNext() || schema.isMandatory(slotName)) {
			String slotSchemaTypeName = schema.getSchema(slotName).getTypeName();
			absAggregateValue = new AbsAggregate(slotSchemaTypeName);
			try {
				while(it.hasNext())
					absAggregateValue.add((AbsTerm)Ontology.externalizeSlotValue(it.next(), this, referenceOnto));
			}
			catch (ClassCastException cce) {
				throw new OntologyException("Non term object in aggregate");
			}
		}
		return absAggregateValue;
	}

	public Object internalizeAggregate(String slotName, AbsAggregate abs, ObjectSchema schema, Ontology referenceOnto) throws OntologyException {
		List l = new ArrayList();
		for (int i = 0; i < abs.size(); i++) {
			Object element = Ontology.internalizeSlotValue(abs.get(i), this, referenceOnto);
			// Check if the element is a Term, a primitive an AID or a List
			Ontology.checkIsTerm(element);
			l.add(element);
		}
		return l;
	}
}

