/*
 * Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0,
 * or the Eclipse Distribution License v. 1.0 which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 */

// Contributors:
//     dclarke, mnorman - Dynamic Persistence
//       http://wiki.eclipse.org/EclipseLink/Development/Dynamic
//       (https://bugs.eclipse.org/bugs/show_bug.cgi?id=200045)
//
//     11/10/2011-2.4 Guy Pelletier
//       - 357474: Address primaryKey option from tenant discriminator column
package org.eclipse.persistence.dynamic;

//javase imports
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Iterator;

import javax.persistence.Embeddable;

//EclipseLink imports
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.descriptors.RelationalDescriptor;
import org.eclipse.persistence.exceptions.ValidationException;
import org.eclipse.persistence.indirection.IndirectList;
import org.eclipse.persistence.internal.dynamic.DynamicPropertiesManager;
import org.eclipse.persistence.internal.dynamic.DynamicTypeImpl;
import org.eclipse.persistence.internal.dynamic.ValuesAccessor;
import org.eclipse.persistence.internal.helper.ConversionManager;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.internal.sessions.factories.ObjectPersistenceWorkbenchXMLProject;
import org.eclipse.persistence.mappings.AggregateObjectMapping;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.mappings.DirectCollectionMapping;
import org.eclipse.persistence.mappings.DirectToFieldMapping;
import org.eclipse.persistence.mappings.ForeignReferenceMapping;
import org.eclipse.persistence.mappings.ManyToManyMapping;
import org.eclipse.persistence.mappings.OneToManyMapping;
import org.eclipse.persistence.mappings.OneToOneMapping;
import org.eclipse.persistence.mappings.converters.EnumTypeConverter;
import org.eclipse.persistence.mappings.foundation.AbstractDirectMapping;
import org.eclipse.persistence.platform.database.DatabasePlatform;
import org.eclipse.persistence.platform.xml.XMLParser;
import org.eclipse.persistence.platform.xml.XMLPlatformFactory;
import org.eclipse.persistence.sequencing.Sequence;
import org.eclipse.persistence.sessions.DatabaseLogin;
import org.eclipse.persistence.sessions.Project;
import org.eclipse.persistence.sessions.factories.XMLProjectReader;
import org.eclipse.persistence.tools.schemaframework.SchemaManager;
import org.w3c.dom.Document;

/**
 * The EntityTypeBuilder is a factory class for creating and extending dynamic
 * entity types. After being constructed in either usage the application can
 * then use the provided API to customize mapping information of the type.
 *
 * @author dclarke, mnorman
 * @since EclipseLink 1.2
 */
public class DynamicTypeBuilder {

    static XMLParser xmlParser = XMLPlatformFactory.getInstance().getXMLPlatform().newXMLParser();

    /**
     * The type being configured for dynamic use or being created/extended
     */
    protected DynamicTypeImpl entityType;

