/*
 * The contents of this file are subject to the terms 
 * of the Common Development and Distribution License 
 * (the License).  You may not use this file except in
 * compliance with the License.
 * 
 * You can obtain a copy of the license at 
 * https://glassfish.dev.java.net/public/CDDLv1.0.html or
 * glassfish/bootstrap/legal/CDDLv1.0.txt.
 * See the License for the specific language governing 
 * permissions and limitations under the License.
 * 
 * When distributing Covered Code, include this CDDL 
 * Header Notice in each file and include the License file 
 * at glassfish/bootstrap/legal/CDDLv1.0.txt.  
 * If applicable, add the following below the CDDL Header, 
 * with the fields enclosed by brackets [] replaced by
 * you own identifying information: 
 * "Portions Copyrighted [year] [name of copyright owner]"
 * 
 * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
 */



/*
 * JDOConcreteBeanGenerator.java
 *
 * Created on November 20, 2001
 */

package com.sun.jdo.spi.persistence.support.ejb.ejbc;

import java.util.*;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.text.MessageFormat;

import com.sun.jdo.api.persistence.model.Model;
import com.sun.jdo.api.persistence.model.jdo.*;
import com.sun.jdo.api.persistence.model.mapping.MappingClassElement;
import com.sun.jdo.api.persistence.model.mapping.MappingFieldElement;

import com.sun.jdo.spi.persistence.support.ejb.model.DeploymentDescriptorModel;
import com.sun.jdo.spi.persistence.support.ejb.model.util.NameMapper;
import com.sun.jdo.spi.persistence.support.ejb.ejbqlc.JDOQLElements;

import com.sun.jdo.spi.persistence.utility.I18NHelper;
import com.sun.jdo.spi.persistence.utility.StringHelper;
import com.sun.jdo.spi.persistence.utility.generator.*;
import com.sun.jdo.spi.persistence.utility.generator.io.*;
import com.sun.jdo.spi.persistence.utility.logging.Logger;
 
/*
 * This is the base class for JDO specific generator for the concrete CMP 
 * beans.
 *
 * @author Marina Vatkina
 */
abstract class JDOConcreteBeanGenerator {

    static final Logger logger = LogHelperEJBCompiler.getLogger();

    // used to transform b/w various ejb names and pc class names
    NameMapper nameMapper = null;

    Model model = null;        // used to look up various metadata

    String beanName = null;
    String helperName = null;
    String concreteImplName = null;
    String abstractBean = null;
    String pkClass = null;
    String pcname = null;
    boolean hasLocalInterface;
    boolean hasRemoteInterface;
    boolean isUpdateable;

    String setPKField = null;

    // Name of the PC or PK Class as a single String parameter.
    String[] pcnameParam = new String[1];
    String[] pkClassParam = new String[1];

    static String[] objectType = new String[]{CMPTemplateFormatter.Object_};
    static String[] param0 = new String[]{CMPTemplateFormatter.param0_};
    static String[] param0PM = new String[] {CMPTemplateFormatter.param0_,
                    CMPTemplateFormatter.jdoPersistenceManager_};

    // Generic parameters
    String[] oneParam = new String[1];
    String[] twoParams = new String[2];
    String[] threeParams = new String[3];
    String[] fourParams = new String[4];
    String[] fiveParams = new String[5];
    String[] sixParams = new String[6];

    String[] queryParams = new String[10];

    // StringBuffer for loading nonDFG fields into read-only beans
    StringBuffer loadNonDFGBody = null;

    ClassLoader loader;

    // Writer for the concrete bean.
    JavaClassWriter concreteImplWriter;

    // Writer for the static _JDOHelper class.
    JavaClassWriter jdoHelperWriter;

    /**
     * I18N message handler
     */
    final static ResourceBundle messages = I18NHelper.loadBundle(
        JDOConcreteBeanGenerator.class);

    /**
     * Signature of the input files. 
     */
    String inputFilesSignature;

    /**
     * Signature of the generator classes for the codegen. 
     */
    String generatorClassesSignature;    

    /**
     * Signature with CVS keyword substitution for identifying the generated code
     */
    static final String SIGNATURE = "$RCSfile: JDOConcreteBeanGenerator.java,v $ $Revision: 1.2 $"; //NOI18N
    
    JDOConcreteBeanGenerator(ClassLoader loader,
                             Model model,
                             NameMapper nameMapper)
                             throws IOException {
        this.loader = loader;
        this.model = model;
        this.nameMapper = nameMapper;

        CMPTemplateFormatter.initHelpers();
        CMPROTemplateFormatter.initHelpers();
    }
    
    void setUpdateable(boolean updateable) {
        isUpdateable = updateable;
    }

    /**
     * Validate this CMP bean. To be overridden in subclass if necessary.
     * No generic validation is done at this time.
     * @param methodHelper the AbstractMethodHelper instance that contains
     * all categorized methods and some other convenience methods for this bean.
     * @param beanName the ejb name for this bean.
     * @return a Collection of Exception instances with a separate instance for 
     * each failed validation. This implementation returns an empty collection 
     * because generic validation always succeeds.
     */
    Collection validate(AbstractMethodHelper methodHelper, String beanName) {
        return new ArrayList();
    }

