org.hibernate.type.ComponentType.java Source code

Java tutorial

Introduction

Here is the source code for org.hibernate.type.ComponentType.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.type;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;

import org.hibernate.EntityMode;
import org.hibernate.FetchMode;
import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.PropertyNotFoundException;
import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer;
import org.hibernate.engine.jdbc.Size;
import org.hibernate.engine.spi.CascadeStyle;
import org.hibernate.engine.spi.Mapping;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.tuple.StandardProperty;
import org.hibernate.tuple.ValueGeneration;
import org.hibernate.tuple.component.ComponentMetamodel;
import org.hibernate.tuple.component.ComponentTuplizer;

/**
 * Handles "component" mappings
 *
 * @author Gavin King
 */
public class ComponentType extends AbstractType implements CompositeType, ProcedureParameterExtractionAware {

    private final String[] propertyNames;
    private final Type[] propertyTypes;
    private final ValueGeneration[] propertyValueGenerationStrategies;
    private final boolean[] propertyNullability;
    protected final int propertySpan;
    private final CascadeStyle[] cascade;
    private final FetchMode[] joinedFetch;
    private final boolean isKey;
    private boolean hasNotNullProperty;
    private final boolean createEmptyCompositesEnabled;

    protected final EntityMode entityMode;
    protected final ComponentTuplizer componentTuplizer;

    /**
     * @deprecated Use the other contructor
     */
    @Deprecated
    public ComponentType(TypeFactory.TypeScope typeScope, ComponentMetamodel metamodel) {
        this(metamodel);
    }

    public ComponentType(ComponentMetamodel metamodel) {
        // for now, just "re-flatten" the metamodel since this is temporary stuff anyway (HHH-1907)
        this.isKey = metamodel.isKey();
        this.propertySpan = metamodel.getPropertySpan();
        this.propertyNames = new String[propertySpan];
        this.propertyTypes = new Type[propertySpan];
        this.propertyValueGenerationStrategies = new ValueGeneration[propertySpan];
        this.propertyNullability = new boolean[propertySpan];
        this.cascade = new CascadeStyle[propertySpan];
        this.joinedFetch = new FetchMode[propertySpan];

        for (int i = 0; i < propertySpan; i++) {
            StandardProperty prop = metamodel.getProperty(i);
            this.propertyNames[i] = prop.getName();
            this.propertyTypes[i] = prop.getType();
            this.propertyNullability[i] = prop.isNullable();
            this.cascade[i] = prop.getCascadeStyle();
            this.joinedFetch[i] = prop.getFetchMode();
            if (!prop.isNullable()) {
                hasNotNullProperty = true;
            }
            this.propertyValueGenerationStrategies[i] = prop.getValueGenerationStrategy();
        }

        this.entityMode = metamodel.getEntityMode();
        this.componentTuplizer = metamodel.getComponentTuplizer();
        this.createEmptyCompositesEnabled = metamodel.isCreateEmptyCompositesEnabled();
    }

    public boolean isKey() {
        return isKey;
    }

    public EntityMode getEntityMode() {
        return entityMode;
    }

    public ComponentTuplizer getComponentTuplizer() {
        return componentTuplizer;
    }

    @Override
    public int getColumnSpan(Mapping mapping) throws MappingException {
        int span = 0;
        for (int i = 0; i < propertySpan; i++) {
            span += propertyTypes[i].getColumnSpan(mapping);
        }
        return span;
    }

    @Override
    public int[] sqlTypes(Mapping mapping) throws MappingException {
        //Not called at runtime so doesn't matter if its slow :)
        int[] sqlTypes = new int[getColumnSpan(mapping)];
        int n = 0;
        for (int i = 0; i < propertySpan; i++) {
            int[] subtypes = propertyTypes[i].sqlTypes(mapping);
            for (int subtype : subtypes) {
                sqlTypes[n++] = subtype;
            }
        }
        return sqlTypes;
    }

    @Override
    public Size[] dictatedSizes(Mapping mapping) throws MappingException {
        //Not called at runtime so doesn't matter if its slow :)
        final Size[] sizes = new Size[getColumnSpan(mapping)];
        int soFar = 0;
        for (Type propertyType : propertyTypes) {
            final Size[] propertySizes = propertyType.dictatedSizes(mapping);
            System.arraycopy(propertySizes, 0, sizes, soFar, propertySizes.length);
            soFar += propertySizes.length;
        }
        return sizes;
    }