    /**
     * Create an EntityType for a new dynamic type. The contained EntityType and
     * its wrapped descriptor are not automatically added to any session. This
     * must be done by the application after the type's is fully configured.
     * <p>
     * <b>Creating new type Example</b>: <code>
     *  DynamicHelper helper = new DynamicHelper(session);
     *  DynamicClassLoader dcl = helper.getDynamicClassLoader();<br>
     *  <br>
     *  Class{@literal <?>} javaType = dcl.creatDynamicClass("model.Simple");<br>
     *  <br>
     *  DynamicTypeBuilder typeBuilder = new JPADynamicTypeBuilder(javaType, null, "SIMPLE_TYPE");<br>
     *  typeBuilder.setPrimaryKeyFields("SID");<br>
     *  typeBuilder.addDirectMapping("id", int.class, "SID");<br>
     *  typeBuilder.addDirectMapping("value1", String.class, "VAL_1");<br>
     *  typeBuilder.addDirectMapping("value2", boolean.class, "VAL_2");<br>
     *  typeBuilder.addDirectMapping("value3", Calendar.class, "VAL_3");<br>
     *  typeBuilder.addDirectMapping("value4", Character.class, "VAL_4");<br>
     *  <br>
     *  helper.addTypes(true, true, typeBuilder.getType());<br>
     * </code>
     *
     * @param dynamicClass
     * @param parentType
     * @param tableNames
     */
    public DynamicTypeBuilder(Class<?> dynamicClass, DynamicType parentType, String... tableNames) {
        RelationalDescriptor descriptor = new RelationalDescriptor();
        descriptor.setJavaClass(dynamicClass);
        this.entityType = new DynamicTypeImpl(descriptor, parentType);
        // JAXB generates some classes that do not conform to DynamicEntity interface - ignore
        if (DynamicEntity.class.isAssignableFrom(dynamicClass)) {
            try {
                Field dpmField = dynamicClass.getField(DynamicPropertiesManager.PROPERTIES_MANAGER_FIELD);
                DynamicPropertiesManager dpm = (DynamicPropertiesManager)dpmField.get(null);
                dpm.setType(entityType);
                entityType.setDynamicPropertiesManager(dpm);
            }
            catch (Exception e) {
                // this is bad! Without the dpmField, not much to do but die :-( ...
                e.printStackTrace();
                return;
            }
        }
        configure(descriptor, tableNames);
    }

    /**
     * Create an EntityTypeBuilder for an existing descriptor. This is used
     *
     * @param dcl
     * @param descriptor
     * @param parentType
     *            provided since the InheritancePolicy on the descriptor may not
     *            have its parent descriptor initialized.
     */
    public DynamicTypeBuilder(DynamicClassLoader dcl, ClassDescriptor descriptor, DynamicType parentType) {
        this.entityType = new DynamicTypeImpl(descriptor, parentType);
        Class<?> dynamicClass = descriptor.getJavaClass();
        if (dynamicClass == null) {
            addDynamicClasses(dcl, descriptor.getJavaClassName(), parentType);
        }
        else {
            // JAXB generates some classes that do not conform to DynamicEntity interface - ignore
            if (DynamicEntity.class.isAssignableFrom(dynamicClass)) {
                try {
                    Field dpmField =
                        descriptor.getJavaClass().getField(DynamicPropertiesManager.PROPERTIES_MANAGER_FIELD);
                    DynamicPropertiesManager dpm = (DynamicPropertiesManager)dpmField.get(null);
                    dpm.setType(entityType);
                    entityType.setDynamicPropertiesManager(dpm);
                }
                catch (Exception e) {
                    // this is bad! Without the dpmField, not much to do but die :-( ...
                    e.printStackTrace();
                    return;
                }
            }
        }
        configure(descriptor);
    }

    /**
     * Register a {@link DynamicClassWriter} with the provided
     * {@link DynamicClassLoader} so that a dynamic class can be generated when
     * needed.
     */
    protected void addDynamicClasses(DynamicClassLoader dcl, String className, DynamicType parentType) {
        if (parentType == null) {
            dcl.addClass(className);
        } else {
            if (parentType.getJavaClass() == null) {
                dcl.addClass(className, new DynamicClassWriter(parentType.getClassName()));
            } else {
                dcl.addClass(className, parentType.getJavaClass());
            }
        }
    }

    /**
     * Initialize a new or existing descriptor configuring the necessary
     * policies as well as
     */
    protected void configure(ClassDescriptor descriptor, String... tableNames) {
        // Configure Table names if provided
        if (tableNames != null) {
            if (tableNames.length == 0) {
                if (descriptor.getTables().size() == 0) {
                    descriptor.descriptorIsAggregate();
                }
            } else {
                for (int index = 0; index < tableNames.length; index++) {
                    descriptor.addTableName(tableNames[index]);
                }
            }

        }

        for (int index = 0; index < descriptor.getMappings().size(); index++) {
            addMapping(descriptor.getMappings().get(index));
        }

        descriptor.setProperty(DynamicType.DESCRIPTOR_PROPERTY, entityType);
    }

    public DynamicType getType() {
        return this.entityType;
    }