    /**
     * Generate all required classes for this CMP bean. 
     * @param methodHelper the AbstractMethodHelper instance that contains
     * all categorized methods and some other convenience methods for this bean.
     * @param beanName the ejb-name of this bean.
     * @param srcout that path to the generated source files.
     * @param classout that path to the compiled class files.
     * @return a Collection of generated source files.
     */
    Collection generate(AbstractMethodHelper methodHelper, String beanName,
                       File srcout, File classout)
                       throws IOException {

        Collection files = new ArrayList();

        this.beanName = beanName;
        this.abstractBean = nameMapper.getAbstractBeanClassForEjbName(beanName);

        String pkgName = CMPTemplateFormatter.getPackageName(abstractBean);
        concreteImplName = nameMapper.getConcreteBeanClassForEjbName(beanName);
        String shortCmpName = CMPTemplateFormatter.getShortClassName(concreteImplName);

        pcname = nameMapper.getPersistenceClassForEjbName(beanName);
        pcnameParam[0] = pcname;

        PersistenceClassElement pcClassElement =
            model.getPersistenceClass(pcname);

        pkClass = nameMapper.getKeyClassForEjbName(beanName).
            replace('$', '.');
        pkClassParam[0] = pkClass;

        PersistenceFieldElement[] allFields = pcClassElement.getFields();

        String prefix = srcout.getPath() + File.separator + 
            concreteImplName.replace('.', File.separatorChar);
    
        String cmp_file_name = prefix + CMPTemplateFormatter.javaExtension_;
    
        String hlp_file_name = prefix + CMPTemplateFormatter.Helper_ + 
            CMPTemplateFormatter.javaExtension_;

        hasLocalInterface =
            (nameMapper.getLocalInterfaceForEjbName(beanName) != null);
        hasRemoteInterface =
            (nameMapper.getRemoteInterfaceForEjbName(beanName) != null);

        if (logger.isLoggable(Logger.FINE)) {
            logger.fine("allFields: " +                              // NOI18N
                ((allFields != null) ? allFields.length : 0));
            logger.fine("cmp_file_name: " + cmp_file_name); // NOI18N
            logger.fine("hlp_file_name: " + hlp_file_name); // NOI18N
            logger.fine("cmp_name: " + concreteImplName); // NOI18N
            logger.fine("pkClass: " + pkClass); // NOI18N
            logger.fine("PCname: " + pcname); // NOI18N
        }

        File cmp_file = new File(cmp_file_name);
        JavaFileWriter concreteImplFileWriter = new IOJavaFileWriter(cmp_file);
        concreteImplWriter = new IOJavaClassWriter();

        File hlp_file = new File(hlp_file_name);
        JavaFileWriter helperFileWriter = new IOJavaFileWriter(hlp_file);
        jdoHelperWriter = new IOJavaClassWriter();

        // Add package statement to both classes.
        if (pkgName != null && pkgName.length() > 0) {
            concreteImplFileWriter.setPackage(pkgName, null);
            helperFileWriter.setPackage(pkgName, null);
        }

        // Add imports statements to both classes.
        addImportStatements(concreteImplFileWriter, helperFileWriter);

        // Generate class name for the concrete impl.
        oneParam[0] = CMPTemplateFormatter.cmpImplCommentsTemplate;
        concreteImplWriter.setClassDeclaration(Modifier.PUBLIC, 
            shortCmpName, oneParam);

        // Add interfaces to the class declarations.
        addInterfaces();

        concreteImplWriter.setSuperclass(abstractBean);

        // Add no-arg constructor.
        concreteImplWriter.addConstructor(shortCmpName, 
            Modifier.PUBLIC, null, null, null,
            CMPTemplateFormatter.super_, null);

        // Add helper class.
        helperName = shortCmpName + CMPTemplateFormatter.Helper_;

        oneParam[0] = shortCmpName;
        jdoHelperWriter.setClassDeclaration(Modifier.PUBLIC,
            helperName, CMPTemplateFormatter.getBodyAsStrings(
                CMPTemplateFormatter.hcomformatter.format(oneParam)));

        setHelperSuperclass();

        // Print internal variables.
        generateFields();

        // Generate type specific methods.
        generateTypeSpecificMethods(allFields, methodHelper);

        // Add finders for all types and selectors for CMP2.0 only.
        generateFinders(methodHelper);

        // Add ejbCreate<XXX> methods.
        generateCreateMethods(methodHelper.getCreateMethods());

        // Add other required methods.
        generateKnownMethods(methodHelper);

        // Add helper methods for the helper class.
        generateHelperClassMethods();

        // Add conversion methods to the helper class.
        generateConversions();

        // Add ObjectId/PrimaryKey conversion methods.
        generatePKObjectIdConversion(getKeyFields(allFields));

        // Print end of classes.
        concreteImplFileWriter.addClass(concreteImplWriter);
        concreteImplFileWriter.save();

        helperFileWriter.addClass(jdoHelperWriter);
        helperFileWriter.save();

        files.add(cmp_file);
        files.add(hlp_file);

        return files;
    }

    /** Add import statements for for the generated classes.
     */
    void addImportStatements(JavaFileWriter concreteImplFileWriter,
            JavaFileWriter helperFileWriter) throws IOException {

        String[] st = CMPTemplateFormatter.importsArray;
        for (int i = 0; i < st.length; i++) {
            concreteImplFileWriter.addImport(st[i], null);
        }

        st = CMPTemplateFormatter.helperImportsArray;
        for (int i = 0; i < st.length; i++) {
            helperFileWriter.addImport(st[i], null);
        }
    }

    /** 
     * Add interfaces to the class declarations.
     */
    void addInterfaces() throws IOException {

        String[] st = CMPTemplateFormatter.interfacesArray;
        for (int i = 0; i < st.length; i++) {
            concreteImplWriter.addInterface(st[i]);
        }
    }

    /**
     * Super class for the helper class is type specific.
     */
    abstract void setHelperSuperclass() throws IOException;

    /**
     * Generate type specific methods for setters, getters,
     * and any other methods that are completely different
     * between bean types.
     */
    void generateTypeSpecificMethods(
            PersistenceFieldElement[] allFields,
            AbstractMethodHelper methodHelper) 
            throws IOException {

        // Initialize loadNonDFGBody for preloading non-DFG fields
        // in read-only beans.
        if (isUpdateable) {
            loadNonDFGBody = null;
        } else {
            loadNonDFGBody = new StringBuffer();
        }
    }

    /**
     * Sets the signature of the input files for the codegen.
     * @param newSignature The signature of the input files.
     */
    void addCodeGenInputFilesSignature(String newSignature)
    {
        if ((inputFilesSignature == null) || 
            (inputFilesSignature.length() == 0)) {
            inputFilesSignature = newSignature;
        }
        else {
            inputFilesSignature = 
                inputFilesSignature + 
                CMPTemplateFormatter.signatureDelimiter_ + 
                newSignature;
        }
    }    
    
    /**
     * Sets the signature of the generator classes for codegen.
     * @param newSignature The signature of the generator classes.
     */
    void addCodeGeneratorClassSignature(String newSignature)
    {
        if ((generatorClassesSignature == null) || 
            (generatorClassesSignature.length() == 0)) {
            generatorClassesSignature = newSignature;
        }
        else {
            generatorClassesSignature = 
                generatorClassesSignature +
                CMPTemplateFormatter.signatureDelimiter_ +
                newSignature;     
        }
    }

    /**
     * Generates required internal variables.
     */
    void generateFields() throws IOException {

        // Add private transient fields:
        CMPTemplateFormatter.addPrivateField(
            CMPTemplateFormatter.privatetransientvformatter.format(pcnameParam),
            Modifier.TRANSIENT, 
            concreteImplWriter);

        // Add private static fields:
        CMPTemplateFormatter.addPrivateField(
            CMPTemplateFormatter.privateStaticVariablesTemplate,
            Modifier.STATIC, 
            concreteImplWriter);

        // Add private static final fields:
        twoParams[0] = pcname;
        twoParams[1] = beanName;
        CMPTemplateFormatter.addPrivateField(
            CMPTemplateFormatter.privatestaticfinalvformatter.format(twoParams),
            Modifier.STATIC + Modifier.FINAL, 
            concreteImplWriter);

        // Add public static final variables for signatures
        twoParams[0] = generatorClassesSignature;
        twoParams[1] = inputFilesSignature;
        CMPTemplateFormatter.addFields(
             CMPTemplateFormatter.publicstaticfinalvformatter.format(twoParams),
             Modifier.PUBLIC + Modifier.STATIC + Modifier.FINAL,
             concreteImplWriter);
        
        // The static fields holding the Query variables and their monitors
        // are generated during finder/selector method handling.

        CMPTemplateFormatter.addPrivateField(
            CMPTemplateFormatter.otherVariablesTemplate,
            0, concreteImplWriter);

        twoParams[0] = concreteImplName;
        twoParams[1] = beanName;
        CMPTemplateFormatter.addPrivateField(
            CMPTemplateFormatter.hvformatter.format(twoParams),
            Modifier.TRANSIENT+Modifier.STATIC, 
            jdoHelperWriter);

        // Add read-only fields:
        if (!isUpdateable) {
            // private transient fields:
            CMPTemplateFormatter.addPrivateField(
                CMPROTemplateFormatter.privatetransientvformatter.format(pcnameParam),
                Modifier.TRANSIENT, 
                concreteImplWriter);

            // private static fields:
            CMPTemplateFormatter.addPrivateField(
                CMPROTemplateFormatter.privateStaticVariablesTemplate,
                Modifier.STATIC, 
                concreteImplWriter);
        }

    }

