org.hibernate.collection.internal.AbstractPersistentCollection.java Source code

Java tutorial

Introduction

Here is the source code for org.hibernate.collection.internal.AbstractPersistentCollection.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.collection.internal;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;

import org.hibernate.AssertionFailure;
import org.hibernate.FlushMode;
import org.hibernate.HibernateException;
import org.hibernate.LazyInitializationException;
import org.hibernate.Session;
import org.hibernate.collection.spi.PersistentCollection;
import org.hibernate.engine.internal.ForeignKeys;
import org.hibernate.engine.spi.CollectionEntry;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.engine.spi.Status;
import org.hibernate.engine.spi.TypedValue;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.SessionFactoryRegistry;
import org.hibernate.internal.util.MarkerObject;
import org.hibernate.internal.util.collections.IdentitySet;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.pretty.MessageHelper;
import org.hibernate.resource.transaction.spi.TransactionStatus;
import org.hibernate.type.CompositeType;
import org.hibernate.type.IntegerType;
import org.hibernate.type.LongType;
import org.hibernate.type.PostgresUUIDType;
import org.hibernate.type.StringType;
import org.hibernate.type.Type;
import org.hibernate.type.UUIDBinaryType;
import org.hibernate.type.UUIDCharType;

/**
 * Base class implementing {@link org.hibernate.collection.spi.PersistentCollection}
 *
 * @author Gavin King
 */
public abstract class AbstractPersistentCollection implements Serializable, PersistentCollection {
    private static final CoreMessageLogger LOG = CoreLogging.messageLogger(AbstractPersistentCollection.class);

    private transient SharedSessionContractImplementor session;
    private boolean isTempSession = false;
    private boolean initialized;
    private transient List<DelayedOperation> operationQueue;
    private transient boolean directlyAccessible;
    private transient boolean initializing;
    private Object owner;
    private int cachedSize = -1;

    private String role;
    private Serializable key;
    // collections detect changes made via their public interface and mark
    // themselves as dirty as a performance optimization
    private boolean dirty;
    protected boolean elementRemoved;
    private Serializable storedSnapshot;

    private String sessionFactoryUuid;
    private boolean allowLoadOutsideTransaction;

    /**
     * Not called by Hibernate, but used by non-JDK serialization,
     * eg. SOAP libraries.
     */
    public AbstractPersistentCollection() {
    }

    protected AbstractPersistentCollection(SharedSessionContractImplementor session) {
        this.session = session;
    }

    /**
     *  * @deprecated {@link #AbstractPersistentCollection(SharedSessionContractImplementor)} should be used instead.
     */
    @Deprecated
    protected AbstractPersistentCollection(SessionImplementor session) {
        this((SharedSessionContractImplementor) session);
    }

    @Override
    public final String getRole() {
        return role;
    }

    @Override
    public final Serializable getKey() {
        return key;
    }

    @Override
    public final boolean isUnreferenced() {
        return role == null;
    }

    @Override
    public final boolean isDirty() {
        return dirty;
    }

    @Override
    public boolean isElementRemoved() {
        return elementRemoved;
    }

    @Override
    public final void clearDirty() {
        dirty = false;
        elementRemoved = false;
    }

    @Override
    public final void dirty() {
        dirty = true;
    }

    @Override
    public final Serializable getStoredSnapshot() {
        return storedSnapshot;
    }

    //Careful: these methods do not initialize the collection.

    @Override
    public abstract boolean empty();

    /**
     * Called by any read-only method of the collection interface
     */
    protected final void read() {
        initialize(false);
    }