    /**
     * Test if a mapping requires initialization when a new instance is created.
     * This is true for:
     * <ul>
     * <li>primitives
     * <li>collection mappings
     * <li>basic indirection references
     * </ul>
     *
     * @see DynamicHelper#newDynamicEntity for creation and initialization
     */
    private boolean requiresInitialization(DatabaseMapping mapping) {
        if (mapping.isDirectToFieldMapping() && mapping.getAttributeClassification() != null && mapping.getAttributeClassification().isPrimitive()) {
            return true;
        }
        if (mapping.isForeignReferenceMapping()) {
            ForeignReferenceMapping frMapping = (ForeignReferenceMapping) mapping;
            return frMapping.usesIndirection() || frMapping.isCollectionMapping();
        }
        if (mapping.isAggregateObjectMapping() && !mapping.isXMLMapping()) {
            return !((AggregateObjectMapping) mapping).isNullAllowed();
        }
        return false;
    }

    /**
     * Set the PK field names on the underlying descriptor ensuring no duplicate
     * names are added.
     *
     * @param pkFieldNames
     *            qualified or unqualified field names
     */
    public void setPrimaryKeyFields(String... pkFieldNames) {
        if (pkFieldNames != null && pkFieldNames.length > 0) {
            for (int index = 0; index < pkFieldNames.length; index++) {
                DatabaseField pkField = new DatabaseField(pkFieldNames[index]);

                if (!getType().getDescriptor().getPrimaryKeyFields().contains(pkField)) {
                    getType().getDescriptor().addPrimaryKeyFieldName(pkFieldNames[index]);
                }
            }
        }
    }

    /**
     * Allows {@link DirectToFieldMapping} (@Basic) mapping to be added to a
     * dynamic type through API. This method can be used on a new
     * {@link DynamicTypeImpl} that has yet to be added to a session and have
     * its descriptor initialized, or it can be called on an active
     * (initialized) descriptor.
     * <p>
     * There is no support currently for having the EclipseLink
     * {@link SchemaManager} generate ALTER TABLE calls so any new columns
     * expected must be added without the help of EclipseLink or use the
     * {@link SchemaManager#replaceObject(org.eclipse.persistence.tools.schemaframework.DatabaseObjectDefinition)}
     * to DROP and CREATE the table. WARNING: This will cause data loss.
     *
     * @param javaType
     *            is the type of the attribute. If the type is a primitive it
     *            will be converted to the comparable non-primitive type.
     */
    public DirectToFieldMapping addDirectMapping(String name, Class<?> javaType, String fieldName) {
        DirectToFieldMapping mapping = new DirectToFieldMapping();
        mapping.setAttributeName(name);
        mapping.setFieldName(fieldName);
        mapping.setAttributeClassification(javaType);

        return (DirectToFieldMapping) addMapping(mapping);
    }

    /**
     * Allows {@link OneToOneMapping} (@OneToOne and @ManyToOne) mappings to be
     * added to a dynamic type through API. This method can be used on a new
     * {@link DynamicTypeImpl} that has yet to be added to a session and have
     * its descriptor initialized, or it can be called on an active
     * (initialized) descriptor.
     * <p>
     * There is no support currently for having the EclipseLink
     * {@link SchemaManager} generate ALTER TABLE calls so any new columns
     * expected must be added without the help of EclipseLink or use the
     * {@link SchemaManager#replaceObject(org.eclipse.persistence.tools.schemaframework.DatabaseObjectDefinition)}
     * to DROP and CREATE the table. WARNING: This will cause data loss.
     */
    public OneToOneMapping addOneToOneMapping(String name, DynamicType refType, String... fkFieldNames) {
        if (fkFieldNames == null) {
            throw new IllegalArgumentException("Invalid FK field names: 'null' for target: " + refType);
        }
        if (refType.getDescriptor().getPrimaryKeyFields().size() != fkFieldNames.length) {
            throw new IllegalArgumentException("Invalid FK field names: " + Arrays.asList(fkFieldNames) + " for target: " + refType);
        }

        OneToOneMapping mapping = new OneToOneMapping();
        mapping.setAttributeName(name);
        mapping.setReferenceClass(refType.getJavaClass());

        for (int index = 0; index < fkFieldNames.length; index++) {
            String targetField = refType.getDescriptor().getPrimaryKeyFields().get(index).getName();
            mapping.addForeignKeyFieldName(fkFieldNames[index], targetField);
        }

        return (OneToOneMapping) addMapping(mapping);
    }

