/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 * 
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, 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/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code.  If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 * 
 * Contributor(s):
 * 
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package com.sun.enterprise.deployment;

import java.util.*;
import java.lang.reflect.*;
import javax.ejb.EJBException;
import com.sun.enterprise.util.LocalStringManagerImpl;
import com.sun.enterprise.util.TypeUtil;
import java.util.logging.Level;
import com.sun.enterprise.deployment.util.DOLUtils;

/** 
 * This class contains information about the persistent state
 * (abstract persistence schema)
 * for EJB2.0 CMP EntityBeans and Join Objects.
 *
 * @author Sanjeev Krishnan
 */

public final class PersistenceDescriptor extends Descriptor {
    
    private Set cmpFields = new HashSet();

    // there can be 0 or more pkey fields 
    // This set contains FieldDescriptors for fields from bean or pkey class 
    private Set pkeyFields = new HashSet(); 

    // true if primkey-field is set or for container-generated pk field
    private boolean pkeyIsOneField = false;

    // false for beans with no primkey-field and pk class = Object
    private boolean pkeyFieldSpecified = true; 

    private String primaryKeyClassName;
    private boolean pkeyStuffInitialized = false;

    // true for beans whose primary fields are all primitive fields
    private boolean pkeyFieldsAllPrimitive = false;
	
    private static LocalStringManagerImpl localStrings =
	    new LocalStringManagerImpl(PersistenceDescriptor.class);

    private EjbCMPEntityDescriptor parentDesc; //the bean whose persistence I describe

    private transient Class persistentClass; // the bean class
    private transient Class stateClass; // the class which holds persistent fields
    private transient Class primaryKeyClass;

    private PersistentFieldInfo[] persFieldInfo;
    private PersistentFieldInfo[] persNoPkeyFieldInfo;
    private PersistentFieldInfo[] pkeyFieldInfo;
    private boolean fieldInfoInitialized=false;
    private PersistentFieldInfo[] fkeyFields;

    private CMRFieldInfo[] cmrFieldInfo;

    private transient Field[] pkeyClassPkeyFields; // fields in primary key class

    private Hashtable queries = new Hashtable();
    private HashSet allQueriedMethods;

    public PersistenceDescriptor() {
    }
    
    /** 
     * The copy constructor.
     */
    public PersistenceDescriptor(PersistenceDescriptor pers) {
	super(pers);

	this.getCMPFields().addAll(pers.getCMPFields());
	//this.primaryKeyFieldDesc = pers.primaryKeyFieldDesc;
    }

    public String getCMRFieldReturnType(String field) {
        String returnType = "java.util.Collection";
        try {
            if( !field.trim().equals("") ) {
                Class persClass = getPersistentClass();
                String methodName = "get" + field.substring(0,1).toUpperCase() 
                    + field.substring(1);
                Method method = TypeUtil.getMethod
                    (persClass, persClass.getClassLoader(), methodName,
                     new String[] {});
                returnType = method.getReturnType().getName();
            }
        } catch(Throwable t) {
            if(DOLUtils.getDefaultLogger().isLoggable(Level.FINE)) {            
                DOLUtils.getDefaultLogger().log(Level.FINE, t.toString(), t);
            }
        }
        
        return returnType;
    }

    /**     
     * Called from EjbCMPEntityDescriptor
     * when some classes in this object are updated.
     */         
    public boolean classesChanged() {

        // XXX Remove any fields marked as persistent that no longer exist
	// in bean class as getter/setter.
        persistentClass = null;
        stateClass = null;
        Class persClass = getPersistentClass();

        Vector fieldDescriptors = parentDesc.getFieldDescriptors();

        // Remove obsolete cmp fields
        if( this.cmpFields != null ) {
            for(Iterator iter = cmpFields.iterator(); iter.hasNext();) {
                FieldDescriptor next = (FieldDescriptor) iter.next();
                if( !fieldDescriptors.contains(next) ) {
                    iter.remove();
                }
            }
        }

        // Remove obsolete pkey fields
        if( this.pkeyFields != null ) {
            for(Iterator iter = pkeyFields.iterator(); iter.hasNext();) {
                FieldDescriptor next = (FieldDescriptor) iter.next();
                if( !fieldDescriptors.contains(next) ) {
                    iter.remove();
                }
            }
        }

        FieldDescriptor primKeyFieldDesc = parentDesc.getPrimaryKeyFieldDesc();
        if( (primKeyFieldDesc != null) && 
            !fieldDescriptors.contains(primKeyFieldDesc) ) {
            parentDesc.setPrimaryKeyFieldDesc(null);
        }


        // CMP 2.x
        // Remove queries for methods no longer in allQueriedMethods

        // First clone old set of queries
        Hashtable queriesClone = (Hashtable) queries.clone();
        
        queries = new Hashtable();
        initializeAllQueriedMethods();

        Iterator it = queriesClone.keySet().iterator();
        while ( it.hasNext() ) {
            Method oldMethod = (Method)it.next();
            Method newMethod = findEquivalentMethod(allQueriedMethods, 
                                                    oldMethod);
            if( newMethod != null ) {
                QueryDescriptor oldQuery = (QueryDescriptor)
                    queriesClone.get(oldMethod);
                QueryDescriptor newQuery = 
                    new QueryDescriptor(oldQuery, newMethod);
                // Only add to list of methods having queries if
                // it's still in the class.
                queries.put(newMethod, newQuery);
            } 
        }

        // Force the persistence descriptor to regenerate its
        // derived info.
        invalidate();

        return false;
    }