    /** Adds ejbFindBy methods.
     */
    void generateFinders(AbstractMethodHelper methodHelper) 
            throws IOException {

        boolean debug = logger.isLoggable(Logger.FINE);
        List finders = methodHelper.getFinders();
        for (int i = 0; i < finders.size(); i++) {
            Method m = (Method)finders.get(i);
            String mname = CMPTemplateFormatter.ejb_ +
                StringHelper.getCapitalizedString(m.getName());

            if (debug) {
                logger.fine("Finder: " + mname); // NOI18N
            }

            if (mname.equals(CMPTemplateFormatter.ejbFindByPrimaryKey_)) {
                // ejbFindByPrimaryKey
                String[] exceptionTypes =
                    CMPTemplateFormatter.getExceptionNames(m);

                oneParam[0] = CMPTemplateFormatter.key_;

                concreteImplWriter.addMethod(CMPTemplateFormatter.ejbFindByPrimaryKey_, // name
                    Modifier.PUBLIC , // modifiers
                    pkClass, // returnType
                    oneParam, // parameterNames
                    pkClassParam, //parameterTypes
                    exceptionTypes,// exceptions
                    CMPTemplateFormatter.ejbFindByPrimaryKeyBody, //body
                    null);// comments

            } else {
                JDOQLElements rs = getJDOQLElements(m, methodHelper);

                // check for single-object finder vs. multi-object finder
                String returnType = isSingleObjectFinder(m) ?
                    pkClass : m.getReturnType().getName();
                CMPTemplateFormatter.addGenericMethod(
                    m, mname, returnType,
                    generateFinderMethodBody(methodHelper, rs, mname, m, returnType, i), 
                    concreteImplWriter);
            }
        }


    }

    /** Returns JDOQLElements instance for this finder method.
     * @param m the finder method as a java.lang.reflect.Method
     * @param methodHelper the AbstractMethodHelper instance that contains
     * all categorized methods and some other convenience methods for this bean.
     * @return JDOQLElements instance.
     */
    abstract JDOQLElements getJDOQLElements(Method m,
            AbstractMethodHelper methodHelper) throws IOException;

    /** Adds ejbCreate<XXX> methods.
     */
    private void generateCreateMethods(List createMethods) throws IOException {
        Class beanClass = null;
        try {
            beanClass = Class.forName(abstractBean, true, loader);
        } catch (Exception e) {
            throw new RuntimeException(e.toString());
        }

        // Store generated getters to avoid duplicates.
        HashSet generated = new HashSet();

        for (int i = 0; i < createMethods.size(); i++) {
            Method m = (Method)createMethods.get(i);
            Method m1 = m;

            // Method name is ejbCreate<Method>
            String createName =CMPTemplateFormatter.ejbCreate_;
            String postCreateName =CMPTemplateFormatter.ejbPostCreate_;
            if (m.getName().length() > 6) {
                String suffix = m.getName().substring(6);
                createName += suffix;
                postCreateName += suffix;
            }

            boolean debug = logger.isLoggable(Logger.FINE);
            if (debug) {
                logger.fine("CreateMethod: " + abstractBean + "" + m.getName()); // NOI18N
                logger.fine("ejbCreateMethod: " + createName); // NOI18N
                logger.fine("ejbPostCreateMethod: " + postCreateName); // NOI18N
            }

            // Get actual method in the bean and resolve exception type to generate...
            try {
                Class[] params = m.getParameterTypes();

                // This is a work around the case when the parameter class loader
                // differs from the given class loader ("loader").
                for (int j = 0; j < params.length; j++) {
                    if (params[j].isPrimitive() ||
                        params[j].getClassLoader() == null ||
                        params[j].getClassLoader().equals(loader)) {
                        continue;
                    }
                    String pname = params[j].getName();

                    if (debug) {
                        logger.fine("Replacing parameter class for: " + pname); // NOI18N
                        logger.fine("Param ClassLoader: " + params[j].getClassLoader());
                        logger.fine("Need ClassLoader: " + loader);
                    }

                    params[j] = Class.forName(pname, true, loader);
                }
                // End of the work around the class loader problem.

                // Cannot use getDeclaredMethod() as the actual method can be in a
                // superclass.
                m = beanClass.getMethod(createName, params);
                m1 = beanClass.getMethod(postCreateName, params);
                if (generated.contains(m)) {
                    // Called from more than one interface - skip it.
                    if (debug) {
                        logger.fine("...generated..."); // NOI18N
                    }

                    continue;

                }
                generated.add(m);

            } catch (Exception e) {
                // method does not exist as ejbCreateXxx. It was a business method.
                continue;
            }

            String[] exc = CMPTemplateFormatter.getExceptionNames(m);
            String parametersList = CMPTemplateFormatter.getParametersList(m);
            String parametersListWithSeparator = makeLiteral(
                    CMPTemplateFormatter.getParametersListWithSeparator(
                            m, CMPTemplateFormatter.paramConcatenator_ ));

            String body = getEJBCreateMethodBody(createName, exc, 
                    parametersList, parametersListWithSeparator);

            CMPTemplateFormatter.addGenericMethod(
                    m, createName, pkClass, body, concreteImplWriter);

            body = getEJBPostCreateMethodBody(postCreateName,
                    parametersList, parametersListWithSeparator);

            CMPTemplateFormatter.addGenericMethod(
                    m1, postCreateName, CMPTemplateFormatter.void_, 
                    body, concreteImplWriter);
        }
    }

    /** Returns method body for EJBCreate method.
     * @param createName the actual name of the method as String.
     * @param exc a String[] of decleared exceptions for this method.
     * @param parametersList the list of method parameters as String.
     * @param parametersListWithSeparator the list of concatenated method 
     * parameters to be passed to another method as String.
     * @return method body as String.
     */
    abstract String getEJBCreateMethodBody(String createName,
            String[] exc, String parametersList,
            String parametersListWithSeparator);

    /** Returns method body for EJBPostCreate method.
     * @param postCreateName the actual name of the method as String.
     * @param parametersList the list of method parameters as String.
     * @param parametersListWithSeparator the list of concatenated method 
     * parameters to be passed to another method as String.
     * @return method body as String.
     */
    abstract String getEJBPostCreateMethodBody(String postCreateName,
            String parametersList, String parametersListWithSeparator);

    /** Returns method body for EJBRemove method.
     * @return method body as String.
     */
    abstract String getEJBRemoveMethodBody();