    /**
     * Add a {@link OneToManyMapping} to the {@link #entityType} being built or
     * extended. This mapping is created using standard foreign keys from the
     * source table(s) to the target table(s) and transparent indirection (
     * {@link IndirectList}).
     *
     * @param name
     *            attribute name to use in the dynamic entity. Also the property
     *            name used to access the state of the entity
     * @param refType
     * @param fkFieldNames
     *            the FK field names specified in the same order to match the PK
     *            field names of the target class
     *
     * @return the newly created, configured mappin. It will be initialized if
     *         the descriptor is already initialized.
     */
    public OneToManyMapping addOneToManyMapping(String name, DynamicType refType, String... fkFieldNames) {
        if (fkFieldNames == null) {
            throw new IllegalArgumentException("Invalid FK field names: 'null' for target: " + refType);
        }
        if (getType().getDescriptor().getPrimaryKeyFields().size() != fkFieldNames.length) {
            throw new IllegalArgumentException("Invalid FK field names: " + Arrays.asList(fkFieldNames) + " for target: " + refType);
        }

        OneToManyMapping mapping = new OneToManyMapping();
        mapping.setAttributeName(name);
        mapping.setReferenceClass(refType.getJavaClass());

        for (int index = 0; index < fkFieldNames.length; index++) {
            String targetField = getType().getDescriptor().getPrimaryKeyFields().get(index).getName();
            mapping.addTargetForeignKeyFieldName(fkFieldNames[index], targetField);
        }

        mapping.useTransparentList();

        return (OneToManyMapping) addMapping(mapping);
    }

    /**
     * Add a {@link DirectCollectionMapping} to the {@link #entityType} being
     * built or extended. This mapping is created using standard foreign keys
     * from the target table(s) to the source table(s) and transparent
     * indirection ( {@link IndirectList}).
     *
     * @param name
     *            attribute name to use in the dynamic entity. Also the property
     *            name used to access the state of the entity
     * @param targetTable
     *            name of table holding the direct values
     * @param valueColumn
     *            name of column in target table holding the direct value for
     *            the collection
     * @param valueType
     *            the type of the attribute assumed to be a known basic type
     * @param fkFieldNames
     *            the FK field names on the source table specified in order to
     *            match the PK field names on the source.
     * @return the new mapping configured and initialized (if the descriptor is
     *         already initialized.
     * @throws IllegalArgumentException
     */
    public DirectCollectionMapping addDirectCollectionMapping(String name, String targetTable, String valueColumn, Class<?> valueType, String... fkFieldNames) throws IllegalArgumentException {
        if (fkFieldNames == null) {
            throw new IllegalArgumentException("Invalid FK field names: 'null' for target: ");
        }
        if (getType().getDescriptor().getPrimaryKeyFields().size() != fkFieldNames.length) {
            throw new IllegalArgumentException("Invalid FK field names: " + Arrays.asList(fkFieldNames) + " for target: ");
        }

        DirectCollectionMapping mapping = new DirectCollectionMapping();
        mapping.setAttributeName(name);
        mapping.setReferenceTableName(targetTable);
        mapping.setDirectFieldName(valueColumn);
        mapping.setDirectFieldClassification(valueType);

        for (int index = 0; index < fkFieldNames.length; index++) {
            String targetField = getType().getDescriptor().getPrimaryKeyFields().get(index).getName();
            mapping.addReferenceKeyFieldName(fkFieldNames[index], targetField);
        }

        mapping.useTransparentList();

        return (DirectCollectionMapping) addMapping(mapping);
    }

