/*
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999 Bull S.A.
 * Contact: jonas-team@objectweb.org
 * 
 * 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.1 of the License, or 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
 *
 * Initial developer(s): Helene Joanin
 * Contributor(s): ______________________________________.
 * 00/09/14 Santiago Gala (sgala@hisitech.com)
 *          Parameters can be followed by a number in the WHERE clause
 * JOnAS 2.2.1 Joe Gittings has proposed to code method signature for security
 *          in order to avoid same signature for inherited methods.
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */


package org.objectweb.jonas_ejb.genic;


import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.StringTokenizer;
import java.util.Vector;

import org.objectweb.jonas_ejb.deployment.api.BeanDesc;
import org.objectweb.jonas_ejb.deployment.api.EntityJdbcCmpDesc;
import org.objectweb.jonas_ejb.deployment.api.FieldJdbcDesc;
import org.objectweb.jonas_ejb.deployment.api.MethodDesc;
import org.objectweb.jonas_ejb.deployment.api.MethodJdbcDesc;
import org.objectweb.jonas_ejb.lib.BeanNaming;
import org.objectweb.jonas_ejb.lib.JavaType;


/**
 * This class is the "Velocity context" for a interface method or a bean method
 * used in the Velocity Templates.
 */

public class VcMethod {

    private String  mName;
    private String  mCapName;
    private StringBuffer mFormalParameters;
    private StringBuffer mActualParameters;
    private StringBuffer mExceptionList;
    private String  mSecuritySignature;
    private int     mTxAttribute;
    private String  mReturnType;
    private boolean isFinderByPk;
    private boolean isFinderSimple;
    private boolean isFinderEnum;
    private boolean isFinderCol;
    private boolean isCreate;
    private boolean isEjbCreate;
    private boolean isRemoveThis;
    private boolean isRemovePk;
    private boolean isRemoveHandle;
    private boolean isEjbRemove;
    private boolean isEjbLoad;
    private boolean isEjbStore;
    private boolean isEjbSetEntityContext;
    private String  mSqlStmt;
    private Vector  mParamWhereList;
    private Method  mMethod;    // Just for explicit error message.