    /**
     * Search for a matching method in a list of methods
     * that might have been loaded with a different classloader.
     * Because of this possibility, we can't use 
     * java.lang.reflect.Method.equals().  
     * @return matched method or NULL
     */
    private Method findEquivalentMethod(Collection methods,
                                        Method methodToMatch) {
        Method matchedMethod = null;
        for(Iterator iter = methods.iterator(); iter.hasNext();) {
            Object o = iter.next();
            Method next;
            if (o instanceof Method) {
                next = (Method) o;
            } else {
                next = ((MethodDescriptor) o).getMethod(parentDesc);
                if (next==null) {
                    return null;
                }
            }
            // Compare methods, ignoring declaring class.
            if( methodsEqual(next, methodToMatch, false) ) {
                matchedMethod = next;
                break;
            }
        }
        return matchedMethod;
    }

    /**
     * Checks whether two methods that might have been loaded by
     * different class loaders are equal.  
     * @param compareDeclaringClass if true, declaring class will
     * be considered as part of equality test.  
     */
    private boolean methodsEqual(Method m1, Method m2, 
                                 boolean compareDeclaringClass) {
        boolean equal = false;

        do {

            String m1Name = m1.getName();
            String m2Name = m2.getName();

            if( !m1Name.equals(m2Name) ) { break; }

            String m1DeclaringClass = m1.getDeclaringClass().getName();
            String m2DeclaringClass = m2.getDeclaringClass().getName();

            if( compareDeclaringClass ) {
                if( !m1DeclaringClass.equals(m2DeclaringClass) ) { break; }
            }

            Class[] m1ParamTypes = m1.getParameterTypes();
            Class[] m2ParamTypes = m2.getParameterTypes();
            
            if( m1ParamTypes.length != m2ParamTypes.length ) { break; }

            equal = true;
            for(int pIndex = 0; pIndex < m1ParamTypes.length; pIndex++) {
                String m1ParamClass = m1ParamTypes[pIndex].getName();
                String m2ParamClass = m2ParamTypes[pIndex].getName();
                if( !m1ParamClass.equals(m2ParamClass) ) {
                    equal = false;
                    break;
                }
            } 

        } while(false);

        return equal;
    }

    /**
     * Checks whether two methods that might have been loaded by
     * different class loaders are equal.  
     * @param compareDeclaringClass if true, declaring class will
     * be considered as part of equality test.  
     */
    private boolean methodsEqual(MethodDescriptor m1, Method m2, 
                                 boolean compareDeclaringClass) {
                                     
        Method m = m1.getMethod(parentDesc);
        return methodsEqual(m, m2, compareDeclaringClass);
        
    }    
    
    public void setParentDescriptor(Descriptor parentDesc)
    {
	this.parentDesc = (EjbCMPEntityDescriptor)parentDesc;
    }

    public Descriptor getParentDescriptor()
    {
	return parentDesc;
    }
   
    public EjbBundleDescriptor getEjbBundleDescriptor() 
    {
	 return parentDesc.getEjbBundleDescriptor();
    }

    /**
     * Get all CMR fields of this bean.  All relationships
     * are stored in EjbBundleDescriptor to avoid the complexity of
     * keeping the two sets consistent.  NOTE : To add or remove
     * a relationship use EjbBundleDescriptor.
     */
    public Set getRelationships()
    {
        Set allRelationships = getEjbBundleDescriptor().getRelationships();
        Set myRelationships  = new HashSet();
        for(Iterator iter = allRelationships.iterator(); iter.hasNext();) {
            RelationshipDescriptor next = (RelationshipDescriptor) iter.next();
            if( next.hasParticipant(parentDesc) ) {
                myRelationships.add(next);
            }
        }
        return myRelationships;
    }