    /**
     * Add a {@link AggregateObjectMapping} ({@link Embeddable} in JPA) to the
     * {@link #entityType} being built or extended.
     *
     * @param name
     *            attribute name to use in the dynamic entity. Also the property
     *            name used to access the state of the entity
     * @param refType
     *            dynamic type re[presenting the Embeddable/AggregateObject
     * @param allowsNull
     *            true indicates that the attribute can be null if all values
     *            are null.
     * @return the new mapping configured and initialized (if the descriptor has
     *         been initialized).
     */
    public AggregateObjectMapping addAggregateObjectMapping(String name, DynamicType refType, boolean allowsNull) {
        AggregateObjectMapping mapping = new AggregateObjectMapping();
        mapping.setAttributeName(name);
        mapping.setReferenceClass(refType.getJavaClass());
        mapping.setIsNullAllowed(allowsNull);

        return (AggregateObjectMapping) addMapping(mapping);
    }

    /**
     * Add a {@link ManyToManyMapping} to the {@link #entityType} being built or
     * extended. This method assumes that the columns names on the relationship
     * table match the PK columns names they relate to. In the case of the
     * target keys from the relationship table a '_' will be appended to the
     * column names if they collide with the names from the source table.
     *
     * @param name
     *            attribute name to use in the dynamic entity. Also the property
     *            name used to access the state of the entity
     * @param refType
     *            target dynamic entity
     * @param relationshipTableName
     */
    public void addManyToManyMapping(String name, DynamicType refType, String relationshipTableName) {
        ManyToManyMapping mapping = new ManyToManyMapping();
        mapping.setAttributeName(name);
        mapping.setReferenceClass(refType.getJavaClass());
        mapping.setRelationTableName(relationshipTableName);

        for (DatabaseField sourcePK : getType().getDescriptor().getPrimaryKeyFields()) {
            mapping.addSourceRelationKeyFieldName(sourcePK.getName(), sourcePK.getQualifiedName());
        }
        for (DatabaseField targetPK : refType.getDescriptor().getPrimaryKeyFields()) {
            String relField = targetPK.getName();
            if (mapping.getSourceRelationKeyFieldNames().contains(relField)) {
                relField = refType.getName() + "_" + relField;
            }
            mapping.addTargetRelationKeyFieldName(relField, targetPK.getQualifiedName());
        }
        mapping.useTransparentList();

        addMapping(mapping);
    }

    /**
     * Add the mapping to the {@link #entityType}'s descriptor being built or
     * extended. This is where the {@link ValuesAccessor} is created and the
     * position of the mapping in the descriptor is captured to use as its
     * index.
     */
    public DatabaseMapping addMapping(DatabaseMapping mapping) {
        ClassDescriptor descriptor = getType().getDescriptor();

        if (!descriptor.getMappings().contains(mapping)) {
            descriptor.addMapping(mapping);
        }

        int index = descriptor.getMappings().indexOf(mapping);

        // Need to account for inherited mappings. When initialized a child
        // descriptor has all of its parent mappings added ahead of its
        // mappings. This adds the necessary offset.
        if (getType().getParentType() != null) {
            DynamicType current = getType();
            while (current.getParentType() != null) {
                index += current.getParentType().getDescriptor().getMappings().size();
                current = current.getParentType();
            }
        }

        // Try to configure the attribute classification if name is available
        if (mapping.getAttributeClassification() == null && mapping.isAbstractDirectMapping()) {
            String typeName = ((AbstractDirectMapping) mapping).getAttributeClassificationName();
            if (typeName != null) {
                // Remove any additional padding
                typeName = typeName.trim();
                try {
                    Class<?> attrType = ConversionManager.getDefaultManager().convertClassNameToClass(typeName);
                    ((AbstractDirectMapping) mapping).setAttributeClassification(attrType);
                } catch (Exception e) {
                }
            }
        }

        mapping.setAttributeAccessor(new ValuesAccessor(mapping));

        if (requiresInitialization(mapping)) {
            this.entityType.getMappingsRequiringInitialization().add(mapping);
        }

        return mapping;
    }