    @Override
    public Size[] defaultSizes(Mapping mapping) throws MappingException {
        //Not called at runtime so doesn't matter if its slow :)
        final Size[] sizes = new Size[getColumnSpan(mapping)];
        int soFar = 0;
        for (Type propertyType : propertyTypes) {
            final Size[] propertySizes = propertyType.defaultSizes(mapping);
            System.arraycopy(propertySizes, 0, sizes, soFar, propertySizes.length);
            soFar += propertySizes.length;
        }
        return sizes;
    }

    @Override
    public final boolean isComponentType() {
        return true;
    }

    public Class getReturnedClass() {
        return componentTuplizer.getMappedClass();
    }

    @Override
    public boolean isSame(Object x, Object y) throws HibernateException {
        if (x == y) {
            return true;
        }
        // null value and empty component are considered equivalent
        Object[] xvalues = getPropertyValues(x, entityMode);
        Object[] yvalues = getPropertyValues(y, entityMode);
        for (int i = 0; i < propertySpan; i++) {
            if (!propertyTypes[i].isSame(xvalues[i], yvalues[i])) {
                return false;
            }
        }
        return true;
    }

    @Override
    public boolean isEqual(final Object x, final Object y) throws HibernateException {
        if (x == y) {
            return true;
        }
        // null value and empty component are considered equivalent
        for (int i = 0; i < propertySpan; i++) {
            if (!propertyTypes[i].isEqual(getPropertyValue(x, i), getPropertyValue(y, i))) {
                return false;
            }
        }
        return true;
    }

    @Override
    public boolean isEqual(final Object x, final Object y, final SessionFactoryImplementor factory)
            throws HibernateException {
        if (x == y) {
            return true;
        }
        // null value and empty component are considered equivalent
        for (int i = 0; i < propertySpan; i++) {
            if (!propertyTypes[i].isEqual(getPropertyValue(x, i), getPropertyValue(y, i), factory)) {
                return false;
            }
        }
        return true;
    }

    @Override
    public int compare(final Object x, final Object y) {
        if (x == y) {
            return 0;
        }
        for (int i = 0; i < propertySpan; i++) {
            int propertyCompare = propertyTypes[i].compare(getPropertyValue(x, i), getPropertyValue(y, i));
            if (propertyCompare != 0) {
                return propertyCompare;
            }
        }
        return 0;
    }

    public boolean isMethodOf(Method method) {
        return false;
    }

    @Override
    public int getHashCode(final Object x) {
        int result = 17;
        for (int i = 0; i < propertySpan; i++) {
            Object y = getPropertyValue(x, i);
            result *= 37;
            if (y != null) {
                result += propertyTypes[i].getHashCode(y);
            }
        }
        return result;
    }

    @Override
    public int getHashCode(final Object x, final SessionFactoryImplementor factory) {
        int result = 17;
        for (int i = 0; i < propertySpan; i++) {
            Object y = getPropertyValue(x, i);
            result *= 37;
            if (y != null) {
                result += propertyTypes[i].getHashCode(y, factory);
            }
        }
        return result;
    }

    @Override
    public boolean isDirty(final Object x, final Object y, final SharedSessionContractImplementor session)
            throws HibernateException {
        if (x == y) {
            return false;
        }
        // null value and empty component are considered equivalent
        for (int i = 0; i < propertySpan; i++) {
            if (propertyTypes[i].isDirty(getPropertyValue(x, i), getPropertyValue(y, i), session)) {
                return true;
            }
        }
        return false;
    }

    public boolean isDirty(final Object x, final Object y, final boolean[] checkable,
            final SharedSessionContractImplementor session) throws HibernateException {
        if (x == y) {
            return false;
        }
        // null value and empty component are considered equivalent
        int loc = 0;
        for (int i = 0; i < propertySpan; i++) {
            int len = propertyTypes[i].getColumnSpan(session.getFactory());
            if (len <= 1) {
                final boolean dirty = (len == 0 || checkable[loc])
                        && propertyTypes[i].isDirty(getPropertyValue(x, i), getPropertyValue(y, i), session);
                if (dirty) {
                    return true;
                }
            } else {
                boolean[] subcheckable = new boolean[len];
                System.arraycopy(checkable, loc, subcheckable, 0, len);
                final boolean dirty = propertyTypes[i].isDirty(getPropertyValue(x, i), getPropertyValue(y, i),
                        subcheckable, session);
                if (dirty) {
                    return true;
                }
            }
            loc += len;
        }
        return false;
    }