    /**
     * Called by the {@link Collection#size} method
     */
    @SuppressWarnings({ "JavaDoc" })
    protected boolean readSize() {
        if (!initialized) {
            if (cachedSize != -1 && !hasQueuedOperations()) {
                return true;
            } else {
                final boolean isExtraLazy = withTemporarySessionIfNeeded(new LazyInitializationWork<Boolean>() {
                    @Override
                    public Boolean doWork() {
                        final CollectionEntry entry = session.getPersistenceContextInternal()
                                .getCollectionEntry(AbstractPersistentCollection.this);

                        if (entry != null) {
                            final CollectionPersister persister = entry.getLoadedPersister();
                            if (persister.isExtraLazy()) {
                                if (hasQueuedOperations()) {
                                    session.flush();
                                }
                                cachedSize = persister.getSize(entry.getLoadedKey(), session);
                                return true;
                            } else {
                                read();
                            }
                        } else {
                            throwLazyInitializationExceptionIfNotConnected();
                        }
                        return false;
                    }
                });
                if (isExtraLazy) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * TBH not sure why this is public
     *
     * @param <T> The java type of the return for this LazyInitializationWork
     */
    public static interface LazyInitializationWork<T> {
        /**
         * Do the represented work and return the result.
         *
         * @return The result
         */
        public T doWork();
    }

    private <T> T withTemporarySessionIfNeeded(LazyInitializationWork<T> lazyInitializationWork) {
        SharedSessionContractImplementor tempSession = null;

        if (session == null) {
            if (allowLoadOutsideTransaction) {
                tempSession = openTemporarySessionForLoading();
            } else {
                throwLazyInitializationException("could not initialize proxy - no Session");
            }
        } else if (!session.isOpenOrWaitingForAutoClose()) {
            if (allowLoadOutsideTransaction) {
                tempSession = openTemporarySessionForLoading();
            } else {
                throwLazyInitializationException("could not initialize proxy - the owning Session was closed");
            }
        } else if (!session.isConnected()) {
            if (allowLoadOutsideTransaction) {
                tempSession = openTemporarySessionForLoading();
            } else {
                throwLazyInitializationException("could not initialize proxy - the owning Session is disconnected");
            }
        }

        SharedSessionContractImplementor originalSession = null;
        boolean isJTA = false;

        if (tempSession != null) {
            isTempSession = true;
            originalSession = session;
            session = tempSession;

            isJTA = session.getTransactionCoordinator().getTransactionCoordinatorBuilder().isJta();

            if (!isJTA) {
                // Explicitly handle the transactions only if we're not in
                // a JTA environment.  A lazy loading temporary session can
                // be created even if a current session and transaction are
                // open (ex: session.clear() was used).  We must prevent
                // multiple transactions.
                ((Session) session).beginTransaction();
            }

            session.getPersistenceContextInternal().addUninitializedDetachedCollection(
                    session.getFactory().getCollectionPersister(getRole()), this);
        }

        try {
            return lazyInitializationWork.doWork();
        } finally {
            if (tempSession != null) {
                // make sure the just opened temp session gets closed!
                isTempSession = false;
                session = originalSession;

                try {
                    if (!isJTA) {
                        ((Session) tempSession).getTransaction().commit();
                    }
                    ((Session) tempSession).close();
                } catch (Exception e) {
                    LOG.warn(
                            "Unable to close temporary session used to load lazy collection associated to no session");
                }
            }
        }
    }

    private SharedSessionContractImplementor openTemporarySessionForLoading() {
        if (sessionFactoryUuid == null) {
            throwLazyInitializationException(
                    "SessionFactory UUID not known to create temporary Session for loading");
        }

        final SessionFactoryImplementor sf = (SessionFactoryImplementor) SessionFactoryRegistry.INSTANCE
                .getSessionFactory(sessionFactoryUuid);
        final SharedSessionContractImplementor session = (SharedSessionContractImplementor) sf.openSession();
        session.getPersistenceContextInternal().setDefaultReadOnly(true);
        session.setFlushMode(FlushMode.MANUAL);
        return session;
    }

    protected Boolean readIndexExistence(final Object index) {
        if (!initialized) {
            final Boolean extraLazyExistenceCheck = withTemporarySessionIfNeeded(
                    new LazyInitializationWork<Boolean>() {
                        @Override
                        public Boolean doWork() {
                            final CollectionEntry entry = session.getPersistenceContextInternal()
                                    .getCollectionEntry(AbstractPersistentCollection.this);
                            final CollectionPersister persister = entry.getLoadedPersister();
                            if (persister.isExtraLazy()) {
                                if (hasQueuedOperations()) {
                                    session.flush();
                                }
                                return persister.indexExists(entry.getLoadedKey(), index, session);
                            } else {
                                read();
                            }
                            return null;
                        }
                    });
            if (extraLazyExistenceCheck != null) {
                return extraLazyExistenceCheck;
            }
        }
        return null;
    }

    protected Boolean readElementExistence(final Object element) {
        if (!initialized) {
            final Boolean extraLazyExistenceCheck = withTemporarySessionIfNeeded(
                    new LazyInitializationWork<Boolean>() {
                        @Override
                        public Boolean doWork() {
                            final CollectionEntry entry = session.getPersistenceContextInternal()
                                    .getCollectionEntry(AbstractPersistentCollection.this);
                            final CollectionPersister persister = entry.getLoadedPersister();
                            if (persister.isExtraLazy()) {
                                if (hasQueuedOperations()) {
                                    session.flush();
                                }
                                return persister.elementExists(entry.getLoadedKey(), element, session);
                            } else {
                                read();
                            }
                            return null;
                        }
                    });
            if (extraLazyExistenceCheck != null) {
                return extraLazyExistenceCheck;
            }
        }
        return null;
    }

    protected static final Object UNKNOWN = new MarkerObject("UNKNOWN");

    protected Object readElementByIndex(final Object index) {
        if (!initialized) {
            class ExtraLazyElementByIndexReader implements LazyInitializationWork {
                private boolean isExtraLazy;
                private Object element;

                @Override
                public Object doWork() {
                    final CollectionEntry entry = session.getPersistenceContextInternal()
                            .getCollectionEntry(AbstractPersistentCollection.this);
                    final CollectionPersister persister = entry.getLoadedPersister();
                    isExtraLazy = persister.isExtraLazy();
                    if (isExtraLazy) {
                        if (hasQueuedOperations()) {
                            session.flush();
                        }
                        element = persister.getElementByIndex(entry.getLoadedKey(), index, session, owner);
                    } else {
                        read();
                    }
                    return null;
                }
            }

            final ExtraLazyElementByIndexReader reader = new ExtraLazyElementByIndexReader();
            //noinspection unchecked
            withTemporarySessionIfNeeded(reader);
            if (reader.isExtraLazy) {
                return reader.element;
            }
        }
        return UNKNOWN;

    }

    protected int getCachedSize() {
        return cachedSize;
    }

    protected boolean isConnectedToSession() {
        return session != null && session.isOpen()
                && session.getPersistenceContextInternal().containsCollection(this);
    }

    protected boolean isInitialized() {
        return initialized;
    }

    /**
     * Called by any writer method of the collection interface
     */
    protected final void write() {
        initialize(true);
        dirty();
    }

    /**
     * Is this collection in a state that would allow us to
     * "queue" operations?
     */
    @SuppressWarnings({ "JavaDoc" })
    protected boolean isOperationQueueEnabled() {
        return !initialized && isConnectedToSession() && isInverseCollection();
    }

    /**
     * Is this collection in a state that would allow us to
     * "queue" puts? This is a special case, because of orphan
     * delete.
     */
    @SuppressWarnings({ "JavaDoc" })
    protected boolean isPutQueueEnabled() {
        return !initialized && isConnectedToSession() && isInverseOneToManyOrNoOrphanDelete();
    }

    /**
     * Is this collection in a state that would allow us to
     * "queue" clear? This is a special case, because of orphan
     * delete.
     */
    @SuppressWarnings({ "JavaDoc" })
    protected boolean isClearQueueEnabled() {
        return !initialized && isConnectedToSession() && isInverseCollectionNoOrphanDelete();
    }

    /**
     * Is this the "inverse" end of a bidirectional association?
     */
    @SuppressWarnings({ "JavaDoc" })
    protected boolean isInverseCollection() {
        final CollectionEntry ce = session.getPersistenceContextInternal().getCollectionEntry(this);
        return ce != null && ce.getLoadedPersister().isInverse();
    }

    /**
     * Is this the "inverse" end of a bidirectional association with
     * no orphan delete enabled?
     */
    @SuppressWarnings({ "JavaDoc" })
    protected boolean isInverseCollectionNoOrphanDelete() {
        final CollectionEntry ce = session.getPersistenceContextInternal().getCollectionEntry(this);
        if (ce == null) {
            return false;
        }
        final CollectionPersister loadedPersister = ce.getLoadedPersister();
        return loadedPersister.isInverse() && !loadedPersister.hasOrphanDelete();
    }

    /**
     * Is this the "inverse" end of a bidirectional one-to-many, or
     * of a collection with no orphan delete?
     */
    @SuppressWarnings({ "JavaDoc" })
    protected boolean isInverseOneToManyOrNoOrphanDelete() {
        final CollectionEntry ce = session.getPersistenceContextInternal().getCollectionEntry(this);
        if (ce == null) {
            return false;
        }
        final CollectionPersister loadedPersister = ce.getLoadedPersister();
        return loadedPersister.isInverse() && (loadedPersister.isOneToMany() || !loadedPersister.hasOrphanDelete());
    }

    /**
     * Queue an addition
     */
    @SuppressWarnings({ "JavaDoc" })
    protected final void queueOperation(DelayedOperation operation) {
        if (operationQueue == null) {
            operationQueue = new ArrayList<DelayedOperation>(10);
        }
        operationQueue.add(operation);
        //needed so that we remove this collection from the second-level cache
        dirty = true;
    }

    /**
     * Replace entity instances with copy in {@code copyCache}/.
     *
     * @param copyCache - mapping from entity in the process of being
     *                    merged to managed copy.
     */
    public final void replaceQueuedOperationValues(CollectionPersister persister, Map copyCache) {
        for (DelayedOperation operation : operationQueue) {
            if (ValueDelayedOperation.class.isInstance(operation)) {
                ((ValueDelayedOperation) operation).replace(persister, copyCache);
            }
        }
    }

    /**
     * After reading all existing elements from the database,
     * add the queued elements to the underlying collection.
     */
    protected final void performQueuedOperations() {
        for (DelayedOperation operation : operationQueue) {
            operation.operate();
        }
        clearOperationQueue();
    }

    @Override
    public void setSnapshot(Serializable key, String role, Serializable snapshot) {
        this.key = key;
        this.role = role;
        this.storedSnapshot = snapshot;
    }

    @Override
    public void postAction() {
        clearOperationQueue();
        cachedSize = -1;
        clearDirty();
    }

    public final void clearOperationQueue() {
        operationQueue = null;
    }

    @Override
    public Object getValue() {
        return this;
    }

    @Override
    public void beginRead() {
        // override on some subclasses
        initializing = true;
    }

    @Override
    public boolean endRead() {
        //override on some subclasses
        return afterInitialize();
    }

    @Override
    public boolean afterInitialize() {
        setInitialized();
        //do this bit after setting initialized to true or it will recurse
        if (hasQueuedOperations()) {
            performQueuedOperations();
            cachedSize = -1;
            return false;
        } else {
            return true;
        }
    }

    /**
     * Initialize the collection, if possible, wrapping any exceptions
     * in a runtime exception
     *
     * @param writing currently obsolete
     *
     * @throws LazyInitializationException if we cannot initialize
     */
    protected final void initialize(final boolean writing) {
        if (initialized) {
            return;
        }

        withTemporarySessionIfNeeded(new LazyInitializationWork<Object>() {
            @Override
            public Object doWork() {
                session.initializeCollection(AbstractPersistentCollection.this, writing);
                return null;
            }
        });
    }

    private void throwLazyInitializationExceptionIfNotConnected() {
        if (!isConnectedToSession()) {
            throwLazyInitializationException("no session or session was closed");
        }
        if (!session.isConnected()) {
            throwLazyInitializationException("session is disconnected");
        }
    }

    private void throwLazyInitializationException(String message) {
        throw new LazyInitializationException("failed to lazily initialize a collection"
                + (role == null ? "" : " of role: " + role) + ", " + message);
    }

    protected final void setInitialized() {
        this.initializing = false;
        this.initialized = true;
    }

    protected final void setDirectlyAccessible(boolean directlyAccessible) {
        this.directlyAccessible = directlyAccessible;
    }

    @Override
    public boolean isDirectlyAccessible() {
        return directlyAccessible;
    }

    @Override
    public final boolean unsetSession(SharedSessionContractImplementor currentSession) {
        prepareForPossibleLoadingOutsideTransaction();
        if (currentSession == this.session) {
            if (!isTempSession) {
                if (hasQueuedOperations()) {
                    final String collectionInfoString = MessageHelper.collectionInfoString(getRole(), getKey());
                    try {
                        final TransactionStatus transactionStatus = session.getTransactionCoordinator()
                                .getTransactionDriverControl().getStatus();
                        if (transactionStatus.isOneOf(TransactionStatus.ROLLED_BACK,
                                TransactionStatus.MARKED_ROLLBACK, TransactionStatus.FAILED_COMMIT,
                                TransactionStatus.FAILED_ROLLBACK, TransactionStatus.ROLLING_BACK)) {
                            // It was due to a rollback.
                            LOG.queuedOperationWhenDetachFromSessionOnRollback(collectionInfoString);
                        } else {
                            // We don't know why the collection is being detached.
                            // Just log the info.
                            LOG.queuedOperationWhenDetachFromSession(collectionInfoString);
                        }
                    } catch (Exception e) {
                        // We don't know why the collection is being detached.
                        // Just log the info.
                        LOG.queuedOperationWhenDetachFromSession(collectionInfoString);
                    }
                }
                this.session = null;
            }
            return true;
        } else {
            if (this.session != null) {
                LOG.logCannotUnsetUnexpectedSessionInCollection(
                        generateUnexpectedSessionStateMessage(currentSession));
            }
            return false;
        }
    }

    protected void prepareForPossibleLoadingOutsideTransaction() {
        if (session != null) {
            allowLoadOutsideTransaction = session.getFactory().getSessionFactoryOptions()
                    .isInitializeLazyStateOutsideTransactionsEnabled();

            if (allowLoadOutsideTransaction && sessionFactoryUuid == null) {
                sessionFactoryUuid = session.getFactory().getUuid();
            }
        }
    }

    @Override
    public final boolean setCurrentSession(SharedSessionContractImplementor session) throws HibernateException {
        if (session == this.session) {
            return false;
        } else if (this.session != null) {
            final String msg = generateUnexpectedSessionStateMessage(session);
            if (isConnectedToSession()) {
                throw new HibernateException(
                        "Illegal attempt to associate a collection with two open sessions. " + msg);
            } else {
                LOG.logUnexpectedSessionInCollectionNotConnected(msg);
            }
        }
        if (hasQueuedOperations()) {
            LOG.queuedOperationWhenAttachToSession(MessageHelper.collectionInfoString(getRole(), getKey()));
        }
        this.session = session;
        return true;
    }

    private String generateUnexpectedSessionStateMessage(SharedSessionContractImplementor session) {
        // NOTE: If this.session != null, this.session may be operating on this collection
        // (e.g., by changing this.role, this.key, or even this.session) in a different thread.

        // Grab the current role and key (it can still get changed by this.session...)
        // If this collection is connected to this.session, then this.role and this.key should
        // be consistent with the CollectionEntry in this.session (as long as this.session doesn't
        // change it). Don't access the CollectionEntry in this.session because that could result
        // in multi-threaded access to this.session.
        final String roleCurrent = role;
        final Serializable keyCurrent = key;

        final StringBuilder sb = new StringBuilder("Collection : ");
        if (roleCurrent != null) {
            sb.append(MessageHelper.collectionInfoString(roleCurrent, keyCurrent));
        } else {
            final CollectionEntry ce = session.getPersistenceContextInternal().getCollectionEntry(this);
            if (ce != null) {
                sb.append(MessageHelper.collectionInfoString(ce.getLoadedPersister(), this, ce.getLoadedKey(),
                        session));
            } else {
                sb.append("<unknown>");
            }
        }
        // only include the collection contents if debug logging
        if (LOG.isDebugEnabled()) {
            final String collectionContents = wasInitialized() ? toString() : "<uninitialized>";
            sb.append("\nCollection contents: [").append(collectionContents).append("]");
        }
        return sb.toString();
    }

    @Override
    public boolean needsRecreate(CollectionPersister persister) {
        // Workaround for situations like HHH-7072.  If the collection element is a component that consists entirely
        // of nullable properties, we currently have to forcefully recreate the entire collection.  See the use
        // of hasNotNullableColumns in the AbstractCollectionPersister constructor for more info.  In order to delete
        // row-by-row, that would require SQL like "WHERE ( COL = ? OR ( COL is null AND ? is null ) )", rather than
        // the current "WHERE COL = ?" (fails for null for most DBs).  Note that
        // the param would have to be bound twice.  Until we eventually add "parameter bind points" concepts to the
        // AST in ORM 5+, handling this type of condition is either extremely difficult or impossible.  Forcing
        // recreation isn't ideal, but not really any other option in ORM 4.
        // Selecting a type used in where part of update statement
        // (must match condidion in org.hibernate.persister.collection.BasicCollectionPersister.doUpdateRows).
        // See HHH-9474
        Type whereType;
        if (persister.hasIndex()) {
            whereType = persister.getIndexType();
        } else {
            whereType = persister.getElementType();
        }
        if (whereType instanceof CompositeType) {
            CompositeType componentIndexType = (CompositeType) whereType;
            return !componentIndexType.hasNotNullProperty();
        }
        return false;
    }

    @Override
    public final void forceInitialization() throws HibernateException {
        if (!initialized) {
            if (initializing) {
                throw new AssertionFailure("force initialize loading collection");
            }
            initialize(false);
        }
    }

    /**
     * Get the current snapshot from the session
     */
    @SuppressWarnings({ "JavaDoc" })
    protected final Serializable getSnapshot() {
        return session.getPersistenceContext().getSnapshot(this);
    }

    @Override
    public final boolean wasInitialized() {
        return initialized;
    }

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

    @Override
    public final boolean hasQueuedOperations() {
        return operationQueue != null;
    }

    @Override
    public final Iterator queuedAdditionIterator() {
        if (hasQueuedOperations()) {
            return new Iterator() {
                private int index;

                @Override
                public Object next() {
                    return operationQueue.get(index++).getAddedInstance();
                }

                @Override
                public boolean hasNext() {
                    return index < operationQueue.size();
                }

                @Override
                public void remove() {
                    throw new UnsupportedOperationException();
                }
            };
        } else {
            return Collections.emptyIterator();
        }
    }

    @Override
    @SuppressWarnings({ "unchecked" })
    public final Collection getQueuedOrphans(String entityName) {
        if (hasQueuedOperations()) {
            final Collection additions = new ArrayList(operationQueue.size());
            final Collection removals = new ArrayList(operationQueue.size());
            for (DelayedOperation operation : operationQueue) {
                additions.add(operation.getAddedInstance());
                removals.add(operation.getOrphan());
            }
            return getOrphans(removals, additions, entityName, session);
        } else {
            return Collections.EMPTY_LIST;
        }
    }

    @Override
    public void preInsert(CollectionPersister persister) throws HibernateException {
    }

    @Override
    public void afterRowInsert(CollectionPersister persister, Object entry, int i) throws HibernateException {
    }

    @Override
    public abstract Collection getOrphans(Serializable snapshot, String entityName) throws HibernateException;

    /**
     * Get the session currently associated with this collection.
     *
     * @return The session
     */
    public final SharedSessionContractImplementor getSession() {
        return session;
    }

    protected final class IteratorProxy implements Iterator {
        protected final Iterator itr;

        public IteratorProxy(Iterator itr) {
            this.itr = itr;
        }

        @Override
        public boolean hasNext() {
            return itr.hasNext();
        }

        @Override
        public Object next() {
            return itr.next();
        }

        @Override
        public void remove() {
            write();
            itr.remove();
        }
    }

    protected final class ListIteratorProxy implements ListIterator {
        protected final ListIterator itr;

        public ListIteratorProxy(ListIterator itr) {
            this.itr = itr;
        }

        @Override
        @SuppressWarnings({ "unchecked" })
        public void add(Object o) {
            write();
            itr.add(o);
        }

        @Override
        public boolean hasNext() {
            return itr.hasNext();
        }

        @Override
        public boolean hasPrevious() {
            return itr.hasPrevious();
        }

        @Override
        public Object next() {
            return itr.next();
        }

        @Override
        public int nextIndex() {
            return itr.nextIndex();
        }

        @Override
        public Object previous() {
            return itr.previous();
        }

        @Override
        public int previousIndex() {
            return itr.previousIndex();
        }

        @Override
        public void remove() {
            write();
            itr.remove();
        }

        @Override
        @SuppressWarnings({ "unchecked" })
        public void set(Object o) {
            write();
            itr.set(o);
        }
    }

    protected class SetProxy implements java.util.Set {
        protected final Collection set;

        public SetProxy(Collection set) {
            this.set = set;
        }

        @Override
        @SuppressWarnings({ "unchecked" })
        public boolean add(Object o) {
            write();
            return set.add(o);
        }

        @Override
        @SuppressWarnings({ "unchecked" })
        public boolean addAll(Collection c) {
            write();
            return set.addAll(c);
        }

        @Override
        public void clear() {
            write();
            set.clear();
        }

        @Override
        public boolean contains(Object o) {
            return set.contains(o);
        }

        @Override
        @SuppressWarnings("unchecked")
        public boolean containsAll(Collection c) {
            return set.containsAll(c);
        }

        @Override
        public boolean isEmpty() {
            return set.isEmpty();
        }

        @Override
        public Iterator iterator() {
            return new IteratorProxy(set.iterator());
        }

        @Override
        public boolean remove(Object o) {
            write();
            return set.remove(o);
        }

        @Override
        @SuppressWarnings("unchecked")
        public boolean removeAll(Collection c) {
            write();
            return set.removeAll(c);
        }

        @Override
        @SuppressWarnings("unchecked")
        public boolean retainAll(Collection c) {
            write();
            return set.retainAll(c);
        }

        @Override
        public int size() {
            return set.size();
        }

        @Override
        public Object[] toArray() {
            return set.toArray();
        }

        @Override
        @SuppressWarnings({ "unchecked" })
        public Object[] toArray(Object[] array) {
            return set.toArray(array);
        }
    }

    protected final class ListProxy implements java.util.List {
        protected final List list;

        public ListProxy(List list) {
            this.list = list;
        }

        @Override
        @SuppressWarnings({ "unchecked" })
        public void add(int index, Object value) {
            write();
            list.add(index, value);
        }

        @Override
        @SuppressWarnings({ "unchecked" })
        public boolean add(Object o) {
            write();
            return list.add(o);
        }

        @Override
        @SuppressWarnings({ "unchecked" })
        public boolean addAll(Collection c) {
            write();
            return list.addAll(c);
        }

        @Override
        @SuppressWarnings({ "unchecked" })
        public boolean addAll(int i, Collection c) {
            write();
            return list.addAll(i, c);
        }

        @Override
        public void clear() {
            write();
            list.clear();
        }

        @Override
        public boolean contains(Object o) {
            return list.contains(o);
        }

        @Override
        @SuppressWarnings("unchecked")
        public boolean containsAll(Collection c) {
            return list.containsAll(c);
        }

        @Override
        public Object get(int i) {
            return list.get(i);
        }

        @Override
        public int indexOf(Object o) {
            return list.indexOf(o);
        }

        @Override
        public boolean isEmpty() {
            return list.isEmpty();
        }

        @Override
        public Iterator iterator() {
            return new IteratorProxy(list.iterator());
        }

        @Override
        public int lastIndexOf(Object o) {
            return list.lastIndexOf(o);
        }

        @Override
        public ListIterator listIterator() {
            return new ListIteratorProxy(list.listIterator());
        }

        @Override
        public ListIterator listIterator(int i) {
            return new ListIteratorProxy(list.listIterator(i));
        }

        @Override
        public Object remove(int i) {
            write();
            return list.remove(i);
        }

        @Override
        public boolean remove(Object o) {
            write();
            return list.remove(o);
        }

        @Override
        @SuppressWarnings("unchecked")
        public boolean removeAll(Collection c) {
            write();
            return list.removeAll(c);
        }

        @Override
        @SuppressWarnings("unchecked")
        public boolean retainAll(Collection c) {
            write();
            return list.retainAll(c);
        }

        @Override
        @SuppressWarnings({ "unchecked" })
        public Object set(int i, Object o) {
            write();
            return list.set(i, o);
        }

        @Override
        public int size() {
            return list.size();
        }

        @Override
        public List subList(int i, int j) {
            return list.subList(i, j);
        }

        @Override
        public Object[] toArray() {
            return list.toArray();
        }

        @Override
        @SuppressWarnings({ "unchecked" })
        public Object[] toArray(Object[] array) {
            return list.toArray(array);
        }

    }

    /**
     * Contract for operations which are part of a collection's operation queue.
     */
    protected interface DelayedOperation {
        public void operate();

        public Object getAddedInstance();

        public Object getOrphan();
    }

    protected interface ValueDelayedOperation extends DelayedOperation {
        void replace(CollectionPersister collectionPersister, Map copyCache);
    }

    protected abstract class AbstractValueDelayedOperation implements ValueDelayedOperation {
        private Object addedValue;
        private Object orphan;

        protected AbstractValueDelayedOperation(Object addedValue, Object orphan) {
            this.addedValue = addedValue;
            this.orphan = orphan;
        }

        public void replace(CollectionPersister persister, Map copyCache) {
            if (addedValue != null) {
                addedValue = getReplacement(persister.getElementType(), addedValue, copyCache);
            }
        }

        protected final Object getReplacement(Type type, Object current, Map copyCache) {
            return type.replace(current, null, session, owner, copyCache);
        }

        @Override
        public final Object getAddedInstance() {
            return addedValue;
        }

        @Override
        public final Object getOrphan() {
            return orphan;
        }
    }

    /**
     * Given a collection of entity instances that used to
     * belong to the collection, and a collection of instances
     * that currently belong, return a collection of orphans
     */
    @SuppressWarnings({ "JavaDoc", "unchecked" })
    protected static Collection getOrphans(Collection oldElements, Collection currentElements, String entityName,
            SharedSessionContractImplementor session) throws HibernateException {

        // short-circuit(s)
        if (currentElements.size() == 0) {
            // no new elements, the old list contains only Orphans
            return oldElements;
        }
        if (oldElements.size() == 0) {
            // no old elements, so no Orphans neither
            return oldElements;
        }

        final EntityPersister entityPersister = session.getFactory().getEntityPersister(entityName);
        final Type idType = entityPersister.getIdentifierType();
        final boolean useIdDirect = mayUseIdDirect(idType);

        // create the collection holding the Orphans
        final Collection res = new ArrayList();

        // collect EntityIdentifier(s) of the *current* elements - add them into a HashSet for fast access
        final java.util.Set currentIds = new HashSet();
        final java.util.Set currentSaving = new IdentitySet();
        final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
        for (Object current : currentElements) {
            if (current != null && ForeignKeys.isNotTransient(entityName, current, null, session)) {
                final EntityEntry ee = persistenceContext.getEntry(current);
                if (ee != null && ee.getStatus() == Status.SAVING) {
                    currentSaving.add(current);
                } else {
                    final Serializable currentId = ForeignKeys.getEntityIdentifierIfNotUnsaved(entityName, current,
                            session);
                    currentIds.add(useIdDirect ? currentId : new TypedValue(idType, currentId));
                }
            }
        }

        // iterate over the *old* list
        for (Object old : oldElements) {
            if (!currentSaving.contains(old)) {
                final Serializable oldId = ForeignKeys.getEntityIdentifierIfNotUnsaved(entityName, old, session);
                if (!currentIds.contains(useIdDirect ? oldId : new TypedValue(idType, oldId))) {
                    res.add(old);
                }
            }
        }

        return res;
    }

    private static boolean mayUseIdDirect(Type idType) {
        return idType == StringType.INSTANCE || idType == IntegerType.INSTANCE || idType == LongType.INSTANCE
                || idType == UUIDBinaryType.INSTANCE || idType == UUIDCharType.INSTANCE
                || idType == PostgresUUIDType.INSTANCE;
    }

    /**
     * Removes entity entries that have an equal identifier with the incoming entity instance
     *
     * @param list The list containing the entity instances
     * @param entityInstance The entity instance to match elements.
     * @param entityName The entity name
     * @param session The session
     */
    public static void identityRemove(Collection list, Object entityInstance, String entityName,
            SharedSessionContractImplementor session) {

        if (entityInstance != null && ForeignKeys.isNotTransient(entityName, entityInstance, null, session)) {
            final EntityPersister entityPersister = session.getFactory().getEntityPersister(entityName);
            final Type idType = entityPersister.getIdentifierType();

            final Serializable idOfCurrent = ForeignKeys.getEntityIdentifierIfNotUnsaved(entityName, entityInstance,
                    session);
            final Iterator itr = list.iterator();
            while (itr.hasNext()) {
                final Serializable idOfOld = ForeignKeys.getEntityIdentifierIfNotUnsaved(entityName, itr.next(),
                        session);
                if (idType.isEqual(idOfCurrent, idOfOld, session.getFactory())) {
                    itr.remove();
                    break;
                }
            }

        }
    }

    /**
     * Removes entity entries that have an equal identifier with the incoming entity instance
     *
     * @param list The list containing the entity instances
     * @param entityInstance The entity instance to match elements.
     * @param entityName The entity name
     * @param session The session
     *
     * @deprecated {@link #identityRemove(Collection, Object, String, SharedSessionContractImplementor)}
     *             should be used instead.
     */
    @Deprecated
    public static void identityRemove(Collection list, Object entityInstance, String entityName,
            SessionImplementor session) {
        identityRemove(list, entityInstance, entityName, (SharedSessionContractImplementor) session);
    }

    @Override
    public Object getIdentifier(Object entry, int i) {
        throw new UnsupportedOperationException();
    }

    @Override
    public Object getOwner() {
        return owner;
    }

    @Override
    public void setOwner(Object owner) {
        this.owner = owner;
    }

}