    /**
     * Configure default sequencing.
     */
    public void configureSequencing(String numberName, String numberFieldName) {
        getType().getDescriptor().setSequenceNumberName(numberName);
        getType().getDescriptor().setSequenceNumberFieldName(numberFieldName);
    }

    /**
     * Configure sequencing specifying the sequence type to use.
     */
    public void configureSequencing(Sequence sequence, String numberName, String numberFieldName) {
        configureSequencing(numberName, numberFieldName);
        getType().getDescriptor().setSequence(sequence);
    }

    public DynamicEnumBuilder addEnum(String fieldName, String className, String columnName,
        DynamicClassLoader dcl) {
        dcl.addEnum(className, (Object)null);
        AbstractDirectMapping adm = addDirectMappingForEnum(fieldName, className, columnName);
        return new DynamicEnumBuilder(className, adm, dcl);
    }

    protected AbstractDirectMapping addDirectMappingForEnum(String fieldName, String className,
        String columnName) {
        DirectToFieldMapping dtfm = addDirectMapping(fieldName, null, columnName);
        dtfm.setConverter(new EnumTypeConverter(dtfm, className));
        addMapping(dtfm);
        return dtfm;
    }

    /**
     * Load a dynamic project from deployment XML creating dynamic types for all
     * descriptors where the provided class name does not exist.
     *
     * @param resourcePath
     * @param login
     * @param dynamicClassLoader
     * @return a Project with {@link DynamicClassLoader} and associated
     *         {@link DynamicClassWriter} configured. Ensure if a new
     *         Login/Platform is being configured that the
     *         {@link ConversionManager#getLoader()} is maintained.
     *         <p>
     *         <tt>null</tt> is returned if the resourcePath cannot locate a
     *         deployment XML
     * @throws IOException
     */
    public static Project loadDynamicProject(String resourcePath, DatabaseLogin login, DynamicClassLoader dynamicClassLoader) throws IOException {

        if (resourcePath == null) {
            throw new NullPointerException("null resourceStream");
        }
        if (dynamicClassLoader == null) {
            throw new NullPointerException("null dynamicClassLoader");
        }
        return loadDynamicProject(dynamicClassLoader.getResourceAsStream(resourcePath), login, dynamicClassLoader);
    }



    /**
     * Load a dynamic project from deployment XML creating dynamic types for all
     * descriptors where the provided class name does not exist.
     *
     * @param resourceStream
     * @param login
     * @param dynamicClassLoader
     * @return a Project with {@link DynamicClassLoader} and associated
     *         {@link DynamicClassWriter} configured. Ensure if a new
     *         Login/Platform is being configured that the
     *         {@link ConversionManager#getLoader()} is maintained.
     *         <p>
     *         <tt>null</tt> is returned if the resourcePath cannot locate a
     *         deployment XML
     * @throws IOException
     */
    public static Project loadDynamicProject(InputStream resourceStream, DatabaseLogin login, DynamicClassLoader dynamicClassLoader) throws IOException {

        if (resourceStream == null) {
            throw new NullPointerException("null resourceStream");
        }
        if (dynamicClassLoader == null) {
            throw new NullPointerException("null dynamicClassLoader");
        }

        // Build an OXM project that loads the deployment XML without converting
        // the class names into classes
        ObjectPersistenceWorkbenchXMLProject opmProject = new ObjectPersistenceWorkbenchXMLProject();
        Document document = xmlParser.parse(resourceStream);
        Project project = XMLProjectReader.readObjectPersistenceRuntimeFormat(document, dynamicClassLoader, opmProject);
        return loadDynamicProject(project, login, dynamicClassLoader);
    }