    /** Adds other known required methods 
     * - CMPTemplateFormatter.commonPublicMethods_
     * - CMPTemplateFormatter.commonPrivateMethods
     * - Other generic methods
     * - Special methods that differ between special types (e.g. read-only
     * beans) to be overridden if necessary
     * - CMPTemplateFormatter.otherPublicMethods_ that differ 
     * between CMP 1.1 and 2.x types are added in subclasses.
     */
    void generateKnownMethods(AbstractMethodHelper methodHelper)
                       throws IOException {

        String[] exc = null;
        String[] st = CMPTemplateFormatter.commonPublicMethodsArray;
        for (int i = 0; i < st.length; i++) {
            String mname = st[i];
            exc = getExceptionList(methodHelper, mname);

            String body = null;
            if (mname.equals(CMPTemplateFormatter.ejbRemove_)) {
                body = getEJBRemoveMethodBody();

            } else if (mname.equals(CMPTemplateFormatter.ejb__flush_)) {
                oneParam[0] = CMPTemplateFormatter.DuplicateKeyException_;
                exc = oneParam;
                body = CMPTemplateFormatter.helpers.getProperty(mname);

            } else {
                body = CMPTemplateFormatter.helpers.getProperty(mname);
            }

            concreteImplWriter.addMethod(mname, // name
                Modifier.PUBLIC, // modifiers
                CMPTemplateFormatter.void_, // returnType
                null, // parameterNames
                null,// parameterTypes
                exc,// exceptions
                CMPTemplateFormatter.getBodyAsStrings(body), // body
                null);// comments

        }

        // This is a cleanup method that is public, but has int param.
        oneParam[0] = CMPTemplateFormatter.int_;
        concreteImplWriter.addMethod(CMPTemplateFormatter.afterCompletion_, // name
            Modifier.PUBLIC, // modifiers
            CMPTemplateFormatter.void_, // returnType
            param0, // parameterNames
            oneParam,// parameterTypes
            null,// exceptions
            CMPTemplateFormatter.afterCompletionBody, // body
            null);// comments

        concreteImplWriter.addMethod(CMPTemplateFormatter.ejb__remove_, // name
            Modifier.PUBLIC , // modifiers
            CMPTemplateFormatter.void_, // returnType
            param0, // parameterNames
            objectType, //parameterTypes
            null,// exceptions
            // body is not defined for this method.
            null, // body
            null);// comments

        String body;
        st = CMPTemplateFormatter.commonPrivateMethodsArray;

        for (int i = 0; i < st.length; i++) {
            String mname = st[i];
            body = CMPTemplateFormatter.helpers.getProperty(mname);

            CMPTemplateFormatter.addGenericMethod(mname, 
                CMPTemplateFormatter.getBodyAsStrings(body), concreteImplWriter);
        }

        // Add jdoArrayCopy to return byte[].
        oneParam[0] = CMPTemplateFormatter.byte_;
        body = CMPTemplateFormatter.jdoarraycopyformatter.format(oneParam);

        oneParam[0] = CMPTemplateFormatter.byteArray_;
        concreteImplWriter.addMethod(
            CMPTemplateFormatter.jdoArrayCopy_, // name
            Modifier.PRIVATE, // modifiers
            CMPTemplateFormatter.byteArray_, // returnType
            param0, // parameterNames
            oneParam,// parameterTypes
            null,// exceptions
            CMPTemplateFormatter.getBodyAsStrings(body), // body
            null);// comments

        // Add setEntityContext
        oneParam[0] = CMPTemplateFormatter.EntityContext_;
        concreteImplWriter.addMethod(CMPTemplateFormatter.setEntityContext_, // name
            Modifier.PUBLIC, // modifiers
            CMPTemplateFormatter.void_, // returnType
            param0, // parameterNames
            oneParam,// parameterTypes
            getExceptionList(methodHelper,
                CMPTemplateFormatter.setEntityContext_,
                oneParam),// exceptions
            CMPTemplateFormatter.setEntityContextBody, // body
            null);// comments

        // Add jdoGetObjectId
        oneParam[0] = CMPTemplateFormatter.key_;

        String[] param = new String[]{concreteImplName};

        concreteImplWriter.addMethod(CMPTemplateFormatter.getObjectId_, // name
            Modifier.PRIVATE , // modifiers
            CMPTemplateFormatter.Object_, // returnType
            oneParam, // parameterNames
            pkClassParam,// parameterTypes
            null,// exceptions
            CMPTemplateFormatter.getBodyAsStrings(
                CMPTemplateFormatter.goidformatter.format(param)), // body
            null);// comments

        // Add jdoGetJdoInstanceClass
        oneParam[0] = CMPTemplateFormatter.jdoGetJdoInstanceClassTemplate;

        CMPTemplateFormatter.addGenericMethod(
                CMPTemplateFormatter.jdoGetJdoInstanceClass_, 
                Modifier.PUBLIC + Modifier.STATIC,
                CMPTemplateFormatter.Class_, oneParam,
                concreteImplWriter);

        generateSpecialKnownMethods();
    }

    /** Adds required methods that differ between special types (e.g. read-only
     * beans) to be overridden if necessary;
     */
    void generateSpecialKnownMethods() throws IOException {

        String[] body = null;

        // For the following methods the method body exists only for
        // updateable beans

        // jdoClosePersistenceManager 
        if (isUpdateable) {
            body = CMPTemplateFormatter.jdoClosePersistenceManagerBody;
        }

        CMPTemplateFormatter.addGenericMethod(
                CMPTemplateFormatter.jdoClosePersistenceManager_, 
                body, concreteImplWriter);

        // assertPersistenceManagerIsNull 
        if (isUpdateable) {
            body = CMPTemplateFormatter.assertPersistenceManagerIsNullBody;
        }
        CMPTemplateFormatter.addGenericMethod(
                CMPTemplateFormatter.assertPersistenceManagerIsNull_, 
                body, concreteImplWriter);

        // assertInTransaction
        if (isUpdateable) {
            oneParam[0] = I18NHelper.getMessage(messages, "EXC_TransactionNotActive"); // NOI18N
            body = CMPTemplateFormatter.getBodyAsStrings(
                CMPTemplateFormatter.intxformatter.format(oneParam));
        }
        CMPTemplateFormatter.addGenericMethod(
                CMPTemplateFormatter.assertInTransaction_, 
                body, concreteImplWriter);

        // For the following methods the method body exists for
        // all bean types

        // Add jdoGetPersistenceManager as method returning 
        // PersistenceManager:
        if (isUpdateable) {
            body = CMPTemplateFormatter.jdoGetPersistenceManagerBody;
        } else {
            body = CMPROTemplateFormatter.jdoGetPersistenceManagerBody;
        }
        CMPTemplateFormatter.addGenericMethod(
                CMPTemplateFormatter.jdoGetPersistenceManager_, 
                CMPTemplateFormatter.jdoPersistenceManagerClass_,
                body, concreteImplWriter);

        // Add jdoLookupPersistenceManagerFactory as static synchronized method:
        oneParam[0] = concreteImplName;
        MessageFormat mformat = null;
        if (isUpdateable) {
            mformat = CMPTemplateFormatter.jdolookuppmfformatter;
        } else {
            mformat = CMPROTemplateFormatter.jdolookuppmfformatter;
        }
        CMPTemplateFormatter.addGenericMethod(
                CMPTemplateFormatter.jdoLookupPersistenceManagerFactory_, 
                Modifier.PRIVATE + Modifier.STATIC + Modifier.SYNCHRONIZED, // modifiers
                CMPTemplateFormatter.getBodyAsStrings(mformat.format(oneParam)), 
                concreteImplWriter);

        // Add jdoGetInstance
        threeParams[0] = pkClass;
        threeParams[1] = pcname;
        threeParams[2] = CMPTemplateFormatter.none_; // will be ignored for updateable beans
        if (isUpdateable) {
            mformat = CMPTemplateFormatter.giformatter;
        } else {
            if (loadNonDFGBody != null) {
                threeParams[2] = loadNonDFGBody.toString();
            }
            mformat = CMPROTemplateFormatter.giformatter;
        }
        CMPTemplateFormatter.addGenericMethod(
                CMPTemplateFormatter.getInstance_, 
                CMPTemplateFormatter.getBodyAsStrings(mformat.format(threeParams)), 
                concreteImplWriter);

        // These are methods that do have arguments.

        // ejb__refresh has method body only for read-only beans
        if (isUpdateable) {
            body = null;
        } else {
            // Reuse threeParams from getInstance_
            body = CMPTemplateFormatter.getBodyAsStrings(
                CMPROTemplateFormatter.ejb__refreshformatter.format(threeParams));
        }
        concreteImplWriter.addMethod(CMPTemplateFormatter.ejb__refresh_, // name
            Modifier.PUBLIC , // modifiers
            CMPTemplateFormatter.void_, // returnType
            param0, // parameterNames
            objectType, //parameterTypes
            null,// exceptions
            body, // body
            null);// comments

        // Add jdoReleasePersistenceManager as method 
        // with PersistenceManager as a param:
        oneParam[0] = CMPTemplateFormatter.jdoPersistenceManagerClass_;
        
        if (isUpdateable) {
            body = CMPTemplateFormatter.jdoReleasePersistenceManagerBody;
        } else {
            body = CMPROTemplateFormatter.jdoReleasePersistenceManagerBody;
        }
        concreteImplWriter.addMethod(CMPTemplateFormatter.jdoReleasePersistenceManager_, // name
            Modifier.PRIVATE, // modifiers
            CMPTemplateFormatter.void_, // returnType
            param0, // parameterNames
            oneParam,// parameterTypes
            null,// exceptions
            body, // body
            null);// comments

    }