    /**
     * Return array of CMRFieldInfo objects for all CMR fields.
     */
    public CMRFieldInfo[] getCMRFieldInfo() 
    {
	if ( cmrFieldInfo == null ) {
	    try {
		initCMRFieldStuff();
	    } catch ( Exception ex ) {
                DOLUtils.getDefaultLogger().log(Level.SEVERE, "enterprise.deployment.backend.invalidDescriptorMappingFailure",
                    new Object[] {ex.toString()});
		throw new EJBException(ex);
	    }
	}
	return cmrFieldInfo;
    }

    /** 
     * Return the CMRFieldInfo object for the given CMR field
     */
    public CMRFieldInfo getCMRFieldInfoByName(String fieldName)
    {
	CMRFieldInfo[] cmrf = this.getCMRFieldInfo();
	for ( int i=0; i<cmrf.length; i++ ) {
	    if ( cmrf[i].name.equals(fieldName) )
		return cmrf[i];
	}
	throw new EJBException("CMRFieldInfo not found for field "+fieldName);
    }

    /**
     * Ensures that persistence descriptor will regenerate its
     * derived information after changes have been made to 
     * persistent characteristics.
     */
    public void invalidate() {
        cmrFieldInfo = null;
	persFieldInfo = null;
        fieldInfoInitialized = false;
        pkeyStuffInitialized = false;
    }

    private void initCMRFieldStuff()
	throws Exception
    {
	if ( !fieldInfoInitialized )
	    initializeFieldInfo();

        Set relationships = getRelationships();
	Iterator it = relationships.iterator();
        // set to the biggest possible size when all relationships
        // are self-referencing
	CMRFieldInfo[] cmrFieldInfo2 = 
            new CMRFieldInfo[relationships.size() * 2];
	int count = 0;
	while ( it.hasNext() ) { 
	    RelationshipDescriptor rd = (RelationshipDescriptor)it.next();
	    RelationRoleDescriptor source = rd.getSource();
	    RelationRoleDescriptor sink = rd.getSink();
	    RelationRoleDescriptor myroles[];
            // if this is a self-referencing relationship, initialize 
            // both source and sink cmr fields
	    if ( source.getPersistenceDescriptor() ==  
                sink.getPersistenceDescriptor() ) {
                myroles = new RelationRoleDescriptor[2];
                myroles[0] = source;
                myroles[1] = sink;
            } else {
                myroles = new RelationRoleDescriptor[1];
                if ( source.getPersistenceDescriptor() == this ) {
                    myroles[0] = source;
                } else { 
                    myroles[0] = sink;
                }
            }
            
            // for all relation role elements in myroles, initialize
            // their cmr fields and put them in cmrFieldInfo2
            for (int ii = 0; ii < myroles.length; ii++) {
	        CMRFieldInfo finfo = new CMRFieldInfo();
	        cmrFieldInfo2[count++] = finfo;

	        PersistenceDescriptor partnerPers = 
		    myroles[ii].getPartner().getPersistenceDescriptor();

	        // Check if partner has a local interface
	        EjbCMPEntityDescriptor partner = 
                    myroles[ii].getPartner().getOwner();
                if ( !partner.isLocalInterfacesSupported() &&
		    myroles[ii].getCMRField() != null ) {
      		    throw new RuntimeException(
                        "No local interface for target bean of CMR field");
	        }

	        String type; 
	        if ( myroles[ii].getPartner().getIsMany() == false ) {
	      	    // 1-1 and many-1
                    if ( partner.isLocalInterfacesSupported() )
		        type = partner.getLocalClassName();
	       	    else 
                        // a unidirectional relation, partner is 
                        // remote-only bean
		        type = partner.getPrimaryKeyClassName();
	        }
	        else {
	      	    // 1-many and many-many
		    type = myroles[ii].getCMRFieldType();
      		    if ( type == null ) { 
		        // A unidirectional relationship from partner 
                        // to this obj
		        type = "java.util.Collection";
    		    }
	        }
	        finfo.type = getClass(type);

	        finfo.name = myroles[ii].getCMRField();
	        if ( finfo.name == null ) {
	       	    // A unidirectional relationship from partner to this obj.
	      	    // We need to maintain a pointer to partner anyway. 
       		    finfo.name = myroles[ii].composeReverseCmrFieldName();
	        }
	        finfo.role = myroles[ii];
	        myroles[ii].setCMRFieldInfo(finfo);

	        if ( rd.isOneOne() && fkeyFields != null ) {
	    	    // set foreign key fields corresponding to this CMR field
		    PersistentFieldInfo[] cmrFkeyFields;
      		    PersistentFieldInfo[] partnerPkeyFields =
					    partnerPers.getPkeyFieldInfo();
		    cmrFkeyFields = 
			new PersistentFieldInfo[partnerPkeyFields.length];
		    for ( int i=0; i<partnerPkeyFields.length; i++ ) {
		        String fkeyName = "_" + finfo.name + "_" 
					    + partnerPkeyFields[i].name;
		        for ( int j=0; j<fkeyFields.length; j++ ) {
		    	    if ( fkeyFields[j].name.equals(fkeyName) )
			        cmrFkeyFields[i] = fkeyFields[j];
		        }
		    }
		    finfo.fkeyFields = cmrFkeyFields;
	        }	
            }
        }

        // initialize the cmrFieldInfo array with the actual size
        // and copy the non-null values of cmrFieldInfo2 to it
        cmrFieldInfo = new CMRFieldInfo[count];
        System.arraycopy(cmrFieldInfo2, 0, cmrFieldInfo, 0, count);

	// Sort cmrFieldInfo in alphabetical order of CMR field name
        for ( int i=cmrFieldInfo.length-1; i>0; i-- ) {
            for ( int j=0; j<i; j++ ) {
                if ( cmrFieldInfo[j].name.compareTo(cmrFieldInfo[j+1].name) 
									> 0 ) {
                    CMRFieldInfo tmp = cmrFieldInfo[j];
                    cmrFieldInfo[j] = cmrFieldInfo[j+1];
                    cmrFieldInfo[j+1] = tmp;
                }
            }
        }
    }