    VcMethod(Method method, MethodDesc methodDesc, BeanDesc dd) {
	/*
	 * The 'method' may be
	 * - a home interface's method
	 * - a local home interface's method
	 * - a remote interface's method
	 * - a local interface's method
	 * - a bean's method as
	 *   setEntityContext(), ejbCreate(), ejbLoad(), ejbStore(), ejbRemove().
	 * In the last case, the 'methodDesc' is null in case of
	 * setEntityContext(), ejbLoad() and ejbStore().
	 */

	Class declClass = method.getDeclaringClass();
	Class params[] = method.getParameterTypes();
	Class exceptions[] = method.getExceptionTypes();
	mMethod = method;

	// Name and CapName
	mName = method.getName();
	mCapName = BeanNaming.firstToUpperCase(mName);

	// FormalParameters and ActualParameters
	mFormalParameters = new StringBuffer();
	mActualParameters = new StringBuffer();
	for (int p=1; p <= params.length; p++) {
	    mFormalParameters.append(JavaType.getName(params[p-1])+" "+"p"+p);
	    mActualParameters.append("p"+p);
	    if (p < params.length) {
		mFormalParameters.append(", ");
		mActualParameters.append(", ");
	    }
	}

	// ExceptionList
	mExceptionList = new StringBuffer();
	for (int e=1; e <= exceptions.length; e++) {
	    mExceptionList.append(exceptions[e-1].getName());
	    if (e < exceptions.length) {
		mExceptionList.append(", ");
	    }
	}

	// SecuritySignature
	mSecuritySignature = null;
	// No SecuritySignature for bean's methods as ejbLoad(), ejbStore(), ...
	if (methodDesc != null) {
	    mSecuritySignature = "";
	    if (methodDesc.getRoleName().length > 0) {
		mSecuritySignature = BeanNaming.getSignature(dd.getEjbName(), method);
	    }
	}

	// TxAttribute
	mTxAttribute = -1;
	// No TxAttribute for bean's methods as ejbLoad(), ejbStore(), ...
	if (methodDesc != null) {
	    mTxAttribute = methodDesc.getTxAttribute();
	}

	// ReturnType
	mReturnType = JavaType.getName(method.getReturnType());

	// isFinderXXX
	isFinderByPk = false;
	isFinderSimple = false;
	isFinderEnum = false;
	isFinderCol = false;
	boolean isFinder = false;
	if (mName.startsWith("find")
	    && (javax.ejb.EJBHome.class.isAssignableFrom(declClass)
		|| javax.ejb.EJBLocalHome.class.isAssignableFrom(declClass))
	    ) {
	    isFinder = true;
	}
	if (isFinder) {
	    isFinderByPk = "findByPrimaryKey".equals(mName);
	    if (java.util.Enumeration.class.equals(method.getReturnType())) {
		isFinderEnum = true;
	    } else if (java.util.Collection.class.equals(method.getReturnType())) {
		isFinderCol = true;
	    } else {
		isFinderSimple = true;
	    }
	}

	// isCreate
	isCreate = false;
	if (mName.startsWith("create")
	    && (javax.ejb.EJBHome.class.isAssignableFrom(declClass)
		|| javax.ejb.EJBLocalHome.class.isAssignableFrom(declClass))
	    ) {
	    isCreate = true;
	}

	// isEjbCreate
	isEjbCreate = false;
	if (mName.startsWith("ejbCreate")
	    && javax.ejb.EnterpriseBean.class.isAssignableFrom(declClass)) {
	    isEjbCreate = true;
	}

	// isRemoveXXX
	isRemoveThis = false;
	isRemovePk = false;
	isRemoveHandle = false;
	boolean isRemove = "remove".equals(mName);
	if (isRemove) {
	    if (declClass.equals(javax.ejb.EJBObject.class)
		|| declClass.equals(javax.ejb.EJBLocalObject.class)) {
		isRemoveThis = true;
	    } else if (declClass.equals(javax.ejb.EJBHome.class)
		       || declClass.equals(javax.ejb.EJBLocalHome.class)) {
		if (params[0].equals(javax.ejb.Handle.class)) {
		    isRemoveHandle = true;
		} else {
		    isRemovePk = true;
		}
	    }
	}

	// isEjbRemove, isEjbLoad, isEjbStore
	isEjbRemove = false;
	isEjbLoad = false;
	isEjbStore = false;
	if (javax.ejb.EnterpriseBean.class.isAssignableFrom(declClass)
	    && (params.length == 0)) {
	    if ("ejbRemove".equals(mName)) {
		isEjbRemove = true;
	    } else if ("ejbLoad".equals(mName)) {
		isEjbLoad = true;
	    } else if ("ejbStore".equals(mName)) {
		isEjbStore = true;
	    }
	}

	// isEjbSetEntityContext
	isEjbSetEntityContext = false;
	if (javax.ejb.EnterpriseBean.class.isAssignableFrom(declClass)) {
	    if ("setEntityContext".equals(mName)
		 && (params.length == 1)
		 && javax.ejb.EntityContext.class.equals(params[0])) {
		isEjbSetEntityContext = true;
	    }
	}

	// For Entity CMP
	// SqlStmt, and ParamsWhere in case of FinderSimple, FinderEnum or FinderCol.
	mSqlStmt = null;
	mParamWhereList = null;
	if (dd instanceof EntityJdbcCmpDesc) {
	    EntityJdbcCmpDesc edd = (EntityJdbcCmpDesc)dd;
	    Field[] fieldsBean = dd.getEjbClass().getFields();
	    StringBuffer lf = new StringBuffer();
	    StringBuffer lv = new StringBuffer();
	    boolean firstLf = true;
	    boolean firstLv = true;
	    if (isEjbCreate) {
		for (int i = 0; i<fieldsBean.length; i++) {
		    Field f = fieldsBean[i];
		    if (edd.hasCmpFieldDesc(f)) {
			FieldJdbcDesc fd = (FieldJdbcDesc)edd.getCmpFieldDesc(f);
			if (firstLv) {
			    firstLv = false;
			} else {
			    lf.append(", ");
			    lv.append(", ");
			}
			lf.append(fd.getJdbcFieldName());
			lv.append("?");
		    }
		}
		mSqlStmt =
		    "insert into "+edd.getJdbcTableName()
		    +" ("+lf+") values ("+lv+")";
	    } else if (isFinderByPk) {
		for (int i = 0; i<fieldsBean.length; i++) {
		    Field f = fieldsBean[i];
		    if (edd.hasCmpFieldDesc(f)) {
			FieldJdbcDesc fd = (FieldJdbcDesc)edd.getCmpFieldDesc(f);
			if (fd.isPrimaryKey()) {
			    if (firstLv) {
				firstLv = false;
			    } else {
				lf.append(", ");
				lv.append(" and ");
			    }
			    lf.append(fd.getJdbcFieldName());
			    lv.append(fd.getJdbcFieldName()+"=?");
			}
		    }
		}
		mSqlStmt =
		    "select "+lf+" from "+edd.getJdbcTableName()
		    +" where "+lv;
	    } else if (isFinderSimple || isFinderEnum || isFinderCol) {
		if (!((MethodJdbcDesc)methodDesc).hasWhereClause()) {
		    throw new Error("No WHERE clause defined for the finder method '"
				    +method.toString()+"'");
		}
		Vector posParams = new Vector();
		String clause_where = parseWhere (((MethodJdbcDesc)methodDesc).getWhereClause(), posParams);
		for (int i = 0; i<fieldsBean.length; i++) {
		    Field f = fieldsBean[i];
		    if (edd.hasCmpFieldDesc(f)) {
			FieldJdbcDesc fd = (FieldJdbcDesc)edd.getCmpFieldDesc(f);
			if (fd.isPrimaryKey()) {
			    if (firstLf) {
				firstLf = false;
			    } else {
				lf.append(", ");
			    }
			    lf.append(fd.getJdbcFieldName());
			}
		    }
		}
		mSqlStmt =
		    "select "+lf+" from "+edd.getJdbcTableName()
		    +" "+clause_where;
		mParamWhereList = new Vector();
		for (int i = 0; i<posParams.size(); i++) {
		    int pos = ((Integer)posParams.elementAt(i)).intValue();
		    if (pos>=params.length) {
			throw new Error("Wrong parameters number between the method definition and the WHERE clause of the method '" + method + "'");
		    }
		    VcParamWhere vpw = new VcParamWhere(params[pos], pos+1);
		    mParamWhereList.addElement(vpw);
		}
	    } else if (isEjbRemove) {
		for (int i = 0; i<fieldsBean.length; i++) {
		    Field f = fieldsBean[i];
		    if (edd.hasCmpFieldDesc(f)) {
			FieldJdbcDesc fd = (FieldJdbcDesc)edd.getCmpFieldDesc(f);
			if (fd.isPrimaryKey()) {
			    if (firstLv) {
				firstLv = false;
			    } else {
				lv.append(" and ");
			    }
			    lv.append(fd.getJdbcFieldName()+"=?");
			}
		    }
		}
		mSqlStmt =
		    "delete from "+edd.getJdbcTableName()
		    +" where "+lv;
	    } else if (isEjbLoad) {
		for (int i = 0; i<fieldsBean.length; i++) {
		    Field f = fieldsBean[i];
		    if (edd.hasCmpFieldDesc(f)) {
			FieldJdbcDesc fd = (FieldJdbcDesc)edd.getCmpFieldDesc(f);
			if (firstLf) {
			    firstLf = false;
			} else {
			    lf.append(", ");
			}
			lf.append(fd.getJdbcFieldName());
			if (fd.isPrimaryKey()) {
			    if (firstLv) {
				firstLv = false;
			    } else {
				lv.append(" and ");
			    }
			    lv.append(fd.getJdbcFieldName()+"=?");
			}
		    }
		}
		mSqlStmt =
		    "select "+lf+" from "+edd.getJdbcTableName()
		    +" where "+lv;
	    } else if (isEjbStore) {
		for (int i = 0; i<fieldsBean.length; i++) {
		    Field f = fieldsBean[i];
		    if (edd.hasCmpFieldDesc(f)) {
			FieldJdbcDesc fd = (FieldJdbcDesc)edd.getCmpFieldDesc(f);
			if (fd.isPrimaryKey()) {
			    if (firstLv) {
				firstLv = false;
			    } else {
				lv.append(" and ");
			    }
			    lv.append(fd.getJdbcFieldName()+"=?");
			} else {
			    if (firstLf) {
				firstLf = false;
			    } else {
				lf.append(", ");
			    }
			    lf.append(fd.getJdbcFieldName()+"=?");
			}
		    }
		}
		mSqlStmt =
		    "update "+edd.getJdbcTableName()
		    +" set "+lf+" where "+lv;
	    }
	}

	// System.out.print("VcMethod: "+method.toString());
	// System.out.println(toString());
    }

