/**
 * Copyright 2005-2018 The Kuali Foundation
 *
 * Licensed under the Educational Community License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.opensource.org/licenses/ecl2.php
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.kuali.rice.krad.service.impl;

import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.ojb.broker.metadata.ClassDescriptor;
import org.apache.ojb.broker.metadata.CollectionDescriptor;
import org.apache.ojb.broker.metadata.FieldDescriptor;
import org.apache.ojb.broker.metadata.ObjectReferenceDescriptor;
import org.apache.ojb.broker.metadata.SuperReferenceDescriptor;
import org.kuali.rice.krad.bo.DataObjectRelationship;
import org.kuali.rice.krad.bo.PersistableBusinessObject;
import org.kuali.rice.krad.bo.PersistableBusinessObjectBaseAdapter;
import org.kuali.rice.krad.exception.ClassNotPersistableException;
import org.kuali.rice.krad.exception.IntrospectionException;
import org.kuali.rice.krad.exception.ObjectNotABusinessObjectRuntimeException;
import org.kuali.rice.krad.exception.ReferenceAttributeDoesntExistException;
import org.kuali.rice.krad.exception.ReferenceAttributeNotAnOjbReferenceException;
import org.kuali.rice.krad.service.PersistenceStructureService;
import org.kuali.rice.krad.util.ForeignKeyFieldsPopulationState;
import org.kuali.rice.krad.util.LegacyDataFramework;
@Deprecated
@LegacyDataFramework
public class PersistenceStructureServiceOjbImpl extends PersistenceServiceImplBase implements PersistenceStructureService {

	/**
	 *
	 * special case when the attributeClass passed in doesnt match the class of
	 * the reference-descriptor as defined in ojb-repository. Currently the only
	 * case of this happening is ObjectCode vs. ObjectCodeCurrent.
	 *
	 * NOTE: This method makes no real sense and is a product of a hack
	 * introduced by KFS for an unknown reason. If you find yourself using this
	 * map stop and go do something else.
	 *
	 * @param from
	 *            the class in the code
	 * @param to
	 *            the class in the repository
	 */
	public static Map<Class, Class> referenceConversionMap = new HashMap<Class, Class>();


	private PersistenceStructureService persistenceStructureServiceJpa;

	public PersistenceStructureService getPersistenceStructureServiceJpa() {
		return this.persistenceStructureServiceJpa;
	}

	public void setPersistenceStructureServiceJpa(PersistenceStructureService persistenceStructureServiceJpa) {
		this.persistenceStructureServiceJpa = persistenceStructureServiceJpa;
	}

	/**
	 * @see org.kuali.rice.krad.service.PersistenceService#isPersistable(java.lang.Class)
	 */

	@Override
	public boolean isPersistable(Class clazz) {
		boolean isPersistable = false;
		try {
			if (getClassDescriptor(clazz) != null) {
				isPersistable = true;
			}
		} catch (ClassNotPersistableException e) {
			isPersistable = false;
		}
		return isPersistable;
	}

	/**
	 * @see org.kuali.rice.krad.service.PersistenceService#getPrimaryKeys(java.lang.Class)
	 */

	@Override
	public List getPrimaryKeys(Class clazz) {
		List pkList = new ArrayList();
		ClassDescriptor classDescriptor = getClassDescriptor(clazz);
		FieldDescriptor keyDescriptors[] = classDescriptor.getPkFields();
		for (int i = 0; i < keyDescriptors.length; ++i) {
			FieldDescriptor keyDescriptor = keyDescriptors[i];
			pkList.add(keyDescriptor.getAttributeName());
		}
		return pkList;
	}

	/**
	 * @see org.kuali.rice.krad.service.PersistenceMetadataExplorerService#listFieldNames(java.lang.Class)
	 */

	@Override
	public List listFieldNames(Class clazz) {
		List fieldNames = new ArrayList();
		ClassDescriptor classDescriptor = getClassDescriptor(clazz);
		FieldDescriptor fieldDescriptors[] = classDescriptor.getFieldDescriptions();
		for (int i = 0; i < fieldDescriptors.length; ++i) {
			FieldDescriptor fieldDescriptor = fieldDescriptors[i];
			fieldNames.add(fieldDescriptor.getAttributeName());
		}
		return fieldNames;
	}

	/**
	 * @see org.kuali.rice.krad.service.PersistenceMetadataService#clearPrimaryKeyFields(java.lang.Object)
	 */
	// Unit tests only
	@Override
	public Object clearPrimaryKeyFields(Object persistableObject) {
		if (persistableObject == null) {
			throw new IllegalArgumentException("invalid (null) persistableObject");
		}

		String className = null;
		String fieldName = null;
		try {
			className = persistableObject.getClass().getName();
			List fields = listPrimaryKeyFieldNames(persistableObject.getClass());
			for (Iterator i = fields.iterator(); i.hasNext();) {
				fieldName = (String) i.next();

				PropertyUtils.setProperty(persistableObject, fieldName, null);
			}

			if (persistableObject instanceof PersistableBusinessObject) {
				((PersistableBusinessObject) persistableObject).setObjectId(null);
			}
		} catch (NoSuchMethodException e) {
			throw new IntrospectionException("no setter for property '" + className + "." + fieldName + "'", e);
		} catch (IllegalAccessException e) {
			throw new IntrospectionException("problem accessing property '" + className + "." + fieldName + "'", e);
		} catch (InvocationTargetException e) {
			throw new IntrospectionException("problem invoking getter for property '" + className + "." + fieldName + "'", e);
		}

		return persistableObject;
	}

	/**
	 * @see org.kuali.rice.krad.service.PersistenceMetadataExplorerService#listPersistableSubclasses(java.lang.Class)
	 */

	// Unit tests only
	@Override
	public List listPersistableSubclasses(Class superclazz) {
		if (superclazz == null) {
			throw new IllegalArgumentException("invalid (null) uberclass");
		}

		Map allDescriptors = getDescriptorRepository().getDescriptorTable();
		List persistableSubclasses = new ArrayList();
		for (Iterator i = allDescriptors.entrySet().iterator(); i.hasNext();) {
			Map.Entry e = (Map.Entry) i.next();

			Class persistableClass = ((ClassDescriptor) e.getValue()).getClassOfObject();
			if (!superclazz.equals(persistableClass) && superclazz.isAssignableFrom(persistableClass)) {
				persistableSubclasses.add(persistableClass);
			}
		}
		return persistableSubclasses;
	}

	/**
	 * @see org.kuali.rice.krad.service.PersistenceService#getRelationshipMetadata(java.lang.Class,
	 *      java.lang.String)
	 */

	@Override
	public Map<String, DataObjectRelationship> getRelationshipMetadata(Class persistableClass, String attributeName, String attributePrefix) {
		if (persistableClass == null) {
			throw new IllegalArgumentException("invalid (null) persistableClass");
		}
		if (StringUtils.isBlank(attributeName)) {
			throw new IllegalArgumentException("invalid (blank) attributeName");
		}

		Map<String, DataObjectRelationship> relationships = new HashMap<String, DataObjectRelationship>();
		ClassDescriptor classDescriptor = getClassDescriptor(persistableClass);
		Vector<ObjectReferenceDescriptor> references = classDescriptor.getObjectReferenceDescriptors();
		for (ObjectReferenceDescriptor objRef : references) {
			Vector fks = objRef.getForeignKeyFields();
			if (fks.contains(attributeName) || objRef.getAttributeName().equals(attributeName)) {
				Map<String, String> fkToPkRefs = getForeignKeysForReference(persistableClass, objRef.getAttributeName());
				DataObjectRelationship rel = new DataObjectRelationship(persistableClass, objRef.getAttributeName(), objRef.getItemClass());
				for (Map.Entry<String, String> ref : fkToPkRefs.entrySet()) {
					if (StringUtils.isBlank(attributePrefix)) {
						rel.getParentToChildReferences().put(ref.getKey(), ref.getValue());
					} else {
						rel.getParentToChildReferences().put(attributePrefix + "." + ref.getKey(), ref.getValue());
					}
				}
				relationships.put(objRef.getAttributeName(), rel);
			}
		}
		return relationships;
	}


	// Unit tests only
	@Override
	public Map<String, DataObjectRelationship> getRelationshipMetadata(Class persistableClass, String attributeName) {
		return getRelationshipMetadata(persistableClass, attributeName, null);
	}

	/**
	 * @see org.kuali.rice.krad.service.PersistenceService#getForeignKeyFieldName(java.lang.Object,
	 *      java.lang.String, java.lang.String)
	 */

	@Override
	public String getForeignKeyFieldName(Class persistableObjectClass, String attributeName, String pkName) {
		String fkName = "";
		ClassDescriptor classDescriptor = getClassDescriptor(persistableObjectClass);
		ObjectReferenceDescriptor objectReferenceDescriptor = classDescriptor.getObjectReferenceDescriptorByName(attributeName);
		if (objectReferenceDescriptor == null) {
			throw new RuntimeException("Attribute name " + attributeName + " is not a valid reference to class " + persistableObjectClass.getName());
		}
		ClassDescriptor referenceDescriptor = this.getClassDescriptor(objectReferenceDescriptor.getItemClass());

		FieldDescriptor[] fkFields = objectReferenceDescriptor.getForeignKeyFieldDescriptors(classDescriptor);
		FieldDescriptor[] pkFields = referenceDescriptor.getPkFields();
		for (int i = 0; i < pkFields.length; i++) {
			FieldDescriptor pkField = pkFields[i];
			if (pkField.getAttributeName().equals(pkName)) {
				fkName = fkFields[i].getAttributeName();
			}
		}
		return fkName;
	}

	/**
	 * @see org.kuali.rice.krad.service.PersistenceService#getReferencesForForeignKey(java.lang.Class,
	 *      java.lang.String)
	 */

	@Override
	public Map getReferencesForForeignKey(Class persistableObjectClass, String attributeName) {
		Map referenceClasses = new HashMap();
		if (PersistableBusinessObject.class.isAssignableFrom(persistableObjectClass)) {
			ClassDescriptor classDescriptor = getClassDescriptor(persistableObjectClass);
			Vector objectReferences = classDescriptor.getObjectReferenceDescriptors();
			for (Iterator iter = objectReferences.iterator(); iter.hasNext();) {
				ObjectReferenceDescriptor referenceDescriptor = (ObjectReferenceDescriptor) iter.next();

				/*
				 * iterate through the fk keys for the reference object and if
				 * matches the attributeName add the class as a reference
				 */
				FieldDescriptor[] refFkNames = referenceDescriptor.getForeignKeyFieldDescriptors(classDescriptor);
				for (int i = 0; i < refFkNames.length; i++) {
					FieldDescriptor fkField = refFkNames[i];
					if (fkField.getAttributeName().equals(attributeName)) {
						referenceClasses.put(referenceDescriptor.getAttributeName(), referenceDescriptor.getItemClass());
					}
				}
			}
		}
		return referenceClasses;
	}

	/**
	 * @see org.kuali.rice.krad.service.PersistenceService#getForeignKeysForReference(java.lang.Class,
	 *      java.lang.String) The Map structure is: Key(String fkFieldName) =>
	 *      Value(String pkFieldName) NOTE that this implementation depends on
	 *      the ordering of foreign-key elements in the ojb-repository matching
	 *      the ordering of primary-key declarations of the class on the other
	 *      side of the relationship. This is done because: 1. The current
	 *      version of OJB requires you to declare all of these things in the
	 *      correct (and matching) order in the ojb-repository file for it to
	 *      work at all. 2. There is no other way to match a given foreign-key
	 *      reference to its corresponding primary-key on the opposing side of
	 *      the relationship. Yes, this is a crummy way to do it, but OJB doesnt
	 *      provide explicit matches of foreign-keys to primary keys, and always
	 *      assumes that foreign-keys map to primary keys on the other object,
	 *      and never to a set of candidate keys, or any other column.
	 */

	@Override
	public Map getForeignKeysForReference(Class clazz, String attributeName) {
		// yelp if nulls were passed in
		if (clazz == null) {
			throw new IllegalArgumentException("The Class passed in for the clazz argument was null.");
		}
		if (attributeName == null) {
			throw new IllegalArgumentException("The String passed in for the attributeName argument was null.");
		}

		// get the class of the attribute name
		Class attributeClass = getBusinessObjectAttributeClass(clazz, attributeName);
		if (attributeClass == null) {
			throw new ReferenceAttributeDoesntExistException("Requested attribute: '" + attributeName + "' does not exist " + "on class: '" + clazz.getName() + "'.");
		}

        // make sure the class of the attribute descends from BusinessObject,
        // otherwise throw an exception
        if ((!PersistableBusinessObject.class.isAssignableFrom(attributeClass)) &&
            (!PersistableBusinessObjectBaseAdapter.class.isAssignableFrom(attributeClass))) {
                throw new ObjectNotABusinessObjectRuntimeException("Attribute requested (" + attributeName +
                ") is of class: " + "'" + attributeClass.getName() + "' and is not a " +
                "descendent of BusinessObject.  Only descendents of BusinessObject " + "can be used.");
        }

		Map fkMap = new HashMap();

		// make sure the attribute designated is listed as a
		// reference-descriptor on the clazz specified, otherwise
		// throw an exception (OJB);
		ClassDescriptor classDescriptor = getClassDescriptor(clazz);
		ObjectReferenceDescriptor referenceDescriptor = classDescriptor.getObjectReferenceDescriptorByName(attributeName);
		if (referenceDescriptor == null) {
			throw new ReferenceAttributeNotAnOjbReferenceException("Attribute requested (" + attributeName + ") is not listed " + "in OJB as a reference-descriptor for class: '" + clazz.getName() + "'");
		}

		// special case when the attributeClass passed in doesnt match the
		// class of the reference-descriptor as defined in ojb-repository.
		// Currently
		// the only case of this happening is ObjectCode vs.
		// ObjectCodeCurrent.
		if (!attributeClass.equals(referenceDescriptor.getItemClass())) {

			if (referenceConversionMap.containsKey(attributeClass)) {
				attributeClass = referenceConversionMap.get(attributeClass);
			} else {
				throw new RuntimeException("The Class of the Java member [" + attributeClass.getName() + "] '" + attributeName + "' does not match the class of the " + "reference-descriptor [" + referenceDescriptor.getItemClass().getName() + "]. " + "This is an unhandled special case for which special code needs to be written " + "in this class.");
			}
		}

		// get the list of the foreign-keys for this reference-descriptor
		// (OJB)
		Vector fkFields = referenceDescriptor.getForeignKeyFields();
		Iterator fkIterator = fkFields.iterator();

		// get the list of the corresponding pk fields on the other side of
		// the relationship
		List pkFields = getPrimaryKeys(attributeClass);
		Iterator pkIterator = pkFields.iterator();

		// make sure the size of the pkIterator is the same as the
		// size of the fkIterator, otherwise this whole thing is borked
		if (pkFields.size() != fkFields.size()) {
			throw new RuntimeException("KualiPersistenceStructureService Error: The number of " + "foreign keys doesnt match the number of primary keys.  This may be a " + "result of misconfigured OJB-repository files.");
		}

		// walk through the list of the foreign keys, get their types
		while (fkIterator.hasNext()) {
			// if there is a next FK but not a next PK, then we've got a big
			// problem,
			// and cannot continue
			if (!pkIterator.hasNext()) {
				throw new RuntimeException("The number of foriegn keys dont match the number of primary " + "keys for the reference '" + attributeName + "', on BO of type '" + clazz.getName() + "'.  " + "This should never happen under normal circumstances, as it means that the OJB repository " + "files are misconfigured.");
			}

			// get the field name of the fk & pk field
			String fkFieldName = (String) fkIterator.next();
			String pkFieldName = (String) pkIterator.next();

			// add the fieldName and fieldType to the map
			fkMap.put(fkFieldName, pkFieldName);
		}

		return fkMap;
	}


	@Override
	public Map<String, String> getInverseForeignKeysForCollection(Class boClass, String collectionName) {
		// yelp if nulls were passed in
		if (boClass == null) {
			throw new IllegalArgumentException("The Class passed in for the boClass argument was null.");
		}
		if (collectionName == null) {
			throw new IllegalArgumentException("The String passed in for the attributeName argument was null.");
		}

		PropertyDescriptor propertyDescriptor = null;

		// make an instance of the class passed
		Object classInstance;
		try {
			classInstance = boClass.newInstance();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}

		// make sure the attribute exists at all, throw exception if not
		try {
			propertyDescriptor = PropertyUtils.getPropertyDescriptor(classInstance, collectionName);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		if (propertyDescriptor == null) {
			throw new ReferenceAttributeDoesntExistException("Requested attribute: '" + collectionName + "' does not exist " + "on class: '" + boClass.getName() + "'. GFK");
		}

		// get the class of the attribute name
		Class attributeClass = propertyDescriptor.getPropertyType();

		// make sure the class of the attribute descends from BusinessObject,
		// otherwise throw an exception
		if (!Collection.class.isAssignableFrom(attributeClass)) {
			throw new ObjectNotABusinessObjectRuntimeException("Attribute requested (" + collectionName + ") is of class: " + "'" + attributeClass.getName() + "' and is not a " + "descendent of Collection");
		}

		// make sure the collection designated is listed as a
		// collection-descriptor
		// on the boClass specified, otherwise throw an exception
		ClassDescriptor classDescriptor = getClassDescriptor(boClass);
		CollectionDescriptor collectionDescriptor = classDescriptor.getCollectionDescriptorByName(collectionName);

		// in collections, the number of keys is equal to the number of keys in
		// the parent class (the class with the collection).
		// Each of the primary keys on the parent object will be mapped to a
		// field in the element object.

		List parentForeignKeys = getPrimaryKeys(boClass);
		Vector childPrimaryKeysLegacy = collectionDescriptor.getForeignKeyFields();

		if (parentForeignKeys.size() != childPrimaryKeysLegacy.size()) {
			throw new RuntimeException("The number of keys in the class descriptor and the inverse foreign key mapping for the collection descriptors do not match.");
		}

		Map<String, String> fkToPkMap = new HashMap<String, String>();

		Iterator pFKIter = parentForeignKeys.iterator();
		Iterator cPKIterator = childPrimaryKeysLegacy.iterator();

		while (pFKIter.hasNext()) {
			String parentForeignKey = (String) pFKIter.next();
			String childPrimaryKey = (String) cPKIterator.next();

			fkToPkMap.put(parentForeignKey, childPrimaryKey);
		}

		return fkToPkMap;
	}

	/**
	 * @see org.kuali.rice.krad.service.PersistenceService#getNestedForeignKeyMap(java.lang.Class)
	 */

	@Override
	public Map getNestedForeignKeyMap(Class persistableObjectClass) {
		Map fkMap = new HashMap();
		ClassDescriptor classDescriptor = getClassDescriptor(persistableObjectClass);
		Vector objectReferences = classDescriptor.getObjectReferenceDescriptors();
		for (Iterator iter = objectReferences.iterator(); iter.hasNext();) {
			ObjectReferenceDescriptor objectReferenceDescriptor = (ObjectReferenceDescriptor) iter.next();
			ClassDescriptor referenceDescriptor = this.getClassDescriptor(objectReferenceDescriptor.getItemClass());

			FieldDescriptor[] fkFields = objectReferenceDescriptor.getForeignKeyFieldDescriptors(classDescriptor);
			FieldDescriptor[] pkFields = referenceDescriptor.getPkFields();
			for (int i = 0; i < pkFields.length; i++) {
				FieldDescriptor pkField = pkFields[i];
				fkMap.put(objectReferenceDescriptor.getAttributeName() + "." + pkField.getAttributeName(), fkFields[i].getAttributeName());
			}
		}

		return fkMap;
	}

	/**
	 * @see org.kuali.rice.krad.service.PersistenceMetadataService#hasPrimaryKeyFieldValues(java.lang.Object)
	 */
	@Override
	public boolean hasPrimaryKeyFieldValues(Object persistableObject) {
		Map keyFields = getPrimaryKeyFieldValues(persistableObject);

		boolean emptyField = false;
		for (Iterator i = keyFields.entrySet().iterator(); !emptyField && i.hasNext();) {
			Map.Entry e = (Map.Entry) i.next();

			Object fieldValue = e.getValue();
			if (fieldValue == null) {
				emptyField = true;
			} else if (fieldValue instanceof String) {
				if (StringUtils.isEmpty((String) fieldValue)) {
					emptyField = true;
				} else {
					emptyField = false;
				}
			}
		}

		return !emptyField;
	}

	/**
	 * @see org.kuali.rice.krad.service.PersistenceService#getForeignKeyFieldsPopulationState(org.kuali.rice.krad.bo.BusinessObject,
	 *      java.lang.String)
	 */
	@Override
	public ForeignKeyFieldsPopulationState getForeignKeyFieldsPopulationState(PersistableBusinessObject bo, String referenceName) {
		boolean allFieldsPopulated = true;
		boolean anyFieldsPopulated = false;
		List<String> unpopulatedFields = new ArrayList<String>();

		// yelp if nulls were passed in
		if (bo == null) {
			throw new IllegalArgumentException("The Class passed in for the BusinessObject argument was null.");
		}
		if (StringUtils.isBlank(referenceName)) {
			throw new IllegalArgumentException("The String passed in for the referenceName argument was null or empty.");
		}

		PropertyDescriptor propertyDescriptor = null;

		// make sure the attribute exists at all, throw exception if not
		try {
			propertyDescriptor = PropertyUtils.getPropertyDescriptor(bo, referenceName);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		if (propertyDescriptor == null) {
			throw new ReferenceAttributeDoesntExistException("Requested attribute: '" + referenceName + "' does not exist " + "on class: '" + bo.getClass().getName() + "'.");
		}

		// get the class of the attribute name
		Class referenceClass = propertyDescriptor.getPropertyType();

		// make sure the class of the attribute descends from BusinessObject,
		// otherwise throw an exception
		if (!PersistableBusinessObject.class.isAssignableFrom(referenceClass)) {
			throw new ObjectNotABusinessObjectRuntimeException("Attribute requested (" + referenceName + ") is of class: " + "'" + referenceClass.getName() + "' and is not a " + "descendent of BusinessObject.  Only descendents of BusinessObject " + "can be used.");
		}

		// make sure the attribute designated is listed as a
		// reference-descriptor
		// on the clazz specified, otherwise throw an exception (OJB);

		ClassDescriptor classDescriptor = getClassDescriptor(bo.getClass());

		// This block is a combination of legacy and jpa
		ObjectReferenceDescriptor referenceDescriptor = classDescriptor.getObjectReferenceDescriptorByName(referenceName);
		if (referenceDescriptor == null) {
			throw new ReferenceAttributeNotAnOjbReferenceException("Attribute requested (" + referenceName + ") is not listed " + "in OJB as a reference-descriptor for class: '" + bo.getClass().getName() + "'");
		}

		// get the list of the foreign-keys for this reference-descriptor
		Vector fkFieldsLegacy = referenceDescriptor.getForeignKeyFields();
		Iterator fkIteratorLegacy = fkFieldsLegacy.iterator();

		// walk through the list of the foreign keys, get their types
		while (fkIteratorLegacy.hasNext()) {

			// get the field name of the fk & pk field
			String fkFieldName = (String) fkIteratorLegacy.next();

			// get the value for the fk field
			Object fkFieldValue = null;
			try {
				fkFieldValue = PropertyUtils.getSimpleProperty(bo, fkFieldName);
			}

			// abort if the value is not retrievable
			catch (Exception e) {
				throw new RuntimeException(e);
			}

			// test the value
			if (fkFieldValue == null) {
				allFieldsPopulated = false;
				unpopulatedFields.add(fkFieldName);
			} else if (fkFieldValue instanceof String) {
				if (StringUtils.isBlank((String) fkFieldValue)) {
					allFieldsPopulated = false;
					unpopulatedFields.add(fkFieldName);
				} else {
					anyFieldsPopulated = true;
				}
			} else {
				anyFieldsPopulated = true;
			}
		}

		// sanity check. if the flag for all fields populated is set, then
		// there should be nothing in the unpopulatedFields list
		if (allFieldsPopulated) {
			if (!unpopulatedFields.isEmpty()) {
				throw new RuntimeException("The flag is set that indicates all fields are populated, but there " + "are fields present in the unpopulatedFields list.  This should never happen, and indicates " + "that the logic in this method is broken.");
			}
		}

		return new ForeignKeyFieldsPopulationState(allFieldsPopulated, anyFieldsPopulated, unpopulatedFields);
	}

	/**
	 * @see org.kuali.rice.krad.service.PersistenceStructureService#listReferenceObjectFieldNames(java.lang.Class)
	 */

	@Override
	public Map<String, Class> listReferenceObjectFields(Class boClass) {
		// validate parameter
		if (boClass == null) {
			throw new IllegalArgumentException("Class specified in the parameter was null.");
		}

		Map<String, Class> references = new HashMap<String, Class>();
		ClassDescriptor classDescriptor = getClassDescriptor(boClass);
		Collection<ObjectReferenceDescriptor> referenceDescriptors = classDescriptor.getObjectReferenceDescriptors(true);

		for (ObjectReferenceDescriptor referenceDescriptor : referenceDescriptors) {
			/*
             * Below check is performed for OJB specific inheritance implementation. For more information see the OJB
             * documentation: http://db.apache.org/ojb/docu/guides/advanced-technique.html#table-per-subclass
             */
            String superReferenceDescriptor = referenceDescriptor.getAttributeName();
            if (!SuperReferenceDescriptor.SUPER_FIELD_INTERNAL_NAME.equals(superReferenceDescriptor)) {
                references.put(superReferenceDescriptor, referenceDescriptor.getItemClass());
            }
		}

		return references;
	}


	@Override
	public Map<String, Class> listCollectionObjectTypes(Class boClass) {
		if (boClass == null) {
			throw new IllegalArgumentException("Class specified in the parameter was null.");
		}

		Map<String, Class> references = new HashMap<String, Class>();
		ClassDescriptor classDescriptor = null;
		try {
			classDescriptor = getClassDescriptor(boClass);
		} catch (ClassNotPersistableException cnpe) {
			return references;
		}

		Collection<CollectionDescriptor> collectionDescriptors = classDescriptor.getCollectionDescriptors(true);
		for (CollectionDescriptor collectionDescriptor : collectionDescriptors) {
			references.put(collectionDescriptor.getAttributeName(), collectionDescriptor.getItemClass());
		}

		return references;
	}

	@Override
	public Map<String, Class> listCollectionObjectTypes(PersistableBusinessObject bo) {
		// validate parameter
		if (bo == null) {
			throw new IllegalArgumentException("BO specified in the parameter was null.");
		}
		if (!(bo instanceof PersistableBusinessObject)) {
			throw new IllegalArgumentException("BO specified [" + bo.getClass().getName() + "] must be a class that " + "inherits from BusinessObject.");
		}

		return listCollectionObjectTypes(bo.getClass());
	}

	/**
	 * @see org.kuali.rice.krad.service.PersistenceStructureService#listReferenceObjectFieldNames(org.kuali.rice.krad.bo.BusinessObject)
	 */
	@Override
	public Map<String, Class> listReferenceObjectFields(PersistableBusinessObject bo) {
		// validate parameter
		if (bo == null) {
			throw new IllegalArgumentException("BO specified in the parameter was null.");
		}

		return listReferenceObjectFields(bo.getClass());
	}


	@Override
	public boolean isReferenceUpdatable(Class boClass, String referenceName) {
		ClassDescriptor classDescriptor = getClassDescriptor(boClass);
		ObjectReferenceDescriptor refDesc = classDescriptor.getObjectReferenceDescriptorByName(referenceName);
		return refDesc.getCascadingStore() != ObjectReferenceDescriptor.CASCADE_NONE;
	}


	@Override
	public boolean isCollectionUpdatable(Class boClass, String collectionName) {
		ClassDescriptor cd = getClassDescriptor(boClass);
		CollectionDescriptor collDesc = cd.getCollectionDescriptorByName(collectionName);
		return collDesc.getCascadingStore() != ObjectReferenceDescriptor.CASCADE_NONE;
	}


	@Override
	public boolean hasCollection(Class boClass, String collectionName) {
		ClassDescriptor cd = getClassDescriptor(boClass);
		return cd.getCollectionDescriptorByName(collectionName) != null;
	}


	@Override
	public boolean hasReference(Class boClass, String referenceName) {
		ClassDescriptor cd = getClassDescriptor(boClass);
		return cd.getObjectReferenceDescriptorByName(referenceName) != null;
	}

	/**
	 * This overridden method ...
	 *
	 * @see org.kuali.rice.krad.service.PersistenceStructureService#getTableName(java.lang.Class)
	 */

	@Override
	public String getTableName(Class<? extends PersistableBusinessObject> boClass) {
		ClassDescriptor cd = getClassDescriptor(boClass);
		return cd.getFullTableName();
	}


}