    public void clearCMPFields() {
        this.cmpFields.clear();
        setCMPFields(this.cmpFields);
    }

    public void addCMPField(String field) {
        addCMPField(new FieldDescriptor(field));
    }

    public void addCMPField(FieldDescriptor fieldDesc) {
        this.cmpFields.add(fieldDesc);
        setCMPFields(this.cmpFields);
    }

    public void removeCMPField(String field) {
        removeCMPField(new FieldDescriptor(field));
    }

    public void removeCMPField(FieldDescriptor fieldDesc) {
        this.cmpFields.remove(fieldDesc);
        setCMPFields(this.cmpFields);
    }

    /**
     * Set the FieldDescriptor objects that the EJB container will persist 
     * for this bean.
     */
    public void setCMPFields(Set cmpFields) {
	this.cmpFields = cmpFields;
	persFieldInfo = null;
	fieldInfoInitialized = false;
	super.changed();
    }
    
    /**
     * Has the supplied field object been deemed persistent. 
     */
    public boolean isCMPField(String field) {
	return this.getCMPFields().contains(new FieldDescriptor(field));
    }

	
    /** 
     * Return the Set of fields deemed persistent.
     * The elements of this Set are FieldDescriptor objects.
     * This Set should be modified by calling addCMPField, removeCMPField
     */
    public Set getCMPFields() {
	return this.cmpFields;
    }

    /**
    public void clearPkeyFields() {
        this.pkeyFields.clear();
        setPkeyFields(this.pkeyFields);
    }

    public void addPkeyField(String field) {
        addPkeyField(new FieldDescriptor(field));
    }
    
    public void addPkeyField(FieldDescriptor fieldDesc) {
        this.pkeyFields.add(fieldDesc);
        setPkeyFields(this.pkeyFields);
    }

    public void removePkeyField(String field) {
        removePkeyField(new FieldDescriptor(field));
    }
    
    public void removePkeyField(FieldDescriptor fieldDesc) {
        this.pkeyFields.remove(fieldDesc);
        setPkeyFields(this.pkeyFields);
    }
    **/

    /**
     * Set the FieldDescriptor objects for primary key fields
     * for this bean.
     */
    public void setPkeyFields(Set pkeyFields) {
	this.pkeyFields = pkeyFields;
	fieldInfoInitialized = false;
	persFieldInfo = null;
	pkeyStuffInitialized = false;
	super.changed();
    }
    
    /** 
     * Return the Set of primary key fields.
     * The elements of this Set are FieldDescriptor objects.
     * This Set can be modified by calling addPkeyField, removePkeyField
     */
    public Set getPkeyFields() {
	if ( !pkeyStuffInitialized )
	    initPkeyInfo();
	return pkeyFields;
    }

    /**
     * Is the supplied field object one of the pkey fields.
     */
    public boolean isPkeyField(String field) {
        return isPkeyField(new FieldDescriptor(field));
    }

    public boolean isPkeyField(FieldDescriptor fieldDesc) {
        return this.getPkeyFields().contains(fieldDesc);
    }

