org.hibernate.tuple.entity.EntityMetamodel.java Source code

Java tutorial

Introduction

Here is the source code for org.hibernate.tuple.entity.EntityMetamodel.java

Source

/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * License: GNU Lesser General Public License (LGPL), version 2.1 or later.
 * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
 */
package org.hibernate.tuple.entity;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.hibernate.EntityMode;
import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementHelper;
import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata;
import org.hibernate.cfg.NotYetImplementedException;
import org.hibernate.engine.OptimisticLockStyle;
import org.hibernate.engine.spi.CascadeStyle;
import org.hibernate.engine.spi.CascadeStyles;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.ReflectHelper;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.mapping.Component;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.tuple.GenerationTiming;
import org.hibernate.tuple.IdentifierProperty;
import org.hibernate.tuple.InDatabaseValueGenerationStrategy;
import org.hibernate.tuple.InMemoryValueGenerationStrategy;
import org.hibernate.tuple.NonIdentifierAttribute;
import org.hibernate.tuple.PropertyFactory;
import org.hibernate.tuple.ValueGeneration;
import org.hibernate.tuple.ValueGenerator;
import org.hibernate.type.AssociationType;
import org.hibernate.type.CompositeType;
import org.hibernate.type.EntityType;
import org.hibernate.type.Type;

import static org.hibernate.internal.CoreLogging.messageLogger;

/**
 * Centralizes metamodel information about an entity.
 *
 * @author Steve Ebersole
 */
public class EntityMetamodel implements Serializable {
    private static final CoreMessageLogger LOG = messageLogger(EntityMetamodel.class);

    private static final int NO_VERSION_INDX = -66;

    private final SessionFactoryImplementor sessionFactory;

    private final String name;
    private final String rootName;
    private EntityType entityType;

    private final IdentifierProperty identifierAttribute;
    private final boolean versioned;

    private final int propertySpan;
    private final int versionPropertyIndex;
    private final NonIdentifierAttribute[] properties;
    // temporary ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    private final String[] propertyNames;
    private final Type[] propertyTypes;
    private final boolean[] propertyLaziness;
    private final boolean[] propertyUpdateability;
    private final boolean[] nonlazyPropertyUpdateability;
    private final boolean[] propertyCheckability;
    private final boolean[] propertyInsertability;
    private final boolean[] propertyNullability;
    private final boolean[] propertyVersionability;
    private final CascadeStyle[] cascadeStyles;
    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    // value generations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    private final boolean hasPreInsertGeneratedValues;
    private final boolean hasPreUpdateGeneratedValues;
    private final boolean hasInsertGeneratedValues;
    private final boolean hasUpdateGeneratedValues;

    private final InMemoryValueGenerationStrategy[] inMemoryValueGenerationStrategies;
    private final InDatabaseValueGenerationStrategy[] inDatabaseValueGenerationStrategies;
    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    private final Map<String, Integer> propertyIndexes = new HashMap<>();
    private final boolean hasCollections;
    private final boolean hasMutableProperties;
    private final boolean hasLazyProperties;
    private final boolean hasNonIdentifierPropertyNamedId;

    private final int[] naturalIdPropertyNumbers;
    private final boolean hasImmutableNaturalId;
    private final boolean hasCacheableNaturalId;

    private boolean lazy; //not final because proxy factory creation can fail
    private final boolean hasCascades;
    private final boolean mutable;
    private final boolean isAbstract;
    private final boolean selectBeforeUpdate;
    private final boolean dynamicUpdate;
    private final boolean dynamicInsert;
    private final OptimisticLockStyle optimisticLockStyle;

    private final boolean polymorphic;
    private final String superclass; // superclass entity-name
    private final boolean explicitPolymorphism;
    private final boolean inherited;
    private final boolean hasSubclasses;
    private final Set subclassEntityNames = new HashSet();
    private final Map entityNameByInheritenceClassMap = new HashMap();

    private final EntityMode entityMode;
    private final EntityTuplizer entityTuplizer;
    private final BytecodeEnhancementMetadata bytecodeEnhancementMetadata;