    /**
     * Generates helper methods for the helper class.
     */
    void generateHelperClassMethods() throws IOException {
        // Add Helper.assertInstanceOfRemoteInterfaceImpl() method for all beans.
        oneParam[0] = CMPTemplateFormatter.assertInstanceOfRemoteInterfaceImplTemplate;

        jdoHelperWriter.addMethod(CMPTemplateFormatter.assertInstanceOfRemoteInterfaceImpl_, // name
            Modifier.PUBLIC, // modifiers
            CMPTemplateFormatter.void_, // returnType
            param0, // parameterNames
            objectType,// parameterTypes
            null,// exceptions
            oneParam, // body
            null);// comments

        // Add Helper.getHelperInstance() method.
        oneParam[0] = CMPTemplateFormatter.getHelperInstanceTemplate;
        CMPTemplateFormatter.addGenericMethod(
                CMPTemplateFormatter.getHelperInstance_, 
                Modifier.PUBLIC + Modifier.STATIC,
                helperName, oneParam,
                jdoHelperWriter);

        // Add Helper.getContainer() method.
        CMPTemplateFormatter.addGenericMethod(
                CMPTemplateFormatter.getContainer_, 
                Modifier.PUBLIC, CMPTemplateFormatter.Object_, 
                CMPTemplateFormatter.getContainerBody,
                jdoHelperWriter);

        // Add getPCCLass to the helper class
        oneParam[0] = concreteImplName;
        CMPTemplateFormatter.addGenericMethod(
                CMPTemplateFormatter.getPCClass_,
                Modifier.PUBLIC, CMPTemplateFormatter.Class_,
                CMPTemplateFormatter.getBodyAsStrings(
                    CMPTemplateFormatter.pcclassgetterformatter.format(
                    oneParam)),
                jdoHelperWriter);
    }

    private String[] getKeyFields(PersistenceFieldElement[] fields) {
        List returnList = new ArrayList();
        int i, count = ((fields != null) ? fields.length : 0);

        for (i = 0; i < count; i++) {
            PersistenceFieldElement pfe = fields[i];

            if (pfe.isKey())
                returnList.add(pfe.getName());
        }

        return (String[])returnList.toArray(new String[returnList.size()]);
    }

    /** Adds ObjectId/PrimaryKey conversion methods to the helper class.
     */
    private void generatePKObjectIdConversion(String[] keyFields)
                       throws IOException{
        int length = keyFields.length;
        StringBuffer getOid = new StringBuffer(); // PK -> Oid
        StringBuffer getPK = new StringBuffer(); // Oid -> PK
        String[] pkfieldParam = new String[1];

        // Add parameter validation to avoid NullPointerException and
        // other startup lines for the conversions that do not depend
        // on the pk type.
        getOid.append(CMPTemplateFormatter.assertPKNotNullTemplate).
            append(CMPTemplateFormatter.noidformatter.format(pcnameParam));
        getPK.append(CMPTemplateFormatter.assertOidNotNullTemplate).
            append(CMPTemplateFormatter.oidcformatter.format(pcnameParam));

        boolean debug = logger.isLoggable(Logger.FINE);
        if (length == 1) {    
            // only one key field - we don't know yet if there is a special PK class.

            // RESOLVE: Find out what must be null for this case....
            String pkfield = keyFields[0];
            String pkfieldType = model.getFieldType(pcname, pkfield);
            pkfieldParam[0] = pkfield;

            if (debug) {
                logger.fine("pkfield: " + pkfield); // NOI18N
            }

            if (model.isPrimitive(pcname, pkfield) ||
                (!pkClass.equals(pkfieldType) && 
                    !pkClass.equals(Object.class.getName()))) {

                // A single primitive PK field requires a user defined PK class for
                // conversion between Object and primitive value. The same type of conversion
                // is generated for a user defined PK class in case of wrapper field type.
                // Generate conversion as key.id = objectId.id and objectId.id = key.id

                getPK.append(CMPTemplateFormatter.npkformatter.format(pkClassParam));
                getOid.append(CMPTemplateFormatter.pkcformatter.format(pkClassParam));

                pkfieldParam[0] = pkfield;

                getOid.append(
                    CMPTemplateFormatter.oidformatter.format(pkfieldParam));
                getPK.append(
                    CMPTemplateFormatter.pkformatter.format(pkfieldParam));

                getPK.append(CMPTemplateFormatter.returnKey_);

            } else {
                // PK Field is of wrapper type or unknown PK Class - generate conversion
                // as key = objectId.id and objectId.id = key.
                oneParam[0] = pkfieldType;
                getOid.append(CMPTemplateFormatter.pkcformatter.format(oneParam));

                twoParams[0] = pkfield;
                twoParams[1] = pkfieldType;
                getOid.append(
                    requireCloneOnGetAndSet(pkfieldType) ?
                        CMPTemplateFormatter.oid1cloneformatter.format(twoParams) :
                        (requireTrimOnSet(pkfieldType) ?
                            CMPTemplateFormatter.oid1stringformatter.format(pkfieldParam) :
                            CMPTemplateFormatter.oid1formatter.format(pkfieldParam)));

                getPK.append(
                    requireCloneOnGetAndSet(pkfieldType) ?
                        CMPTemplateFormatter.pk1cloneformatter.format(pkfieldParam) :
                        CMPTemplateFormatter.pk1formatter.format(pkfieldParam));

            }
        } else {
            // pkClass declaration with more than 1 field.
            getPK.append(CMPTemplateFormatter.npkformatter.format(pkClassParam));
            getOid.append(CMPTemplateFormatter.pkcformatter.format(pkClassParam));

            for (int i = 0; i < length; i++) {
                String pkfield = keyFields[i];
                pkfieldParam[0] = pkfield;

                if (debug) {
                    logger.fine("pkfield: " + pkfield); // NOI18N
                }

                if (!model.isPrimitive(pcname, pkfield)) {
                    getOid.append(
                        CMPTemplateFormatter.assertpkfieldformatter.format(pkfieldParam));
                }

                String pkfieldType = model.getFieldType(pcname, pkfield);
                twoParams[0] = pkfield;
                twoParams[1] = pkfieldType;
                getOid.append(
                    requireCloneOnGetAndSet(pkfieldType) ?
                        CMPTemplateFormatter.oidcloneformatter.format(twoParams) :
                        (requireTrimOnSet(pkfieldType) ?
                            CMPTemplateFormatter.oidstringformatter.format(pkfieldParam) :
                            CMPTemplateFormatter.oidformatter.format(pkfieldParam)));
                getPK.append(
                    requireCloneOnGetAndSet(pkfieldType) ?
                        CMPTemplateFormatter.pkcloneformatter.format(twoParams) :
                        CMPTemplateFormatter.pkformatter.format(pkfieldParam));
            }

            getPK.append(CMPTemplateFormatter.returnKey_);
        }
        getOid.append(CMPTemplateFormatter.returnOid_);


        // Add ones that can be used for Collection conversion.
        jdoHelperWriter.addMethod(CMPTemplateFormatter.convertPrimaryKeyToObjectId_, // name
            Modifier.PUBLIC, // modifiers
            CMPTemplateFormatter.Object_, // returnType
            param0, // parameterNames
            objectType,// parameterTypes
            null,// exceptions
            CMPTemplateFormatter.getBodyAsStrings(getOid.toString()), // body
            null);// comments

        jdoHelperWriter.addMethod(CMPTemplateFormatter.convertObjectIdToPrimaryKey_, // name
            Modifier.PUBLIC, // modifiers
            CMPTemplateFormatter.Object_, // returnType
            param0, // parameterNames
            objectType,// parameterTypes
            null,// exceptions
            CMPTemplateFormatter.getBodyAsStrings(getPK.toString()), // body
            null);// comments

    }