    /**
     * Initialize pkeyFields, pkeyIsOneField, primaryKeyClassName
     * Must be called after this PersistenceDescriptor has been attached
     * to the Ejb/JoinDescriptor which has been
     * attached to the EjbBundleDescriptor.
     */
    private void initPkeyInfo()
    {
	try {
	    pkeyIsOneField = false;
            pkeyFieldSpecified = true;
	    primaryKeyClassName = parentDesc.getPrimaryKeyClassName();
    
	    FieldDescriptor fd = parentDesc.getPrimaryKeyFieldDesc();
	    if ( pkeyFields == null || pkeyFields.size() == 0 ) {
	        pkeyFields = new HashSet();

	        if ( fd != null ) // primkey-field was set
		    pkeyFields.add(fd);

	        else if (!primaryKeyClassName.equals("java.lang.Object")) {
		    // get fields of primaryKeyClass
		    primaryKeyClass = getClass(primaryKeyClassName);
		    Field[] fields = primaryKeyClass.getFields();
		    pkeyFieldsAllPrimitive = true;
		    for ( int i=0; i<fields.length; i++ ) {
		        // ignore static or final fields
		        int m = fields[i].getModifiers();
		        if ( Modifier.isStatic(m) || Modifier.isFinal(m) )
			    continue;
		        if ( !fields[i].getType().isPrimitive() )
			    pkeyFieldsAllPrimitive = false;
		        pkeyFields.add(new FieldDescriptor(
						    fields[i].getName()));
		    }
	        }
	        else {
		    // Will use PM-generated primary key
		    primaryKeyClass = getClass(primaryKeyClassName);
                       pkeyIsOneField = true;
                       pkeyFieldSpecified = false;
	        }
	    }
	    if ( fd != null )
	        pkeyIsOneField = true;

	    pkeyStuffInitialized = true;
	}
	catch ( Exception ex ) {
            DOLUtils.getDefaultLogger().log(Level.SEVERE, "enterprise.deployment.backend.invalidDescriptorMappingFailure", 
                new Object[] {ex.toString()});
            throw new EJBException(ex);
	}
    }

    /**
     * @return true if the primary key of this object is one field
     * in its class and the type of the field is not a primitive type.
     * True for EJBs if the primkey-field deployment descriptor element
     * is specified, or if a container-inserted pk field is used.
     */
    public boolean primaryKeyIsOneField() 
    {
	if ( !pkeyStuffInitialized )
	    initPkeyInfo();
	return pkeyIsOneField;
    }


    /**
     * @return false if the primkey-field is not specified and pk class = Object
     */
    public boolean primaryKeyIsSpecified() 
    {
        if( !pkeyStuffInitialized )
            initPkeyInfo();
	return pkeyFieldSpecified;
    }


    /**
     * @return true if the primkey-field is not specified all fields of
     * pkey class are Java primitive types.
     */
    public boolean primaryKeyFieldsAllPrimitive() 
    {
        if( !pkeyStuffInitialized )
            initPkeyInfo();
	return pkeyFieldsAllPrimitive;
    }



    /**
     * Get this bean's primary key class.
     * For EJBs, this is EjbEntityDescriptor.getPrimaryKeyClassName(),
     */
    public Class getPrimaryKeyClass()
    {
	if ( !pkeyStuffInitialized )
	    initPkeyInfo();
	return primaryKeyClass;
    }
   

    // May return null for Join objects when called at/before deployment
    public Class getPersistentClass()
    {
	if ( persistentClass == null ) {
	    persistentClass = getClass(parentDesc.getEjbClassName());
	}
	return persistentClass;
    }

    public Class getStateClass()
    {
	if ( stateClass == null ) {
	    stateClass = getPersistentClass();
	    if ( parentDesc.isEJB20() ) {
		if( !Modifier.isAbstract(stateClass.getModifiers()) ) {
	            throw new EJBException("2.x CMP bean class " 
                        + stateClass.getName() + " must be decleared abstract "
                        + "or cmp-version for the corresponding bean must be set to 1.x.");
                }
	        String stateClassName = parentDesc.getStateImplClassName();
	        stateClass = getClass(stateClassName);
	    }
	}
	return stateClass;
    }



    private Class getClass(String className)
    {            
	try {
	    return getEjbBundleDescriptor().getClassLoader().loadClass(className);
	} catch ( Exception ex ) {
	    //if ( debug ) ex.printStackTrace();
	    throw new EJBException(ex);
	}
    }


    /**
     * Set the array of PersistentFieldInfo objects representing the 
     * foreign key fields of this bean.
     */
    public void setFkeyFields(PersistentFieldInfo[] fkeyFields) {
	this.fkeyFields = fkeyFields;
	fieldInfoInitialized = false;
	persFieldInfo = null;
	super.changed();
    }
    
	
    /** 
     * Return the array of PersistentFieldInfo objects for the 
     * foreign key fields of this bean.
     */
    public PersistentFieldInfo[] getFkeyFields() {
	if ( !fieldInfoInitialized )
	    initializeFieldInfo();
	return this.fkeyFields;
    }


    /** 
     * Return the array of PersistentFieldInfo objects for the 
     * CMP + foreign key fields
     */
    public PersistentFieldInfo[] getPersistentFieldInfo()
    {
	if ( !fieldInfoInitialized )
	    initializeFieldInfo();
	return persFieldInfo;
    }