    public EntityMetamodel(PersistentClass persistentClass, EntityPersister persister,
            SessionFactoryImplementor sessionFactory) {
        this.sessionFactory = sessionFactory;

        name = persistentClass.getEntityName();
        rootName = persistentClass.getRootClass().getEntityName();

        identifierAttribute = PropertyFactory.buildIdentifierAttribute(persistentClass,
                sessionFactory.getIdentifierGenerator(rootName));

        versioned = persistentClass.isVersioned();

        if (persistentClass.hasPojoRepresentation()) {
            final Component identifierMapperComponent = persistentClass.getIdentifierMapper();
            final CompositeType nonAggregatedCidMapper;
            final Set<String> idAttributeNames;

            if (identifierMapperComponent != null) {
                nonAggregatedCidMapper = (CompositeType) identifierMapperComponent.getType();
                idAttributeNames = new HashSet<>();
                //noinspection unchecked
                final Iterator<Property> propertyItr = identifierMapperComponent.getPropertyIterator();
                while (propertyItr.hasNext()) {
                    idAttributeNames.add(propertyItr.next().getName());
                }
            } else {
                nonAggregatedCidMapper = null;
                idAttributeNames = Collections.singleton(identifierAttribute.getName());
            }

            bytecodeEnhancementMetadata = BytecodeEnhancementMetadataPojoImpl.from(persistentClass,
                    idAttributeNames, nonAggregatedCidMapper,
                    sessionFactory.getSessionFactoryOptions().isEnhancementAsProxyEnabled());
        } else {
            bytecodeEnhancementMetadata = new BytecodeEnhancementMetadataNonPojoImpl(
                    persistentClass.getEntityName());
        }

        boolean hasLazy = false;

        propertySpan = persistentClass.getPropertyClosureSpan();
        properties = new NonIdentifierAttribute[propertySpan];
        List<Integer> naturalIdNumbers = new ArrayList<>();
        // temporary ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        propertyNames = new String[propertySpan];
        propertyTypes = new Type[propertySpan];
        propertyUpdateability = new boolean[propertySpan];
        propertyInsertability = new boolean[propertySpan];
        nonlazyPropertyUpdateability = new boolean[propertySpan];
        propertyCheckability = new boolean[propertySpan];
        propertyNullability = new boolean[propertySpan];
        propertyVersionability = new boolean[propertySpan];
        propertyLaziness = new boolean[propertySpan];
        cascadeStyles = new CascadeStyle[propertySpan];
        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        // generated value strategies ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        this.inMemoryValueGenerationStrategies = new InMemoryValueGenerationStrategy[propertySpan];
        this.inDatabaseValueGenerationStrategies = new InDatabaseValueGenerationStrategy[propertySpan];

        boolean foundPreInsertGeneratedValues = false;
        boolean foundPreUpdateGeneratedValues = false;
        boolean foundPostInsertGeneratedValues = false;
        boolean foundPostUpdateGeneratedValues = false;
        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        Iterator iter = persistentClass.getPropertyClosureIterator();
        int i = 0;
        int tempVersionProperty = NO_VERSION_INDX;
        boolean foundCascade = false;
        boolean foundCollection = false;
        boolean foundMutable = false;
        boolean foundNonIdentifierPropertyNamedId = false;
        boolean foundUpdateableNaturalIdProperty = false;

        while (iter.hasNext()) {
            Property prop = (Property) iter.next();

            if (prop == persistentClass.getVersion()) {
                tempVersionProperty = i;
                properties[i] = PropertyFactory.buildVersionProperty(persister, sessionFactory, i, prop,
                        bytecodeEnhancementMetadata.isEnhancedForLazyLoading());
            } else {
                properties[i] = PropertyFactory.buildEntityBasedAttribute(persister, sessionFactory, i, prop,
                        bytecodeEnhancementMetadata.isEnhancedForLazyLoading());
            }

            if (prop.isNaturalIdentifier()) {
                naturalIdNumbers.add(i);
                if (prop.isUpdateable()) {
                    foundUpdateableNaturalIdProperty = true;
                }
            }

            if ("id".equals(prop.getName())) {
                foundNonIdentifierPropertyNamedId = true;
            }

            // temporary ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            boolean lazy = !EnhancementHelper.includeInBaseFetchGroup(prop,
                    bytecodeEnhancementMetadata.isEnhancedForLazyLoading(),
                    sessionFactory.getSessionFactoryOptions().isEnhancementAsProxyEnabled());

            if (lazy) {
                hasLazy = true;
            }

            propertyLaziness[i] = lazy;

            propertyNames[i] = properties[i].getName();
            propertyTypes[i] = properties[i].getType();
            propertyNullability[i] = properties[i].isNullable();
            propertyUpdateability[i] = properties[i].isUpdateable();
            propertyInsertability[i] = properties[i].isInsertable();
            propertyVersionability[i] = properties[i].isVersionable();
            nonlazyPropertyUpdateability[i] = properties[i].isUpdateable() && !lazy;
            propertyCheckability[i] = propertyUpdateability[i] || (propertyTypes[i].isAssociationType()
                    && ((AssociationType) propertyTypes[i]).isAlwaysDirtyChecked());

            cascadeStyles[i] = properties[i].getCascadeStyle();
            // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

            // generated value strategies ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            GenerationStrategyPair pair = buildGenerationStrategyPair(sessionFactory, prop);
            inMemoryValueGenerationStrategies[i] = pair.getInMemoryStrategy();
            inDatabaseValueGenerationStrategies[i] = pair.getInDatabaseStrategy();

            if (pair.getInMemoryStrategy() != null) {
                final GenerationTiming timing = pair.getInMemoryStrategy().getGenerationTiming();
                if (timing != GenerationTiming.NEVER) {
                    final ValueGenerator generator = pair.getInMemoryStrategy().getValueGenerator();
                    if (generator != null) {
                        // we have some level of generation indicated
                        if (timing == GenerationTiming.INSERT) {
                            foundPreInsertGeneratedValues = true;
                        } else if (timing == GenerationTiming.ALWAYS) {
                            foundPreInsertGeneratedValues = true;
                            foundPreUpdateGeneratedValues = true;
                        }
                    }
                }
            }
            if (pair.getInDatabaseStrategy() != null) {
                final GenerationTiming timing = pair.getInDatabaseStrategy().getGenerationTiming();
                if (timing == GenerationTiming.INSERT) {
                    foundPostInsertGeneratedValues = true;
                } else if (timing == GenerationTiming.ALWAYS) {
                    foundPostInsertGeneratedValues = true;
                    foundPostUpdateGeneratedValues = true;
                }
            }
            // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

            if (properties[i].isLazy()) {
                hasLazy = true;
            }

            if (properties[i].getCascadeStyle() != CascadeStyles.NONE) {
                foundCascade = true;
            }

            if (indicatesCollection(properties[i].getType())) {
                foundCollection = true;
            }

            if (propertyTypes[i].isMutable() && propertyCheckability[i]) {
                foundMutable = true;
            }

            mapPropertyToIndex(prop, i);
            i++;
        }