    public static Project loadDynamicProject(Project project, DatabaseLogin login, DynamicClassLoader dynamicClassLoader) {
        if (project != null) {
            if (login == null) {
                if (project.getLogin() == null) {
                    project.setLogin(new DatabaseLogin());
                }
            } else {
                project.setLogin(login);
            }
            if (project.getLogin().getPlatform() == null) {
                project.getLogin().setPlatform(new DatabasePlatform());
            }

            project.getLogin().getPlatform().getConversionManager().setLoader(dynamicClassLoader);

            for (Iterator<?> i = project.getOrderedDescriptors().iterator(); i.hasNext();) {
                ClassDescriptor descriptor = (ClassDescriptor) i.next();
                if (descriptor.getJavaClass() == null) {
                    createType(dynamicClassLoader, descriptor, project);
                }
            }
            project.convertClassNamesToClasses(dynamicClassLoader);
            for (Iterator<?> i = project.getOrderedDescriptors().iterator(); i.hasNext();) {
                ClassDescriptor descriptor = (ClassDescriptor) i.next();
                Class<?> dynamicClass = descriptor.getJavaClass();
                // JAXB generates some classes that do not conform to DynamicEntity interface - ignore
                if (dynamicClass != null && DynamicEntity.class.isAssignableFrom(dynamicClass)) {
                    DynamicType type = DynamicHelper.getType(descriptor);
                    try {
                        Field dpmField =
                            descriptor.getJavaClass().getField(DynamicPropertiesManager.PROPERTIES_MANAGER_FIELD);
                        DynamicPropertiesManager dpm = (DynamicPropertiesManager)dpmField.get(null);
                        dpm.setType(type);
                        ((DynamicTypeImpl)type).setDynamicPropertiesManager(dpm);
                    }
                    catch (Exception e) {
                        // this is bad! Without the dpmField, not much to do but die :-( ...
                        e.printStackTrace();
                        return null;
                    }
                }
            }
        }

        return project;
    }

    /**
     * Create EntityType for a descriptor including the creation of a new
     * dynamic type. This method needs to handle inheritance where the parent
     * type needs to be defined before this type.
     */
    private static DynamicType createType(DynamicClassLoader dcl, ClassDescriptor descriptor, Project project) {
        Class<?> javaClass = null;
        try {
            javaClass = dcl.loadClass(descriptor.getJavaClassName());
        } catch (ClassNotFoundException e) {
        }

        if (javaClass != null) {
            descriptor.setJavaClass(javaClass);
        }

        DynamicType parent = null;

        if (descriptor.hasInheritance() && descriptor.getInheritancePolicy().getParentClassName() != null) {
            ClassDescriptor parentDesc = null;
            for (Iterator<?> i = project.getOrderedDescriptors().iterator(); parentDesc == null && i.hasNext();) {
                ClassDescriptor d = (ClassDescriptor) i.next();
                if (d.getJavaClassName().equals(descriptor.getInheritancePolicy().getParentClassName())) {
                    parentDesc = d;
                }
            }

            if (parentDesc == null) {
                throw ValidationException.missingDescriptor(descriptor.getInheritancePolicy().getParentClassName());
            }

            parent = DynamicHelper.getType(parentDesc);
            if (parent == null) {
                parent = createType(dcl, parentDesc, project);
            }
        }

        DynamicType type = DynamicHelper.getType(descriptor);
        if (type == null) {
            type = new DynamicTypeBuilder(dcl, descriptor, parent).getType();
        }

        // JAXB generates some classes that do not conform to DynamicEntity interface - ignore
        if (javaClass != null && DynamicEntity.class.isAssignableFrom(javaClass)) {
            try {
                Field dpmField = javaClass.getField(DynamicPropertiesManager.PROPERTIES_MANAGER_FIELD);
                DynamicPropertiesManager dpm = (DynamicPropertiesManager)dpmField.get(null);
                dpm.setType(type);
                ((DynamicTypeImpl)type).setDynamicPropertiesManager(dpm);
            }
            catch (Exception e) {
                // this is bad! Without the dpmField, not much to do but die :-( ...
                e.printStackTrace();
                return null;
            }
        }

        return type;
    }
}