    /** 
     * Return the PersistentFieldInfo object for the given CMP/fkey field
     */
    public PersistentFieldInfo getPersistentFieldInfoByName(String fieldName)
    {
	if ( !fieldInfoInitialized )
	    initializeFieldInfo();
	for ( int i=0; i<persFieldInfo.length; i++ ) {
	    if ( persFieldInfo[i].name.equals(fieldName) )
		return persFieldInfo[i];
	}
	throw new EJBException("PersistentFieldInfo not found for field "+fieldName);
    }


    /** 
     * Return the array of PersistentFieldInfo objects for the CMP fields
     * which are not primary keys + foreign key fields.
     */
    public PersistentFieldInfo[] getNonPkeyPersFieldInfo()
    {
	if ( !fieldInfoInitialized )
	    initializeFieldInfo();
	return persNoPkeyFieldInfo;
    }


    /** 
     * Return the array of PersistentFieldInfo objects for the pkey fields.
     */
    public PersistentFieldInfo[] getPkeyFieldInfo()
    {
	if ( !fieldInfoInitialized )
	    initializeFieldInfo();
	return pkeyFieldInfo;
    }

    /** 
     * Return PersistentFieldInfo object for the given pkey field.
     */
    public PersistentFieldInfo getPkeyFieldInfoByName(String fieldName)
    {
	if ( !fieldInfoInitialized )
	    initializeFieldInfo();
	for ( int i=0; i<pkeyFieldInfo.length; i++ ) {
	    if ( pkeyFieldInfo[i].name.equals(fieldName) )
		return pkeyFieldInfo[i];
	}
	throw new EJBException("PersistentFieldInfo not found for pkey field "+fieldName);
    }



    /**
     * @return an array of all Field objects in the primary key class.
     * Returns null if primaryKeyIsOneField() == true.
     */
    public Field[] getPkeyClassFields()
    {
	if ( !fieldInfoInitialized )
	    initializeFieldInfo();
	return pkeyClassPkeyFields;
    }