        if (naturalIdNumbers.size() == 0) {
            naturalIdPropertyNumbers = null;
            hasImmutableNaturalId = false;
            hasCacheableNaturalId = false;
        } else {
            naturalIdPropertyNumbers = ArrayHelper.toIntArray(naturalIdNumbers);
            hasImmutableNaturalId = !foundUpdateableNaturalIdProperty;
            hasCacheableNaturalId = persistentClass.getNaturalIdCacheRegionName() != null;
        }

        this.hasPreInsertGeneratedValues = foundPreInsertGeneratedValues;
        this.hasPreUpdateGeneratedValues = foundPreUpdateGeneratedValues;
        this.hasInsertGeneratedValues = foundPostInsertGeneratedValues;
        this.hasUpdateGeneratedValues = foundPostUpdateGeneratedValues;

        hasCascades = foundCascade;
        hasNonIdentifierPropertyNamedId = foundNonIdentifierPropertyNamedId;
        versionPropertyIndex = tempVersionProperty;
        hasLazyProperties = hasLazy;
        if (hasLazyProperties) {
            LOG.lazyPropertyFetchingAvailable(name);
        }

        lazy = persistentClass.isLazy() && (
        // TODO: this disables laziness even in non-pojo entity modes:
        !persistentClass.hasPojoRepresentation()
                || !ReflectHelper.isFinalClass(persistentClass.getProxyInterface()));
        mutable = persistentClass.isMutable();
        if (persistentClass.isAbstract() == null) {
            // legacy behavior (with no abstract attribute specified)
            isAbstract = persistentClass.hasPojoRepresentation()
                    && ReflectHelper.isAbstractClass(persistentClass.getMappedClass());
        } else {
            isAbstract = persistentClass.isAbstract().booleanValue();
            if (!isAbstract && persistentClass.hasPojoRepresentation()
                    && ReflectHelper.isAbstractClass(persistentClass.getMappedClass())) {
                LOG.entityMappedAsNonAbstract(name);
            }
        }