    /**
     * Returns the name of the concrete bean class for the specified name of
     * the persistence capable class.
     * @param pcClass the name of the persistence capable class
     * @return the name of the corresponding concrete bean class
     */
    String getConcreteBeanForPCClass(String pcClass) {
        return nameMapper.getConcreteBeanClassForEjbName(
            nameMapper.getEjbNameForPersistenceClass(pcClass));
    }

    /**
     * Generates conversion methods from PC to EJBObject and back
     * to the helper class.
     */
    void generateConversions() throws IOException {
        String[] pcParams = new String[] {CMPTemplateFormatter.pc_,
                    CMPTemplateFormatter.jdoPersistenceManager_};
        String[] pcParamTypes = new String[] {CMPTemplateFormatter.Object_,
                    CMPTemplateFormatter.jdoPersistenceManagerClass_};

        String[] collParamTypes = new String[] {CMPTemplateFormatter.Collection_,
                    CMPTemplateFormatter.jdoPersistenceManagerClass_};

        // For PC - PK conversion.
        String[] body = null;

        // Generate for Remote object conversion.
        if (hasRemoteInterface == false) {
            body = CMPTemplateFormatter.getBodyAsStrings(
                CMPTemplateFormatter.returnNull_ );

            jdoHelperWriter.addMethod(CMPTemplateFormatter.convertPCToEJBObject_, // name
                Modifier.PUBLIC, // modifiers
                CMPTemplateFormatter.ejbObject_, // returnType
                pcParams, // parameterNames
                pcParamTypes,// parameterTypes
                null,// exceptions
                body, // body
                null);// comments

            twoParams[0] = CMPTemplateFormatter.ejbObject_;
            twoParams[1] = CMPTemplateFormatter.jdoPersistenceManagerClass_;
            jdoHelperWriter.addMethod(CMPTemplateFormatter.convertEJBObjectToPC_, // name
                Modifier.PUBLIC, // modifiers
                CMPTemplateFormatter.Object_, // returnType
                param0PM, // parameterNames
                twoParams,// parameterTypes
                null,// exceptions
                body, // body
                null);// comments
        }

    }

    /**
     * Verifies if expected exception is part of the throws clause of the
     * corresponding method in the abstract class. 
     * @param exc the list of the Exceptions to check.
     * @param checkExc the Exception to check for as String.
     * @return <code>true</code> if the passed Exception is declared.
     */
    boolean containsException(String[] exc, String checkExc) {
        boolean rc = false;
        if (exc != null) {
            for (int i = 0; i < exc.length; i++) {
                if (exc[i].equals(checkExc)) {
                    rc = true;
                    break;
                }
            }
        }
        return rc;
    }

    /**
     * Verifies if expected exception is part of the throws clause of the
     * corresponding method in the abstract class. Returns EJBException
     * if the requested one is not found.
     * @param exc the list of the Exceptions to check.
     * @param checkExc the Exception to check for as String.
     * @return Exception to be thrown in the try-catch block.
     */
    String getException(String[] exc, String checkExc) {
        return (containsException(exc, checkExc)? checkExc : 
            CMPTemplateFormatter.ejbException_);
    }

    /**
     * Verifies if expected exception or its superclass are part of the 
     * throws clause of the corresponding method in the abstract class. 
     * Returns EJBException if none of the requested exceptions is not found.
     * @param exc the list of the Exceptions to check.
     * @param checkExc the Exception to check for as String.
     * @param superExc the known superclass for the Exception to check for as String.
     * @return Exception to be thrown in the try-catch block.
     */
    String getException(String[] exc, String checkExc, String superExc) {
        String rc = CMPTemplateFormatter.ejbException_;
        if (exc != null) {
            for (int i = 0; i < exc.length; i++) {
                if (exc[i].equals(checkExc) || exc[i].equals(superExc)) {
                    rc = checkExc;
                    break;
                }
            }
        }
        return rc;
    }

    // helper methods to generate finder/selector method bodies

    /**
     * Checks if the finder method is a single-object or multi-object finder.
     * @param finder Method object of the finder
     * @return <code>true</code> if it is a single-object finder
     */
    private boolean isSingleObjectFinder(Method finder) {
        return (!(finder.getReturnType().equals(java.util.Collection.class) ||
                  finder.getReturnType().equals(java.util.Enumeration.class)));
    }

    /**
     * Generates the body of the Entity-Bean finder methods.
     * @param methodHelper the AbstractMethodHelper instance that contains
     * all categorized methods and some other convenience methods for this bean
     * @param jdoqlElements Result of the JDOQL-Compiler
     * @param mname name of the findermethod in
     * the concrete entity bean implementation
     * @param m method instance
     * @param returnType the returnType of this findermethod
     * @param index index of finder method in finders list
     * @return the generated body
     * @exception IOException
     */
    private String generateFinderMethodBody(AbstractMethodHelper methodHelper,
                                            JDOQLElements jdoqlElements,
                                            String mname,
                                            Method m,
                                            String returnType,
                                            int index) throws IOException {

        StringBuffer body = new StringBuffer();
        body.append(CMPTemplateFormatter.assertPersistenceManagerIsNullTemplate);
        body.append(CMPTemplateFormatter.endLine_);
        body.append(generateFinderSelectorCommonBody(methodHelper,
                                                     jdoqlElements,
                                                     mname,
                                                     m,
                                                     returnType,
                                                     index));

        // getting the catch-clause body from the properties
        oneParam[0] = mname;

        // testing if this is a single-object finder
        if (isSingleObjectFinder(m)) {
            // generating the specific single finder method result set handling
            fourParams[0] = mname;
            fourParams[1] = pkClass;
            fourParams[2] = concreteImplName;
            fourParams[3] = CMPTemplateFormatter.catchClauseTemplate;
            body.append(CMPTemplateFormatter.singlefinderformatter.format(fourParams));
        } else {
            // generating the specific multi-object finder method result set handling
            // if CMP11 and the returntype is Enumeration, convert resultCollection
            // to Enumeration else leave Collection
            twoParams[0] = concreteImplName;
            twoParams[1] = CMPTemplateFormatter.catchClauseTemplate;
            if (isFinderReturningEnumeration(m)) {
                body.append(CMPTemplateFormatter.multifinderenumerationformatter.format(twoParams));
            } else {
                body.append(CMPTemplateFormatter.multifinderformatter.format(twoParams));
            }
        }

        return body.toString();
    }