    // Initialize persFieldInfo, pkeyFieldInfo, persNoPkeyFieldInfo, 
    // pkeyClassPkeyFields
    private void initializeFieldInfo()
    {
	if ( !pkeyStuffInitialized )
	    initPkeyInfo();

	int cmpFieldCount = cmpFields.size();
        if (cmpFieldCount==0) {
            throw new EJBException("No cmp field defined for CMP EJB " + parentDesc.getName());
        }

	int fkeyCount = 0;
	if ( fkeyFields != null )
	    fkeyCount = fkeyFields.length;

	persFieldInfo = new PersistentFieldInfo[cmpFieldCount + fkeyCount];
	// Add CMP fields
	int fcount = 0;
	Iterator itr = cmpFields.iterator();
	while ( itr.hasNext() ) {
	    persFieldInfo[fcount] = new PersistentFieldInfo();
	    persFieldInfo[fcount].name =((FieldDescriptor)itr.next()).getName();
	    fcount++;
	}
	// Add foreign key fields
	if ( fkeyFields != null ) {
	    for ( int i=0; i<fkeyFields.length; i++ ) {
		persFieldInfo[fcount] = fkeyFields[i];
		fcount++;
	    }
	}

	// sort persFieldInfo in alphabetical order
        for ( int i=persFieldInfo.length-1; i>0; i-- ) {
            for ( int j=0; j<i; j++ ) {
                if ( persFieldInfo[j].name.compareTo(persFieldInfo[j+1].name) 
									> 0 ) {
                    PersistentFieldInfo tmp = persFieldInfo[j];
                    persFieldInfo[j] = persFieldInfo[j+1];
                    persFieldInfo[j+1] = tmp;
                }
            }
        }

        // Initialize pkeyFieldInfo[] and persNoPkeyFieldInfo[] 
	// They contain the same PersistentFieldInfo objects as persFieldInfo.
        pkeyFieldInfo = new PersistentFieldInfo[pkeyFields.size()];
	if ( pkeyFieldSpecified ) {

            // check if PK class has public non-persistent fields
            StringBuffer nonPersFieldsInPK = new StringBuffer();
            for ( Iterator it=pkeyFields.iterator(); it.hasNext(); ) {
                FieldDescriptor fd = (FieldDescriptor)it.next();
                boolean isPersistent = false;
                for ( int i=0; i<persFieldInfo.length; i++ ) {
                    if ( fd.getName().equals(persFieldInfo[i].name) ) {
                        isPersistent = true;
                        break;
                    }
                }
                if ( !isPersistent ) {
                    // if not the first non-persistent field
                    if ( nonPersFieldsInPK.length() != 0 ) {
                        nonPersFieldsInPK.append(", ");
                    }
                    nonPersFieldsInPK.append(fd.getName());
                }
            }
            if ( nonPersFieldsInPK.length() != 0 ) {
                throw new EJBException(localStrings.getLocalString(
                "enterprise.deployment.pkhasnopersistentfields",
                "CMP bean [{0}], primary key class [{1}] has " + 
                "public non-persistent field(s) [{2}].",
                new Object[] {getParentDescriptor().getName(),
                getPrimaryKeyClass().getName(),
                nonPersFieldsInPK.toString()}));
            }

	    persNoPkeyFieldInfo = new PersistentFieldInfo[persFieldInfo.length -
							  pkeyFieldInfo.length];
	    int pkeyCount = 0;
	    int noPkeyCount = 0;
	    for ( int i=0; i<persFieldInfo.length; i++ ) {
		boolean isPkey = false;
		for ( Iterator it=pkeyFields.iterator(); it.hasNext(); ) {
		    FieldDescriptor fd = (FieldDescriptor)it.next();
		    if ( fd.getName().equals(persFieldInfo[i].name) ) {
			isPkey = true;
			break;
		    }
		}
		if ( isPkey ) 
		    pkeyFieldInfo[pkeyCount++] = persFieldInfo[i];
		else
		    persNoPkeyFieldInfo[noPkeyCount++] = persFieldInfo[i];
	    }
	}

	if ( pkeyIsOneField  && pkeyFieldSpecified) {
	    // Initialize pkey field type. This is needed for
	    // beans with no pkey field (i.e. PM-generated pkey)
	    // because they wont have get/set for the pkey in the bean class
	    // so the getCMPFieldType() below will bomb if type is not set.
	    // Note: pkeyFieldInfo and persFieldInfo arrays share the same
	    // PersistentFieldInfo objects.
	    pkeyFieldInfo[0].type = getPrimaryKeyClass();
	}

	// Initialize Java types in persFieldInfo
	for ( int i=0; i<persFieldInfo.length; i++ ) {
	    // fill type for CMP fields if not there 
	    // (fkey types will already be filled)
	    if ( persFieldInfo[i].type == null )
	        persFieldInfo[i].type = getCMPFieldType(persFieldInfo[i].name);
        }

        // When called from Deploytool, the bean class is abstract,
        // and doesnt have the CMP fields: they will be in the generated 
        // code after deployment. So all the java.lang.reflect.Field 
	// values in PersistentFieldInfo can only be initialized at runtime.
	try {
	    if ( persistentClass != null 
		 && !Modifier.isAbstract(persistentClass.getModifiers()) ) {
		for ( int i=0; i<persFieldInfo.length; i++ ) {
		    persFieldInfo[i].field = getField(getStateClass(), 
						      persFieldInfo[i].name);
		}
	    }

	    // Initialize pkeyClassPkeyFields
	    if ( !pkeyIsOneField && primaryKeyClass != null 
                 && !Modifier.isAbstract(primaryKeyClass.getModifiers()) ) {
		pkeyClassPkeyFields = new Field[pkeyFieldInfo.length];
		for ( int i=0; i<pkeyFieldInfo.length; i++ ) {
		    pkeyClassPkeyFields[i] = primaryKeyClass.getField(
							pkeyFieldInfo[i].name); 
		}
	    } 
	} catch ( NoSuchFieldException ex ) {
            if(DOLUtils.getDefaultLogger().isLoggable(Level.FINE)) {            
                DOLUtils.getDefaultLogger().log(Level.FINE, ex.toString(), ex);
            }
	    throw new EJBException(ex);
	}

        fieldInfoInitialized = true;
    }

    private Field getField(final Class c, final String name)
        throws NoSuchFieldException
    {
        // This privileged block is needed because this code can be
        // called when application code is on the stack, which is not
        // allowed to reflect on private members of classes.
        Field field = (Field)java.security.AccessController.doPrivileged(
            new java.security.PrivilegedAction() {
                public Object run()  {
                    try {
                        // this is needed for EJB2.0 CMP beans whose 
                        // generated fields are private.
                        return c.getDeclaredField(name);
                    }
                    catch ( NoSuchFieldException ex ) {
                        return null;
                    }
                }
            }
        );

        if ( field == null )
            field = c.getField(name);
        return field;
    }

    /**
     * @return the Class object corresponding to the type of the given
     *         CMP field.
     */
    public Class getTypeFor(String field)
    {
	return getCMPFieldType(field);
    }