        selectBeforeUpdate = persistentClass.hasSelectBeforeUpdate();

        dynamicUpdate = persistentClass.useDynamicUpdate() || (getBytecodeEnhancementMetadata()
                .isEnhancedForLazyLoading()
                && getBytecodeEnhancementMetadata().getLazyAttributesMetadata().getFetchGroupNames().size() > 1);
        dynamicInsert = persistentClass.useDynamicInsert();

        polymorphic = persistentClass.isPolymorphic();
        explicitPolymorphism = persistentClass.isExplicitPolymorphism();
        inherited = persistentClass.isInherited();
        superclass = inherited ? persistentClass.getSuperclass().getEntityName() : null;
        hasSubclasses = persistentClass.hasSubclasses();

        optimisticLockStyle = persistentClass.getOptimisticLockStyle();
        final boolean isAllOrDirty = optimisticLockStyle == OptimisticLockStyle.ALL
                || optimisticLockStyle == OptimisticLockStyle.DIRTY;
        if (isAllOrDirty && !dynamicUpdate) {
            throw new MappingException("optimistic-lock=all|dirty requires dynamic-update=\"true\": " + name);
        }
        if (versionPropertyIndex != NO_VERSION_INDX && isAllOrDirty) {
            throw new MappingException(
                    "version and optimistic-lock=all|dirty are not a valid combination : " + name);
        }

        hasCollections = foundCollection;
        hasMutableProperties = foundMutable;

        iter = persistentClass.getSubclassIterator();
        while (iter.hasNext()) {
            subclassEntityNames.add(((PersistentClass) iter.next()).getEntityName());
        }
        subclassEntityNames.add(name);

        if (persistentClass.hasPojoRepresentation()) {
            entityNameByInheritenceClassMap.put(persistentClass.getMappedClass(), persistentClass.getEntityName());
            iter = persistentClass.getSubclassIterator();
            while (iter.hasNext()) {
                final PersistentClass pc = (PersistentClass) iter.next();
                entityNameByInheritenceClassMap.put(pc.getMappedClass(), pc.getEntityName());
            }
        }