    /**
     * JDOQuery Codegeneration for the common part of the
     * selecter and the finder methods. That consists of:
     * 1. create JDO Query object
     * 2. setting JDO Query elements (declare parameters, set variables etc.)
     * 3. convert parameter values (if available and necessary)
     * 4. execute the JDO Query
     * @param methodHelper the AbstractMethodHelper instance that contains
     * all categorized methods and some other convenience methods for this bean
     * @param jdoqlElements Result of the JDOQL-Compiler
     * @param methodName name of the finder/selector method in
     * the concrete entitybean implementation
     * @param m the method instance
     * @param returnType the return type of the finder/selectormethod
     * @param index index of finder/selector in corresponding list
     * @return the body as a string
     * @exception IOException
     */
    String generateFinderSelectorCommonBody(AbstractMethodHelper methodHelper,
                                                    JDOQLElements jdoqlElements,
                                                    String methodName,
                                                    Method m,
                                                    String returnType,
                                                    int index) throws IOException{
        // unique identifier across finders/selectors
        String queryVariableQualifier = m.getName() + '_' + index;

        // add private static query variables and their monitors
        // no need to check ejbFindByPrimaryKey here
        oneParam[0] = queryVariableQualifier;
        CMPTemplateFormatter.addPrivateField(
            CMPTemplateFormatter.finderselectorstaticvformatter.format(oneParam),
            Modifier.STATIC, 
            concreteImplWriter);
        CMPTemplateFormatter.addPrivateField(
            CMPTemplateFormatter.finderselectorstaticfinalvformatter.format(oneParam),
            Modifier.STATIC + Modifier.FINAL, 
            concreteImplWriter);

        StringBuffer body = new StringBuffer();

        String[] parameterEjbNames = jdoqlElements.getParameterEjbNames();

        // common param check for finder/selector
        body.append(generateFinderSelectorParamCheck(m, parameterEjbNames));
        
        // generating the querydeclaration
        String pcClassName = jdoqlElements.getCandidateClassName();
        String concreteBeanClassName = getConcreteBeanForPCClass(pcClassName);
        queryParams[0] = returnType;
        queryParams[1] = queryVariableQualifier;
        queryParams[2] = concreteBeanClassName;
        queryParams[3] = StringHelper.escape(jdoqlElements.getFilter());
        queryParams[4] = StringHelper.escape(jdoqlElements.getParameters());
        queryParams[5] = StringHelper.escape(jdoqlElements.getVariables());
        queryParams[6] = StringHelper.escape(jdoqlElements.getResult());
        queryParams[7] = StringHelper.escape(jdoqlElements.getOrdering());
        queryParams[8] = Boolean.toString(methodHelper.isQueryPrefetchEnabled(m));
        queryParams[9] = StringHelper.escape(generateQueryIgnoreCache());
        body.append(CMPTemplateFormatter.finderselectorformatter.format(queryParams));

        // now generate the query execution
        // two cases: w/ and w/o query parameters
        String queryParam = generateParamConvBody(m, parameterEjbNames);

        if (jdoqlElements.isAggregate()) {
            if (queryParam == null) {
               oneParam[0] = CMPTemplateFormatter.none_;
               body.append(CMPTemplateFormatter.aggqueryexecformatter.format(oneParam));
            } else {
               oneParam[0] = queryParam;
               body.append(
                 CMPTemplateFormatter.aggqueryexecparamconvformatter.format(oneParam));
            }
        } else {
            if (queryParam == null) {
               oneParam[0] = CMPTemplateFormatter.none_;
               body.append(CMPTemplateFormatter.queryexecformatter.format(oneParam));
            } else {
               oneParam[0] = queryParam;
               body.append(
                 CMPTemplateFormatter.queryexecparamconvformatter.format(oneParam));
            }
        }

        return body.toString();
    }

    /**
     * Generates code that check the finder/selector parameters for the
     * Query.execute call (if necessary).
     * @param m Method instance of the specific finder/selector method
     * @param parameterEjbNames array of ejb names
     * @return the codefragment for the checking local/remote parameters
     *         for method if EJB name is known from ejbql.
     */
    String generateFinderSelectorParamCheck(Method m,
            String[] parameterEjbNames) {
        StringBuffer checkBody = new StringBuffer();

        return checkBody.toString();
    }

    /**
     * Generates a setIgnoreCache(true) call for a JDOQL query, 
     * if necessary.
     * @return the codefragment to set the ignoreCache flag of a JDOQL query.
     */
    String generateQueryIgnoreCache()
    {
        return CMPTemplateFormatter.none_;
    }

    /**
     * Checks if the finder returns an Enumeration.
     * @param finder Methodobject of the finder
     * @return <code>true</code> if the finder returns a Enumeration
     */
    abstract boolean isFinderReturningEnumeration(Method finder); 

    /**
     * Generates code that converts the finder/selector parameters for the
     * Query.execute call (if necessary). It maps local and remote interface
     * values to their corresponding pc instance using JDOHelper methods
     * provided by the concrete entity bean. Primitive type values are wrapped.
     * @param m Method instance of the specific finder/selector method
     * @param parameterEjbNames array of ejb names
     * @return the codefragment for the conversions as a string or null if there
     * aren't any parameters
     */
    private String generateParamConvBody(Method m, String[] parameterEjbNames) {

        StringBuffer paramString = new StringBuffer();
        Class[] paramTypes = m.getParameterTypes();
        int paramLength = paramTypes.length;
        MessageFormat mformat = null;
        String paramClassName = null;

        if (paramLength > 0) {
            // iterate over all paramclasses
            for (int i = 0; i < paramLength; i++) {
                paramClassName = paramTypes[i].getName();

                // if local interface
                if (nameMapper.isLocalInterface(paramClassName) ||
                        nameMapper.isRemoteInterface(paramClassName)) {

                    if (parameterEjbNames[i] != null) {
                        mformat = CMPTemplateFormatter.queryexecparamconvargumentformatter;
                        String concreteImplName =
                            nameMapper.getConcreteBeanClassForEjbName(
                            parameterEjbNames[i]);
                        threeParams[0] = concreteImplName;
                        threeParams[1] = String.valueOf(i);
                        threeParams[2] =
                            nameMapper.isLocalInterface(paramClassName) ?
                            CMPTemplateFormatter.convertEJBLocalObjectToPC_ :
                            CMPTemplateFormatter.convertEJBObjectToPC_;
                        paramString.append(mformat.format(threeParams));
                    } else {
                        paramString.append(CMPTemplateFormatter.param_ + i);
                    }

                // if primitive type, do some wrapping
                } else if(paramTypes[i].isPrimitive()) {
                    paramString.append(
                          JavaClassWriterHelper.getWrapperExpr(
                                             paramTypes[i],
                                             JavaClassWriterHelper.param_ + i
                                             ));

                // else take the param as it is
                } else {
                    paramString.append(CMPTemplateFormatter.param_ + i);
                }
                // normal delimiter
                if (i < paramLength - 1) paramString.append(
                                          CMPTemplateFormatter.paramSeparator_);
            }
        } else return null;

        return paramString.toString();
    }