    private Class getCMPFieldType(String field)
    {
	Class pclass = getPersistentClass();
	if ( Modifier.isAbstract(pclass.getModifiers()) ) {
	    // An EJB2.0 CMP bean : field is a virtual field 
	    String javaBeanName = capitalize(field);
	    String getter = "get"+javaBeanName;
	    try {
		Method method = pclass.getMethod(getter, (Class[]) null);
		return method.getReturnType();
	    } catch ( Exception ex ) {
		throw new RuntimeException("Cannot find accessor " + getter + " for CMP field "+field);
	    }
	}
	else {
	    // field is a Java field in state class
	    try {
		Field f = getField(getStateClass(), field);
		return f.getType();
	    } catch ( NoSuchFieldException ex ) {
		throw new RuntimeException("Cant find CMP field "+field+" in class "+getStateClass().getName());
	    }
	}
    }


    // called from CMPClassGenerator too
    public static String capitalize(String name)
    {
	// EJB2.0 proposed final draft says that CMP/CMR field names
	// must begin with a lower case letter.
	if ( Character.isUpperCase(name.charAt(0)) ) {
	    throw new javax.ejb.EJBException("CMP/CMR field "+name+" must start with a lower case character.");
	}
	else {
	    char chars[] = name.toCharArray();
	    chars[0] = Character.toUpperCase(chars[0]);
	    return new String(chars);
	}
    }

    public void setQueryFor(MethodDescriptor method, QueryDescriptor query)
    {
		queries.put(method, query);
    }


    public QueryDescriptor getQueryFor(MethodDescriptor method)
    {
	return (QueryDescriptor)queries.get(method);
    }

    public void  removeQueryFor(MethodDescriptor method)
    {           
	queries.remove(method);
    }       

    public void setQueryFor(Method method, QueryDescriptor query)
    {
        // Use our own method equality check. This prevents problems
        // when a different classloader was used to load the input
        // method.  Also note that two methods with the same name and
        // signature on *different* interfaces are considered EQUAL.
        // This matches the spec requirement that the same finder
        // method defined on the LocalHome and RemoteHome has only
        // ONE query-method declaration in the deployment descriptor.
        // 
        MethodDescriptor md = new MethodDescriptor(method,"");
        setQueryFor(md, query);
    }

    public QueryDescriptor getQueryFor(Method method)
    {
        // Use our own method equality check. See setQueryFor comment
        // for more details.
        MethodDescriptor md = new MethodDescriptor(method,"");
        return (QueryDescriptor) queries.get(md);
    }
    
    /**
     * Get all methods for which setQueryFor was done 
     * @return a Set of Methods
     */
    public Set getQueriedMethods()
    {
	return queries.keySet();
    }

    /**
     * Get all HomeIntf.find* and EJBClass.ejbSelect* methods
     * for which an EJB-QL query could possibly be set.
     * @return a Set of Methods
     */
    public Set getAllPossibleQueriedMethods()
    {
	if ( allQueriedMethods == null ) 
	    initializeAllQueriedMethods();

	return allQueriedMethods;
    }

    private void initializeAllQueriedMethods() 
    {
	allQueriedMethods = new HashSet();	    

	// add all ejbSelect* methods in persistentClass
	Method[] beanMethods = getPersistentClass().getMethods();
	for ( int i=0; i<beanMethods.length; i++ ) {
	    if ( beanMethods[i].getName().startsWith("ejbSelect") ) {
		allQueriedMethods.add(new MethodDescriptor(beanMethods[i], MethodDescriptor.EJB_BEAN));
	    }
	}

        // add all finders in Home/LocalHome intf, findByPrimaryKey too
        if ( parentDesc.isRemoteInterfacesSupported() ) {
	    Class homeIntf = getClass(parentDesc.getHomeClassName());
    
	    Method[] homeMethods = homeIntf.getMethods();
	    for ( int i=0; i<homeMethods.length; i++ ) {
	        String name = homeMethods[i].getName();
	        if ( name.startsWith("find") 
		        && !name.equals("findByPrimaryKey") ) {
		    allQueriedMethods.add(new MethodDescriptor(homeMethods[i], MethodDescriptor.EJB_HOME));
	        }
	    }
        }
        if ( parentDesc.isLocalInterfacesSupported() ) {
	    Class homeIntf = getClass(parentDesc.getLocalHomeClassName());
    
	    Method[] homeMethods = homeIntf.getMethods();
	    for ( int i=0; i<homeMethods.length; i++ ) {
	        String name = homeMethods[i].getName();
	        if ( name.startsWith("find") 
		        && !name.equals("findByPrimaryKey") ) {
		    allQueriedMethods.add(new MethodDescriptor(homeMethods[i], MethodDescriptor.EJB_LOCALHOME));
	        }
	    }
        }
    }

    /**
    * Return my formatted string representation.
    */
    public void print(StringBuffer toStringBuffer) {
	super.print(toStringBuffer);
	toStringBuffer.append("\n Entity descriptor");
	toStringBuffer.append("\n cmpFields ").append(cmpFields);
    }
}