        entityMode = persistentClass.hasPojoRepresentation() ? EntityMode.POJO : EntityMode.MAP;
        final EntityTuplizerFactory entityTuplizerFactory = sessionFactory.getSessionFactoryOptions()
                .getEntityTuplizerFactory();
        final String tuplizerClassName = persistentClass.getTuplizerImplClassName(entityMode);
        if (tuplizerClassName == null) {
            entityTuplizer = entityTuplizerFactory.constructDefaultTuplizer(entityMode, this, persistentClass);
        } else {
            entityTuplizer = entityTuplizerFactory.constructTuplizer(tuplizerClassName, this, persistentClass);
        }
    }

    private static GenerationStrategyPair buildGenerationStrategyPair(
            final SessionFactoryImplementor sessionFactory, final Property mappingProperty) {
        final ValueGeneration valueGeneration = mappingProperty.getValueGenerationStrategy();
        if (valueGeneration != null && valueGeneration.getGenerationTiming() != GenerationTiming.NEVER) {
            // the property is generated in full. build the generation strategy pair.
            if (valueGeneration.getValueGenerator() != null) {
                // in-memory generation
                return new GenerationStrategyPair(FullInMemoryValueGenerationStrategy.create(valueGeneration));
            } else {
                // in-db generation
                return new GenerationStrategyPair(create(sessionFactory, mappingProperty, valueGeneration));
            }
        } else if (mappingProperty.getValue() instanceof Component) {
            final CompositeGenerationStrategyPairBuilder builder = new CompositeGenerationStrategyPairBuilder(
                    mappingProperty);
            interpretPartialCompositeValueGeneration(sessionFactory, (Component) mappingProperty.getValue(),
                    builder);
            return builder.buildPair();
        }

        return NO_GEN_PAIR;
    }

    private static final GenerationStrategyPair NO_GEN_PAIR = new GenerationStrategyPair();

    private static void interpretPartialCompositeValueGeneration(SessionFactoryImplementor sessionFactory,
            Component composite, CompositeGenerationStrategyPairBuilder builder) {
        Iterator subProperties = composite.getPropertyIterator();
        while (subProperties.hasNext()) {
            final Property subProperty = (Property) subProperties.next();
            builder.addPair(buildGenerationStrategyPair(sessionFactory, subProperty));
        }
    }

    public static InDatabaseValueGenerationStrategyImpl create(SessionFactoryImplementor sessionFactoryImplementor,
            Property mappingProperty, ValueGeneration valueGeneration) {
        final int numberOfMappedColumns = mappingProperty.getType().getColumnSpan(sessionFactoryImplementor);
        if (numberOfMappedColumns == 1) {
            return new InDatabaseValueGenerationStrategyImpl(valueGeneration.getGenerationTiming(),
                    valueGeneration.referenceColumnInSql(),
                    new String[] { valueGeneration.getDatabaseGeneratedReferencedColumnValue() }

            );
        } else {
            if (valueGeneration.getDatabaseGeneratedReferencedColumnValue() != null) {
                LOG.debugf(
                        "Value generator specified column value in reference to multi-column attribute [%s -> %s]; ignoring",
                        mappingProperty.getPersistentClass(), mappingProperty.getName());
            }
            return new InDatabaseValueGenerationStrategyImpl(valueGeneration.getGenerationTiming(),
                    valueGeneration.referenceColumnInSql(), new String[numberOfMappedColumns]);
        }
    }

    public static class GenerationStrategyPair {
        private final InMemoryValueGenerationStrategy inMemoryStrategy;
        private final InDatabaseValueGenerationStrategy inDatabaseStrategy;

        public GenerationStrategyPair() {
            this(NoInMemoryValueGenerationStrategy.INSTANCE, NoInDatabaseValueGenerationStrategy.INSTANCE);
        }

        public GenerationStrategyPair(FullInMemoryValueGenerationStrategy inMemoryStrategy) {
            this(inMemoryStrategy, NoInDatabaseValueGenerationStrategy.INSTANCE);
        }

        public GenerationStrategyPair(InDatabaseValueGenerationStrategyImpl inDatabaseStrategy) {
            this(NoInMemoryValueGenerationStrategy.INSTANCE, inDatabaseStrategy);
        }

        public GenerationStrategyPair(InMemoryValueGenerationStrategy inMemoryStrategy,
                InDatabaseValueGenerationStrategy inDatabaseStrategy) {
            // perform some normalization.  Also check that only one (if any) strategy is specified
            if (inMemoryStrategy == null) {
                inMemoryStrategy = NoInMemoryValueGenerationStrategy.INSTANCE;
            }
            if (inDatabaseStrategy == null) {
                inDatabaseStrategy = NoInDatabaseValueGenerationStrategy.INSTANCE;
            }

            if (inMemoryStrategy.getGenerationTiming() != GenerationTiming.NEVER
                    && inDatabaseStrategy.getGenerationTiming() != GenerationTiming.NEVER) {
                throw new ValueGenerationStrategyException(
                        "in-memory and in-database value generation are mutually exclusive");
            }

            this.inMemoryStrategy = inMemoryStrategy;
            this.inDatabaseStrategy = inDatabaseStrategy;
        }

        public InMemoryValueGenerationStrategy getInMemoryStrategy() {
            return inMemoryStrategy;
        }

        public InDatabaseValueGenerationStrategy getInDatabaseStrategy() {
            return inDatabaseStrategy;
        }
    }

    public static class ValueGenerationStrategyException extends HibernateException {
        public ValueGenerationStrategyException(String message) {
            super(message);
        }
    }

    private static class CompositeGenerationStrategyPairBuilder {
        private final Property mappingProperty;

        private boolean hadInMemoryGeneration;
        private boolean hadInDatabaseGeneration;

        private List<InDatabaseValueGenerationStrategy> inDatabaseStrategies;

        public CompositeGenerationStrategyPairBuilder(Property mappingProperty) {
            this.mappingProperty = mappingProperty;
        }

        public void addPair(GenerationStrategyPair generationStrategyPair) {
            add(generationStrategyPair.getInMemoryStrategy());
            add(generationStrategyPair.getInDatabaseStrategy());
        }

        private void add(InMemoryValueGenerationStrategy inMemoryStrategy) {
            if (inMemoryStrategy.getGenerationTiming() != GenerationTiming.NEVER) {
                hadInMemoryGeneration = true;
            }
        }

        private void add(InDatabaseValueGenerationStrategy inDatabaseStrategy) {
            if (inDatabaseStrategies == null) {
                inDatabaseStrategies = new ArrayList<>();
            }
            inDatabaseStrategies.add(inDatabaseStrategy);

            if (inDatabaseStrategy.getGenerationTiming() != GenerationTiming.NEVER) {
                hadInDatabaseGeneration = true;
            }
        }

        public GenerationStrategyPair buildPair() {
            if (hadInMemoryGeneration && hadInDatabaseGeneration) {
                throw new ValueGenerationStrategyException("Composite attribute [" + mappingProperty.getName()
                        + "] contained both in-memory" + " and in-database value generation");
            } else if (hadInMemoryGeneration) {
                throw new NotYetImplementedException("Still need to wire in composite in-memory value generation");

            } else if (hadInDatabaseGeneration) {
                final Component composite = (Component) mappingProperty.getValue();

                // we need the numbers to match up so we can properly handle 'referenced sql column values'
                if (inDatabaseStrategies.size() != composite.getPropertySpan()) {
                    throw new ValueGenerationStrategyException(
                            "Internal error : mismatch between number of collected in-db generation strategies"
                                    + " and number of attributes for composite attribute : "
                                    + mappingProperty.getName());
                }

                // the base-line values for the aggregated InDatabaseValueGenerationStrategy we will build here.
                GenerationTiming timing = GenerationTiming.INSERT;
                boolean referenceColumns = false;
                String[] columnValues = new String[composite.getColumnSpan()];

                // start building the aggregate values
                int propertyIndex = -1;
                int columnIndex = 0;
                Iterator subProperties = composite.getPropertyIterator();
                while (subProperties.hasNext()) {
                    propertyIndex++;
                    final Property subProperty = (Property) subProperties.next();
                    final InDatabaseValueGenerationStrategy subStrategy = inDatabaseStrategies.get(propertyIndex);

                    if (subStrategy.getGenerationTiming() == GenerationTiming.ALWAYS) {
                        // override the base-line to the more often "ALWAYS"...
                        timing = GenerationTiming.ALWAYS;

                    }
                    if (subStrategy.referenceColumnsInSql()) {
                        // override base-line value
                        referenceColumns = true;
                    }
                    if (subStrategy.getReferencedColumnValues() != null) {
                        if (subStrategy.getReferencedColumnValues().length != subProperty.getColumnSpan()) {
                            throw new ValueGenerationStrategyException(
                                    "Internal error : mismatch between number of collected 'referenced column values'"
                                            + " and number of columns for composite attribute : "
                                            + mappingProperty.getName() + '.' + subProperty.getName());
                        }
                        System.arraycopy(subStrategy.getReferencedColumnValues(), 0, columnValues, columnIndex,
                                subProperty.getColumnSpan());
                    }
                }

                // then use the aggregated values to build the InDatabaseValueGenerationStrategy
                return new GenerationStrategyPair(
                        new InDatabaseValueGenerationStrategyImpl(timing, referenceColumns, columnValues));
            } else {
                return NO_GEN_PAIR;
            }
        }
    }

    private static class NoInMemoryValueGenerationStrategy implements InMemoryValueGenerationStrategy {
        /**
         * Singleton access
         */
        public static final NoInMemoryValueGenerationStrategy INSTANCE = new NoInMemoryValueGenerationStrategy();

        @Override
        public GenerationTiming getGenerationTiming() {
            return GenerationTiming.NEVER;
        }

        @Override
        public ValueGenerator getValueGenerator() {
            return null;
        }
    }

    private static class FullInMemoryValueGenerationStrategy implements InMemoryValueGenerationStrategy {
        private final GenerationTiming timing;
        private final ValueGenerator generator;

        private FullInMemoryValueGenerationStrategy(GenerationTiming timing, ValueGenerator generator) {
            this.timing = timing;
            this.generator = generator;
        }

        public static FullInMemoryValueGenerationStrategy create(ValueGeneration valueGeneration) {
            return new FullInMemoryValueGenerationStrategy(valueGeneration.getGenerationTiming(),
                    valueGeneration.getValueGenerator());
        }

        @Override
        public GenerationTiming getGenerationTiming() {
            return timing;
        }

        @Override
        public ValueGenerator getValueGenerator() {
            return generator;
        }
    }

    private static class NoInDatabaseValueGenerationStrategy implements InDatabaseValueGenerationStrategy {
        /**
         * Singleton access
         */
        public static final NoInDatabaseValueGenerationStrategy INSTANCE = new NoInDatabaseValueGenerationStrategy();

        @Override
        public GenerationTiming getGenerationTiming() {
            return GenerationTiming.NEVER;
        }

        @Override
        public boolean referenceColumnsInSql() {
            return true;
        }

        @Override
        public String[] getReferencedColumnValues() {
            return null;
        }
    }

    private static class InDatabaseValueGenerationStrategyImpl implements InDatabaseValueGenerationStrategy {
        private final GenerationTiming timing;
        private final boolean referenceColumnInSql;
        private final String[] referencedColumnValues;

        private InDatabaseValueGenerationStrategyImpl(GenerationTiming timing, boolean referenceColumnInSql,
                String[] referencedColumnValues) {
            this.timing = timing;
            this.referenceColumnInSql = referenceColumnInSql;
            this.referencedColumnValues = referencedColumnValues;
        }

        @Override
        public GenerationTiming getGenerationTiming() {
            return timing;
        }

        @Override
        public boolean referenceColumnsInSql() {
            return referenceColumnInSql;
        }

        @Override
        public String[] getReferencedColumnValues() {
            return referencedColumnValues;
        }
    }

    private void mapPropertyToIndex(Property prop, int i) {
        propertyIndexes.put(prop.getName(), i);
        if (prop.getValue() instanceof Component) {
            Iterator iter = ((Component) prop.getValue()).getPropertyIterator();
            while (iter.hasNext()) {
                Property subprop = (Property) iter.next();
                propertyIndexes.put(prop.getName() + '.' + subprop.getName(), i);
            }
        }
    }

    public EntityTuplizer getTuplizer() {
        return entityTuplizer;
    }

    public boolean isNaturalIdentifierInsertGenerated() {
        // the intention is for this call to replace the usage of the old ValueInclusion stuff (as exposed from
        // persister) in SelectGenerator to determine if it is safe to use the natural identifier to find the
        // insert-generated identifier.  That wont work if the natural-id is also insert-generated.
        //
        // Assumptions:
        //      * That code checks that there is a natural identifier before making this call, so we assume the same here
        //       * That code assumes a non-composite natural-id, so we assume the same here
        final InDatabaseValueGenerationStrategy strategy = inDatabaseValueGenerationStrategies[naturalIdPropertyNumbers[0]];
        return strategy != null && strategy.getGenerationTiming() != GenerationTiming.NEVER;
    }

    public boolean isVersionGenerated() {
        final InDatabaseValueGenerationStrategy strategy = inDatabaseValueGenerationStrategies[versionPropertyIndex];
        return strategy != null && strategy.getGenerationTiming() != GenerationTiming.NEVER;
    }

    public int[] getNaturalIdentifierProperties() {
        return naturalIdPropertyNumbers;
    }

    public boolean hasNaturalIdentifier() {
        return naturalIdPropertyNumbers != null;
    }

    public boolean isNaturalIdentifierCached() {
        return hasNaturalIdentifier() && hasCacheableNaturalId;
    }

    public boolean hasImmutableNaturalId() {
        return hasImmutableNaturalId;
    }

    public Set getSubclassEntityNames() {
        return subclassEntityNames;
    }

    private boolean indicatesCollection(Type type) {
        if (type.isCollectionType()) {
            return true;
        } else if (type.isComponentType()) {
            Type[] subtypes = ((CompositeType) type).getSubtypes();
            for (Type subtype : subtypes) {
                if (indicatesCollection(subtype)) {
                    return true;
                }
            }
        }
        return false;
    }

    public SessionFactoryImplementor getSessionFactory() {
        return sessionFactory;
    }

    public String getName() {
        return name;
    }

    public String getRootName() {
        return rootName;
    }

    public EntityType getEntityType() {
        if (entityType == null) {
            entityType = sessionFactory.getTypeResolver().getTypeFactory().manyToOne(name);
        }
        return entityType;
    }

    public IdentifierProperty getIdentifierProperty() {
        return identifierAttribute;
    }

    public int getPropertySpan() {
        return propertySpan;
    }

    public int getVersionPropertyIndex() {
        return versionPropertyIndex;
    }

    public VersionProperty getVersionProperty() {
        if (NO_VERSION_INDX == versionPropertyIndex) {
            return null;
        } else {
            return (VersionProperty) properties[versionPropertyIndex];
        }
    }

    public NonIdentifierAttribute[] getProperties() {
        return properties;
    }

    public int getPropertyIndex(String propertyName) {
        Integer index = getPropertyIndexOrNull(propertyName);
        if (index == null) {
            throw new HibernateException("Unable to resolve property: " + propertyName);
        }
        return index;
    }

    public Integer getPropertyIndexOrNull(String propertyName) {
        return propertyIndexes.get(propertyName);
    }

    public boolean hasCollections() {
        return hasCollections;
    }

    public boolean hasMutableProperties() {
        return hasMutableProperties;
    }

    public boolean hasNonIdentifierPropertyNamedId() {
        return hasNonIdentifierPropertyNamedId;
    }

    public boolean hasLazyProperties() {
        return hasLazyProperties;
    }

    public boolean hasCascades() {
        return hasCascades;
    }

    public boolean isMutable() {
        return mutable;
    }

    public boolean isSelectBeforeUpdate() {
        return selectBeforeUpdate;
    }

    public boolean isDynamicUpdate() {
        return dynamicUpdate;
    }

    public boolean isDynamicInsert() {
        return dynamicInsert;
    }

    public OptimisticLockStyle getOptimisticLockStyle() {
        return optimisticLockStyle;
    }

    public boolean isPolymorphic() {
        return polymorphic;
    }

    public String getSuperclass() {
        return superclass;
    }

    public boolean isExplicitPolymorphism() {
        return explicitPolymorphism;
    }

    public boolean isInherited() {
        return inherited;
    }

    public boolean hasSubclasses() {
        return hasSubclasses;
    }

    public boolean isLazy() {
        return lazy;
    }

    public void setLazy(boolean lazy) {
        this.lazy = lazy;
    }

    public boolean isVersioned() {
        return versioned;
    }

    public boolean isAbstract() {
        return isAbstract;
    }

    /**
     * Return the entity-name mapped to the given class within our inheritance hierarchy, if any.
     *
     * @param inheritenceClass The class for which to resolve the entity-name.
     * @return The mapped entity-name, or null if no such mapping was found.
     */
    public String findEntityNameByEntityClass(Class inheritenceClass) {
        return (String) entityNameByInheritenceClassMap.get(inheritenceClass);
    }

    @Override
    public String toString() {
        return "EntityMetamodel(" + name + ':' + ArrayHelper.toString(properties) + ')';
    }

    // temporary ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    public String[] getPropertyNames() {
        return propertyNames;
    }

    public Type[] getPropertyTypes() {
        return propertyTypes;
    }

    public boolean[] getPropertyLaziness() {
        return propertyLaziness;
    }

    public boolean[] getPropertyUpdateability() {
        return propertyUpdateability;
    }

    public boolean[] getPropertyCheckability() {
        return propertyCheckability;
    }

    public boolean[] getNonlazyPropertyUpdateability() {
        return nonlazyPropertyUpdateability;
    }

    public boolean[] getPropertyInsertability() {
        return propertyInsertability;
    }

    public boolean[] getPropertyNullability() {
        return propertyNullability;
    }

    public boolean[] getPropertyVersionability() {
        return propertyVersionability;
    }

    public CascadeStyle[] getCascadeStyles() {
        return cascadeStyles;
    }

    public boolean hasPreInsertGeneratedValues() {
        return hasPreInsertGeneratedValues;
    }

    public boolean hasPreUpdateGeneratedValues() {
        return hasPreUpdateGeneratedValues;
    }

    public boolean hasInsertGeneratedValues() {
        return hasInsertGeneratedValues;
    }

    public boolean hasUpdateGeneratedValues() {
        return hasUpdateGeneratedValues;
    }

    public InMemoryValueGenerationStrategy[] getInMemoryValueGenerationStrategies() {
        return inMemoryValueGenerationStrategies;
    }

    public InDatabaseValueGenerationStrategy[] getInDatabaseValueGenerationStrategies() {
        return inDatabaseValueGenerationStrategies;
    }

    public EntityMode getEntityMode() {
        return entityMode;
    }

    /**
     * Whether or not this class can be lazy (ie intercepted)
     */
    public boolean isInstrumented() {
        return bytecodeEnhancementMetadata.isEnhancedForLazyLoading();
    }

    public BytecodeEnhancementMetadata getBytecodeEnhancementMetadata() {
        return bytecodeEnhancementMetadata;
    }
}