    /**
     * Returns list of the declared exceptions for the method with this name
     * in the abstract bean. Returns null if such method does not exist
     * or does not have checked exceptions.
     * @param methodHelper the AbstractMethodHelper instance that contains
     * all categorized methods and some other convenience methods for this bean.
     * @param mname method name to check.
     * @param paramTypeNames list of parameter types to the method
     * @return list of the declared exceptions as String[].
     */
    String[] getExceptionList(AbstractMethodHelper methodHelper,
                                      String mname,
                                      String[] paramTypeNames) {
        String[] rc = null;
        Class[] paramTypes = null;

        Map methodNames = methodHelper.getMethodNames();
        Method m = (Method) methodNames.get(mname);

        boolean debug = logger.isLoggable(Logger.FINE);
        if (debug) {
            logger.fine("Processing method: " + mname);
            logger.fine("Known method: " + m);
        }

        if (m == null) {
            // Check the bean class:
            if( paramTypeNames != null ) {
                paramTypes = new Class[ paramTypeNames.length ];
                try {
                    for( int i = paramTypeNames.length - 1; i >= 0; i-- ) {
                        paramTypes[i] = Class.forName( paramTypeNames[i], true, loader );
                    }
                } catch( Exception e ) {
                    // Ignore
                }
            }

            try {
                Class beanClass = Class.forName(abstractBean, true, loader);
                m = beanClass.getMethod(mname, paramTypes);
                if (debug) {
                    logger.fine("Found method: " + m);
                }

            } catch (Exception e) {
                // Ignore. Generate what we know.
            }
        } 

        if (m != null) { 
            rc = CMPTemplateFormatter.getExceptionNames(m);
        }

        return rc;
    }

    /**
     * Returns list of the declared exceptions for the method with this name
     * in the abstract bean. Returns null if such method does not exist
     * or does not have checked exceptions.
     * @param methodHelper the AbstractMethodHelper instance that contains
     * all categorized methods and some other convenience methods for this bean.
     * @param mname method name to check.
     * @return list of the declared exceptions as String[].
     */
    String[] getExceptionList( AbstractMethodHelper methodHelper, String mname ) {
        return getExceptionList( methodHelper, mname, null );
    }

    /**
     * This method will return the given string or "" if it is null or
     * empty string.
     * @param st input string
     */
    private String makeLiteral(String st) {
        return (StringHelper.isEmpty(st)) ? 
            CMPTemplateFormatter.escapedEmptyString_ :
            CMPTemplateFormatter.paramInitializer_ + st;
    }

    /**
     * Returns the signatures of the classes and properties which are
     * involved in the codegen.
     * @return The signatures as a string.
     */
    String getSignaturesOfGeneratorClasses() 
    {
        StringBuffer signatures = new StringBuffer().

            // adding signature of JDOConcreteBeanGenerator
            append(JDOConcreteBeanGenerator.SIGNATURE).
            append(CMPTemplateFormatter.signatureDelimiter_).

            // adding signature of CMPTemplates.properties
            append(CMPTemplateFormatter.signatureTemplate).
            append(CMPTemplateFormatter.signatureDelimiter_).
                
            // adding signature of DeploymentDescriptorModel
            append(DeploymentDescriptorModel.SIGNATURE);
        
        return signatures.toString();
    }

    /** Verifies if this field type requires clone for copy-in, copy-out
     * semantics.
     * @param fieldType the field type as String.
     * @return <code>true</code> if field type requires clone.
     */
    boolean requireCloneOnGetAndSet(String fieldType) {
        return (CMPTemplateFormatter.Date_.equals(fieldType) ||
            CMPTemplateFormatter.SqlDate_.equals(fieldType) ||
            CMPTemplateFormatter.SqlTime_.equals(fieldType) ||
            CMPTemplateFormatter.SqlTimestamp_.equals(fieldType));
    }

    /** Verifies if this field type requires trim on set operation.
     * @param fieldType the field type as String.
     * @return <code>true</code> if field type is java.lang.String.
     */
    boolean requireTrimOnSet(String fieldType) {
        return CMPTemplateFormatter.String_.equals(fieldType);
    }

    /** Generates code that preloads non-DFG fields for read-only beans.
     * @param fieldInfo the FieldInfo instance for this CMP field.
     */
    void loadNonDFGField(FieldInfo fieldInfo) {
        if( !isUpdateable && !fieldInfo.isDFG ) {
            oneParam[0] = fieldInfo.getter;
            loadNonDFGBody.append(
                    CMPROTemplateFormatter.loadNonDFGformatter.format(oneParam));
        }
    }

    /*
     * This class contains the field information to generate get/set methods.
     *
     */
    class FieldInfo {
    
        final PersistenceFieldElement pfe;
    
        final String name;
        final String type;
        final String getter;
        final String setter;
    
        final boolean isKey;
        final boolean isPrimitive;
        final boolean isByteArray;
        final boolean isSerializable;
        final boolean requireCloneOnGetAndSet;
        final boolean isGeneratedField;
        final boolean isDFG;
    
        FieldInfo(Model model, NameMapper nameMapper, 
                PersistenceFieldElement pfe, 
                String beanName, String pcname) {
    
            this.pfe = pfe;
    
            String pfn = pfe.getName();
            name = nameMapper.getEjbFieldForPersistenceField(pcname, pfn);
    
            String fname = StringHelper.getCapitalizedString(name);
            getter = CMPTemplateFormatter.get_ + fname;
            setter = CMPTemplateFormatter.set_ + fname;
    
            boolean debug = logger.isLoggable(Logger.FINE);
            if (debug) {
                logger.fine("-Methods: " + getter + " " + setter); // NOI18N
            }
    
            isKey = pfe.isKey();
            isPrimitive = model.isPrimitive(pcname, pfn);
            isByteArray = model.isByteArray(beanName, name);
            isSerializable = model.isByteArray(pcname, pfn);

            if (isSerializable) {
                // Replace '$' with '.' if it's an inner class.
                type = model.getFieldType(beanName, name).replace('$', '.');
            } else {
                type = model.getFieldType(beanName, name);
            }

            if (debug) {
                logger.fine("Field: " + name + " " + type); // NOI18N
            }
    
            requireCloneOnGetAndSet = requireCloneOnGetAndSet(type);
            isGeneratedField = nameMapper.isGeneratedField(beanName, name);

            // Check if the field is in DFG.
            MappingClassElement mce = model.getMappingClass(pcname);
            MappingFieldElement mfe = mce.getField(name);
            isDFG = (mfe.getFetchGroup() == MappingFieldElement.GROUP_DEFAULT);
        }
    }

}