    /**
     * Parses Strings with syntax "WHERE summary like ?<nnn>"
     * where <nnn> is a decimal integer that indexes (starting by 0)
     * the parameters in the findByXXX method
     * @author: Santiago Gala (sgala@hisitech.com)
     */
    private String parseWhere(String fragment, Vector pos) {
	StringTokenizer parseWhere = new StringTokenizer(fragment, "?\n", true);
	int numpos = pos.size();
	String new_where = "";
	String tok;
	while (parseWhere.hasMoreTokens()) {
	    tok = parseWhere.nextToken();
	    String numVar = "";
	    if (Character.isDigit(tok.charAt(0))) {
		for(int i = 0; tok.length() > i && Character.isDigit(tok.charAt(i)); i++) {
		    numVar += tok.charAt(i);
		}
		if (numVar.length() > 0) {
		    new_where += tok.substring(numVar.length(),tok.length());
		    pos.setElementAt((Object)new Integer(Integer.parseInt(numVar)-1), numpos - 1);
		}
	    } else {
		new_where += tok;
		if(tok.charAt(0) == '?')
		    pos.addElement(new Integer(numpos++));
	    }
	}
	return new_where;
    }

    public String getName() {
	return mName;
    }
    public String getCapName() {
	return mCapName;
    }
    public String getFormalParameters() {
	return mFormalParameters.toString();
    }
    public String getActualParameters() {
	return mActualParameters.toString();
    }
    public String getExceptionList() {
	return mExceptionList.toString();
    }
    public String getSecuritySignature() {
	if (mSecuritySignature == null) {
	    throw new Error("VcMethod.getSecuritySignature() not available for the method '"
			    +mMethod.toString()+"'");
	}
	return mSecuritySignature;
    }
    public int getTxAttribute() {
	if (mTxAttribute == -1) {
	    throw new Error("VcMethod.getTxAttribute() not available for the method '"
			    +mMethod.toString()+"'");
	}
	return mTxAttribute;
    }
    public String getReturnType() {
	return mReturnType;
    }
    public boolean isFinderByPk() {
	return isFinderByPk;
    }
    public boolean isFinderSimple() {
	return isFinderSimple;
    }
    public boolean isFinderEnum() {
	return isFinderEnum;
    }
    public boolean isFinderCol() {
	return isFinderCol;
    }
    public boolean isCreate() {
	return isCreate;
    }
    public boolean isEjbCreate() {
	return isEjbCreate;
    }
    public boolean isRemoveThis() {
	return isRemoveThis;
    }
    public boolean isRemovePk() {
	return isRemovePk;
    }
    public boolean isRemoveHandle() {
	return isRemoveHandle;
    }
    public boolean isEjbRemove() {
	return isEjbRemove;
    }
    public boolean isEjbLoad() {
	return isEjbLoad;
    }
    public boolean isEjbStore() {
	return isEjbStore;
    }
    public boolean isEjbSetEntityContext() {
	return isEjbSetEntityContext;
    }
    public String getSqlStmt() {
	if (mSqlStmt == null) {
	    throw new Error("VcMethod.getSqlStmt() not available for the method '"
			    +mMethod.toString()+"'");
	}
	return mSqlStmt;
    }
    public Vector getParamWhereList() {
	if (mParamWhereList == null) {
	    throw new Error("VcMethod.getParamWhereList() not available for the method '"
			    +mMethod.toString()+"'");
	}
	return mParamWhereList;
    }