    @Override
    public boolean isModified(final Object old, final Object current, final boolean[] checkable,
            final SharedSessionContractImplementor session) throws HibernateException {
        if (old == current) {
            return false;
        }
        // null value and empty components are considered equivalent
        int loc = 0;
        for (int i = 0; i < propertySpan; i++) {
            int len = propertyTypes[i].getColumnSpan(session.getFactory());
            boolean[] subcheckable = new boolean[len];
            System.arraycopy(checkable, loc, subcheckable, 0, len);
            if (propertyTypes[i].isModified(getPropertyValue(old, i), getPropertyValue(current, i), subcheckable,
                    session)) {
                return true;
            }
            loc += len;
        }
        return false;

    }

    @Override
    public Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner)
            throws HibernateException, SQLException {
        return resolve(hydrate(rs, names, session, owner), session, owner);
    }

    @Override
    public void nullSafeSet(PreparedStatement st, Object value, int begin, SharedSessionContractImplementor session)
            throws HibernateException, SQLException {

        Object[] subvalues = nullSafeGetValues(value, entityMode);

        for (int i = 0; i < propertySpan; i++) {
            propertyTypes[i].nullSafeSet(st, subvalues[i], begin, session);
            begin += propertyTypes[i].getColumnSpan(session.getFactory());
        }
    }

    @Override
    public void nullSafeSet(PreparedStatement st, Object value, int begin, boolean[] settable,
            SharedSessionContractImplementor session) throws HibernateException, SQLException {

        Object[] subvalues = nullSafeGetValues(value, entityMode);

        int loc = 0;
        for (int i = 0; i < propertySpan; i++) {
            int len = propertyTypes[i].getColumnSpan(session.getFactory());
            //noinspection StatementWithEmptyBody
            if (len == 0) {
                //noop
            } else if (len == 1) {
                if (settable[loc]) {
                    propertyTypes[i].nullSafeSet(st, subvalues[i], begin, session);
                    begin++;
                }
            } else {
                boolean[] subsettable = new boolean[len];
                System.arraycopy(settable, loc, subsettable, 0, len);
                propertyTypes[i].nullSafeSet(st, subvalues[i], begin, subsettable, session);
                begin += ArrayHelper.countTrue(subsettable);
            }
            loc += len;
        }
    }

    private Object[] nullSafeGetValues(Object value, EntityMode entityMode) throws HibernateException {
        if (value == null) {
            return new Object[propertySpan];
        } else {
            return getPropertyValues(value, entityMode);
        }
    }

    @Override
    public Object nullSafeGet(ResultSet rs, String name, SharedSessionContractImplementor session, Object owner)
            throws HibernateException, SQLException {

        return nullSafeGet(rs, new String[] { name }, session, owner);
    }

    @Override
    public Object getPropertyValue(Object component, int i, SharedSessionContractImplementor session)
            throws HibernateException {
        return getPropertyValue(component, i);
    }

    public Object getPropertyValue(Object component, int i, EntityMode entityMode) throws HibernateException {
        return getPropertyValue(component, i);
    }

    public Object getPropertyValue(Object component, int i) throws HibernateException {
        if (component == null) {
            component = new Object[propertySpan];
        }
        if (component instanceof Object[]) {
            // A few calls to hashCode pass the property values already in an
            // Object[] (ex: QueryKey hash codes for cached queries).
            // It's easiest to just check for the condition here prior to
            // trying reflection.
            return ((Object[]) component)[i];
        } else {
            return componentTuplizer.getPropertyValue(component, i);
        }
    }

    @Override
    public Object[] getPropertyValues(Object component, SharedSessionContractImplementor session)
            throws HibernateException {
        return getPropertyValues(component, entityMode);
    }

    @Override
    public Object[] getPropertyValues(Object component, EntityMode entityMode) throws HibernateException {
        if (component == null) {
            component = new Object[propertySpan];
        }
        if (component instanceof Object[]) {
            // A few calls to hashCode pass the property values already in an 
            // Object[] (ex: QueryKey hash codes for cached queries).
            // It's easiest to just check for the condition here prior to
            // trying reflection.
            return (Object[]) component;
        } else {
            return componentTuplizer.getPropertyValues(component);
        }
    }

    @Override
    public void setPropertyValues(Object component, Object[] values, EntityMode entityMode)
            throws HibernateException {
        componentTuplizer.setPropertyValues(component, values);
    }

    @Override
    public Type[] getSubtypes() {
        return propertyTypes;
    }

    public ValueGeneration[] getPropertyValueGenerationStrategies() {
        return propertyValueGenerationStrategies;
    }

    @Override
    public String getName() {
        return "component" + ArrayHelper.toString(propertyNames);
    }

    @Override
    public String toLoggableString(Object value, SessionFactoryImplementor factory) throws HibernateException {
        if (value == null) {
            return "null";
        }

        if (entityMode == null) {
            throw new ClassCastException(value.getClass().getName());
        }
        Map<String, String> result = new HashMap<>();
        Object[] values = getPropertyValues(value, entityMode);
        for (int i = 0; i < propertyTypes.length; i++) {
            if (values[i] == LazyPropertyInitializer.UNFETCHED_PROPERTY) {
                result.put(propertyNames[i], "<uninitialized>");
            } else {
                result.put(propertyNames[i], propertyTypes[i].toLoggableString(values[i], factory));
            }
        }
        return StringHelper.unqualify(getName()) + result.toString();
    }

    @Override
    public String[] getPropertyNames() {
        return propertyNames;
    }

    @Override
    public Object deepCopy(Object component, SessionFactoryImplementor factory) throws HibernateException {
        if (component == null) {
            return null;
        }

        Object[] values = getPropertyValues(component, entityMode);
        for (int i = 0; i < propertySpan; i++) {
            values[i] = propertyTypes[i].deepCopy(values[i], factory);
        }

        Object result = instantiate(entityMode);
        setPropertyValues(result, values, entityMode);

        //not absolutely necessary, but helps for some
        //equals()/hashCode() implementations
        if (componentTuplizer.hasParentProperty()) {
            componentTuplizer.setParent(result, componentTuplizer.getParent(component), factory);
        }

        return result;
    }

    @Override
    public Object replace(Object original, Object target, SharedSessionContractImplementor session, Object owner,
            Map copyCache) throws HibernateException {

        if (original == null) {
            return null;
        }
        //if ( original == target ) return target;

        final Object result = target == null ? instantiate(owner, session) : target;

        Object[] values = TypeHelper.replace(getPropertyValues(original, entityMode),
                getPropertyValues(result, entityMode), propertyTypes, session, owner, copyCache);

        setPropertyValues(result, values, entityMode);
        return result;
    }

    @Override
    public Object replace(Object original, Object target, SharedSessionContractImplementor session, Object owner,
            Map copyCache, ForeignKeyDirection foreignKeyDirection) throws HibernateException {

        if (original == null) {
            return null;
        }
        //if ( original == target ) return target;

        final Object result = target == null ? instantiate(owner, session) : target;

        Object[] values = TypeHelper.replace(getPropertyValues(original, entityMode),
                getPropertyValues(result, entityMode), propertyTypes, session, owner, copyCache,
                foreignKeyDirection);

        setPropertyValues(result, values, entityMode);
        return result;
    }

    /**
     * This method does not populate the component parent
     */
    public Object instantiate(EntityMode entityMode) throws HibernateException {
        return componentTuplizer.instantiate();
    }

    public Object instantiate(Object parent, SharedSessionContractImplementor session) throws HibernateException {

        Object result = instantiate(entityMode);

        if (componentTuplizer.hasParentProperty() && parent != null) {
            componentTuplizer.setParent(result, session.getPersistenceContextInternal().proxyFor(parent),
                    session.getFactory());
        }

        return result;
    }

    @Override
    public CascadeStyle getCascadeStyle(int i) {
        return cascade[i];
    }

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

    @Override
    public Serializable disassemble(Object value, SharedSessionContractImplementor session, Object owner)
            throws HibernateException {

        if (value == null) {
            return null;
        } else {
            Object[] values = getPropertyValues(value, entityMode);
            for (int i = 0; i < propertyTypes.length; i++) {
                values[i] = propertyTypes[i].disassemble(values[i], session, owner);
            }
            return values;
        }
    }

    @Override
    public Object assemble(Serializable object, SharedSessionContractImplementor session, Object owner)
            throws HibernateException {

        if (object == null) {
            return null;
        } else {
            Object[] values = (Object[]) object;
            Object[] assembled = new Object[values.length];
            for (int i = 0; i < propertyTypes.length; i++) {
                assembled[i] = propertyTypes[i].assemble((Serializable) values[i], session, owner);
            }
            Object result = instantiate(owner, session);
            setPropertyValues(result, assembled, entityMode);
            return result;
        }
    }

    @Override
    public FetchMode getFetchMode(int i) {
        return joinedFetch[i];
    }

    @Override
    public Object hydrate(final ResultSet rs, final String[] names, final SharedSessionContractImplementor session,
            final Object owner) throws HibernateException, SQLException {

        int begin = 0;
        boolean notNull = false;
        Object[] values = new Object[propertySpan];
        for (int i = 0; i < propertySpan; i++) {
            int length = propertyTypes[i].getColumnSpan(session.getFactory());
            String[] range = ArrayHelper.slice(names, begin, length); //cache this
            Object val = propertyTypes[i].hydrate(rs, range, session, owner);
            if (val == null) {
                if (isKey) {
                    return null; //different nullability rules for pk/fk
                }
            } else {
                notNull = true;
            }
            values[i] = val;
            begin += length;
        }

        return notNull ? values : null;
    }

    @Override
    public Object resolve(Object value, SharedSessionContractImplementor session, Object owner)
            throws HibernateException {

        if (value != null) {
            Object result = instantiate(owner, session);
            Object[] values = (Object[]) value;
            Object[] resolvedValues = new Object[values.length]; //only really need new array during semiresolve!
            for (int i = 0; i < values.length; i++) {
                resolvedValues[i] = propertyTypes[i].resolve(values[i], session, owner);
            }
            setPropertyValues(result, resolvedValues, entityMode);
            return result;
        } else if (isCreateEmptyCompositesEnabled()) {
            return instantiate(owner, session);
        } else {
            return null;
        }
    }

    @Override
    public Object semiResolve(Object value, SharedSessionContractImplementor session, Object owner)
            throws HibernateException {
        //note that this implementation is kinda broken
        //for components with many-to-one associations
        return resolve(value, session, owner);
    }

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

    @Override
    public boolean[] toColumnNullness(Object value, Mapping mapping) {
        boolean[] result = new boolean[getColumnSpan(mapping)];
        if (value == null) {
            return result;
        }
        Object[] values = getPropertyValues(value, EntityMode.POJO); //TODO!!!!!!!
        int loc = 0;
        for (int i = 0; i < propertyTypes.length; i++) {
            boolean[] propertyNullness = propertyTypes[i].toColumnNullness(values[i], mapping);
            System.arraycopy(propertyNullness, 0, result, loc, propertyNullness.length);
            loc += propertyNullness.length;
        }
        return result;
    }

    @Override
    public boolean isEmbedded() {
        return false;
    }

    @Override
    public int getPropertyIndex(String name) {
        String[] names = getPropertyNames();
        for (int i = 0, max = names.length; i < max; i++) {
            if (names[i].equals(name)) {
                return i;
            }
        }
        throw new PropertyNotFoundException(
                "Unable to locate property named " + name + " on " + getReturnedClass().getName());
    }

    private Boolean canDoExtraction;

    @Override
    public boolean canDoExtraction() {
        if (canDoExtraction == null) {
            canDoExtraction = determineIfProcedureParamExtractionCanBePerformed();
        }
        return canDoExtraction;
    }

    private boolean determineIfProcedureParamExtractionCanBePerformed() {
        for (Type propertyType : propertyTypes) {
            if (!ProcedureParameterExtractionAware.class.isInstance(propertyType)) {
                return false;
            }
            if (!((ProcedureParameterExtractionAware) propertyType).canDoExtraction()) {
                return false;
            }
        }
        return true;
    }

    @Override
    public Object extract(CallableStatement statement, int startIndex, SharedSessionContractImplementor session)
            throws SQLException {
        Object[] values = new Object[propertySpan];

        int currentIndex = startIndex;
        boolean notNull = false;
        for (int i = 0; i < propertySpan; i++) {
            // we know this cast is safe from canDoExtraction
            final Type propertyType = propertyTypes[i];
            final Object value = ((ProcedureParameterExtractionAware) propertyType).extract(statement, currentIndex,
                    session);
            if (value == null) {
                if (isKey) {
                    return null; //different nullability rules for pk/fk
                }
            } else {
                notNull = true;
            }
            values[i] = value;
            currentIndex += propertyType.getColumnSpan(session.getFactory());
        }

        if (!notNull) {
            values = null;
        }

        return resolve(values, session, null);
    }

    @Override
    public Object extract(CallableStatement statement, String[] paramNames,
            SharedSessionContractImplementor session) throws SQLException {
        // for this form to work all sub-property spans must be one (1)...

        Object[] values = new Object[propertySpan];

        int indx = 0;
        boolean notNull = false;
        for (String paramName : paramNames) {
            // we know this cast is safe from canDoExtraction
            final ProcedureParameterExtractionAware propertyType = (ProcedureParameterExtractionAware) propertyTypes[indx];
            final Object value = propertyType.extract(statement, new String[] { paramName }, session);
            if (value == null) {
                if (isKey) {
                    return null; //different nullability rules for pk/fk
                }
            } else {
                notNull = true;
            }
            values[indx] = value;
        }

        if (!notNull) {
            values = null;
        }

        return resolve(values, session, null);
    }

    @Override
    public boolean hasNotNullProperty() {
        return hasNotNullProperty;
    }

    private boolean isCreateEmptyCompositesEnabled() {
        return createEmptyCompositesEnabled;
    }
}