    public String toString() {
	StringBuffer ret = new StringBuffer();
	ret.append("\n    Name                  = "+getName());
	ret.append("\n    CapName               = "+getCapName());
	ret.append("\n    FormalParameters      = "+getFormalParameters());
	ret.append("\n    ActualParameters      = "+getActualParameters());
	ret.append("\n    ExceptionList         = "+getExceptionList());
	if (mSecuritySignature != null) {
	    ret.append("\n    SecuritySignature     = "+getSecuritySignature());
	}
	if (mTxAttribute != -1) {
	    ret.append("\n    TxAttribute           = "+getTxAttribute());
	}
	ret.append("\n    ReturnType            = "+getReturnType());
	ret.append("\n    isFinderByPk          = "+isFinderByPk());
	ret.append("\n    isFinderSimple        = "+isFinderSimple());
	ret.append("\n    isFinderEnum          = "+isFinderEnum());
	ret.append("\n    isFinderCol           = "+isFinderCol());
	ret.append("\n    isCreate              = "+isCreate());
	ret.append("\n    isEjbCreate           = "+isEjbCreate());
	ret.append("\n    isRemoveThis          = "+isRemoveThis());
	ret.append("\n    isRemovePk            = "+isRemovePk());
	ret.append("\n    isRemoveHandle        = "+isRemoveHandle());
	ret.append("\n    isEjbRemove           = "+isEjbRemove());
	ret.append("\n    isEjbLoad             = "+isEjbLoad());
	ret.append("\n    isEjbStore            = "+isEjbStore());
	ret.append("\n    isEjbSetEntityContext = "+isEjbSetEntityContext());
	if (mSqlStmt != null) {
	    ret.append("\n    SqlStmt               = "+getSqlStmt());
	}
	if (mParamWhereList != null) {
	    ret.append("\n    ParamWhereList        = "+getParamWhereList());
	}
	return(ret.toString());
    }

}
