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

Java tutorial

Introduction

Here is the source code for org.hibernate.collection.internal.PersistentBag.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.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;

import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.loader.CollectionAliases;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.type.Type;

/**
 * An unordered, unkeyed collection that can contain the same element
 * multiple times. The Java collections API, curiously, has no <tt>Bag</tt>.
 * Most developers seem to use <tt>List</tt>s to represent bag semantics,
 * so Hibernate follows this practice.
 *
 * @author Gavin King
 */
public class PersistentBag extends AbstractPersistentCollection implements List {

    protected List bag;

    // The Collection provided to a PersistentBag constructor,
    private Collection providedCollection;

    /**
     * Constructs a PersistentBag.  Needed for SOAP libraries, etc
     */
    @SuppressWarnings("UnusedDeclaration")
    public PersistentBag() {
    }

    /**
     * Constructs a PersistentBag
     *
     * @param session The session
     */
    public PersistentBag(SharedSessionContractImplementor session) {
        super(session);
    }

    /**
     * Constructs a PersistentBag
     *
     * @param session The session
     *
     * @deprecated {@link #PersistentBag(SharedSessionContractImplementor)} should be used instead.
     */
    @Deprecated
    public PersistentBag(SessionImplementor session) {
        this((SharedSessionContractImplementor) session);
    }

    /**
     * Constructs a PersistentBag
     *
     * @param session The session
     * @param coll The base elements.
     */
    @SuppressWarnings("unchecked")
    public PersistentBag(SharedSessionContractImplementor session, Collection coll) {
        super(session);
        providedCollection = coll;
        if (coll instanceof List) {
            bag = (List) coll;
        } else {
            bag = new ArrayList(coll);
        }
        setInitialized();
        setDirectlyAccessible(true);
    }

    /**
     * Constructs a PersistentBag
     *
     * @param session The session
     * @param coll The base elements.
     *
     * @deprecated {@link #PersistentBag(SharedSessionContractImplementor, Collection)}
     * should be used instead.
     */
    @Deprecated
    public PersistentBag(SessionImplementor session, Collection coll) {
        this((SharedSessionContractImplementor) session, coll);
    }

    @Override
    public boolean isWrapper(Object collection) {
        return bag == collection;
    }

    @Override
    public boolean isDirectlyProvidedCollection(Object collection) {
        return isDirectlyAccessible() && providedCollection == collection;
    }

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

    @Override
    public Iterator entries(CollectionPersister persister) {
        return bag.iterator();
    }

    @Override
    @SuppressWarnings("unchecked")
    public Object readFrom(ResultSet rs, CollectionPersister persister, CollectionAliases descriptor, Object owner)
            throws HibernateException, SQLException {
        // note that if we load this collection from a cartesian product
        // the multiplicity would be broken ... so use an idbag instead
        final Object element = persister.readElement(rs, owner, descriptor.getSuffixedElementAliases(),
                getSession());
        if (element != null) {
            bag.add(element);
        }
        return element;
    }

    @Override
    public void beforeInitialize(CollectionPersister persister, int anticipatedSize) {
        this.bag = (List) persister.getCollectionType().instantiate(anticipatedSize);
    }

    @Override
    @SuppressWarnings("unchecked")
    public boolean equalsSnapshot(CollectionPersister persister) throws HibernateException {
        final Type elementType = persister.getElementType();
        final List<Object> sn = (List<Object>) getSnapshot();
        if (sn.size() != bag.size()) {
            return false;
        }

        // HHH-11032 - Group objects by Type.getHashCode() to reduce the complexity of the search
        final Map<Integer, List<Object>> hashToInstancesBag = groupByEqualityHash(bag, elementType);
        final Map<Integer, List<Object>> hashToInstancesSn = groupByEqualityHash(sn, elementType);
        if (hashToInstancesBag.size() != hashToInstancesSn.size()) {
            return false;
        }

        // First iterate over the hashToInstancesBag entries to see if the number
        // of List values is different for any hash value.
        for (Map.Entry<Integer, List<Object>> hashToInstancesBagEntry : hashToInstancesBag.entrySet()) {
            final Integer hash = hashToInstancesBagEntry.getKey();
            final List<Object> instancesBag = hashToInstancesBagEntry.getValue();
            final List<Object> instancesSn = hashToInstancesSn.get(hash);
            if (instancesSn == null || (instancesBag.size() != instancesSn.size())) {
                return false;
            }
        }

        // We already know that both hashToInstancesBag and hashToInstancesSn have:
        // 1) the same hash values;
        // 2) the same number of values with the same hash value.

        // Now check if the number of occurrences of each element is the same.
        for (Map.Entry<Integer, List<Object>> hashToInstancesBagEntry : hashToInstancesBag.entrySet()) {
            final Integer hash = hashToInstancesBagEntry.getKey();
            final List<Object> instancesBag = hashToInstancesBagEntry.getValue();
            final List<Object> instancesSn = hashToInstancesSn.get(hash);
            for (Object instance : instancesBag) {
                if (!expectOccurrences(instance, instancesBag, elementType,
                        countOccurrences(instance, instancesSn, elementType))) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * Groups items in searchedBag according to persistence "equality" as defined in Type.isSame and Type.getHashCode
     *
     * @return Map of "equality" hashCode to List of objects
     */
    private Map<Integer, List<Object>> groupByEqualityHash(List<Object> searchedBag, Type elementType) {
        if (searchedBag.isEmpty()) {
            return Collections.emptyMap();
        }
        Map<Integer, List<Object>> map = new HashMap<>();
        for (Object o : searchedBag) {
            map.computeIfAbsent(nullableHashCode(o, elementType), k -> new ArrayList<>()).add(o);
        }
        return map;
    }

    /**
     * @param o
     * @param elementType
     * @return the default elementType hashcode of the object o, or null if the object is null
     */
    private Integer nullableHashCode(Object o, Type elementType) {
        if (o == null) {
            return null;
        } else {
            return elementType.getHashCode(o);
        }
    }

    @Override
    public boolean isSnapshotEmpty(Serializable snapshot) {
        return ((Collection) snapshot).isEmpty();
    }

    private int countOccurrences(Object element, List<Object> list, Type elementType) {
        int result = 0;
        for (Object listElement : list) {
            if (elementType.isSame(element, listElement)) {
                result++;
            }
        }
        return result;
    }

    private boolean expectOccurrences(Object element, List<Object> list, Type elementType, int expected) {
        int result = 0;
        for (Object listElement : list) {
            if (elementType.isSame(element, listElement)) {
                if (result++ > expected) {
                    return false;
                }
            }
        }
        return result == expected;
    }

    @Override
    @SuppressWarnings("unchecked")
    public Serializable getSnapshot(CollectionPersister persister) throws HibernateException {
        final ArrayList clonedList = new ArrayList(bag.size());
        for (Object item : bag) {
            clonedList.add(persister.getElementType().deepCopy(item, persister.getFactory()));
        }
        return clonedList;
    }

    @Override
    public Collection getOrphans(Serializable snapshot, String entityName) throws HibernateException {
        final List sn = (List) snapshot;
        return getOrphans(sn, bag, entityName, getSession());
    }

    @Override
    public Serializable disassemble(CollectionPersister persister) throws HibernateException {
        final int length = bag.size();
        final Serializable[] result = new Serializable[length];
        for (int i = 0; i < length; i++) {
            result[i] = persister.getElementType().disassemble(bag.get(i), getSession(), null);
        }
        return result;
    }

    @Override
    @SuppressWarnings("unchecked")
    public void initializeFromCache(CollectionPersister persister, Serializable disassembled, Object owner)
            throws HibernateException {
        final Serializable[] array = (Serializable[]) disassembled;
        final int size = array.length;
        beforeInitialize(persister, size);
        for (Serializable item : array) {
            final Object element = persister.getElementType().assemble(item, getSession(), owner);
            if (element != null) {
                bag.add(element);
            }
        }
    }

    @Override
    public boolean needsRecreate(CollectionPersister persister) {
        return !persister.isOneToMany();
    }

    // For a one-to-many, a <bag> is not really a bag;
    // it is *really* a set, since it can't contain the
    // same element twice. It could be considered a bug
    // in the mapping dtd that <bag> allows <one-to-many>.

    // Anyway, here we implement <set> semantics for a
    // <one-to-many> <bag>!

    @Override
    @SuppressWarnings("unchecked")
    public Iterator getDeletes(CollectionPersister persister, boolean indexIsFormula) throws HibernateException {
        final Type elementType = persister.getElementType();
        final ArrayList deletes = new ArrayList();
        final List sn = (List) getSnapshot();
        final Iterator olditer = sn.iterator();
        int i = 0;
        while (olditer.hasNext()) {
            final Object old = olditer.next();
            final Iterator newiter = bag.iterator();
            boolean found = false;
            if (bag.size() > i && elementType.isSame(old, bag.get(i++))) {
                //a shortcut if its location didn't change!
                found = true;
            } else {
                //search for it
                //note that this code is incorrect for other than one-to-many
                while (newiter.hasNext()) {
                    if (elementType.isSame(old, newiter.next())) {
                        found = true;
                        break;
                    }
                }
            }
            if (!found) {
                deletes.add(old);
            }
        }
        return deletes.iterator();
    }

    @Override
    public boolean needsInserting(Object entry, int i, Type elemType) throws HibernateException {
        final List sn = (List) getSnapshot();
        if (sn.size() > i && elemType.isSame(sn.get(i), entry)) {
            //a shortcut if its location didn't change!
            return false;
        } else {
            //search for it
            //note that this code is incorrect for other than one-to-many
            for (Object old : sn) {
                if (elemType.isSame(old, entry)) {
                    return false;
                }
            }
            return true;
        }
    }

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

    @Override
    public boolean needsUpdating(Object entry, int i, Type elemType) {
        return false;
    }

    @Override
    public int size() {
        return readSize() ? getCachedSize() : bag.size();
    }

    @Override
    public boolean isEmpty() {
        return readSize() ? getCachedSize() == 0 : bag.isEmpty();
    }

    @Override
    public boolean contains(Object object) {
        final Boolean exists = readElementExistence(object);
        return exists == null ? bag.contains(object) : exists;
    }

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

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

    @Override
    public Object[] toArray(Object[] a) {
        read();
        return bag.toArray(a);
    }

    @Override
    @SuppressWarnings("unchecked")
    public boolean add(Object object) {
        if (!isOperationQueueEnabled()) {
            write();
            return bag.add(object);
        } else {
            queueOperation(new SimpleAdd(object));
            return true;
        }
    }

    @Override
    public boolean remove(Object o) {
        initialize(true);
        if (bag.remove(o)) {
            elementRemoved = true;
            dirty();
            return true;
        } else {
            return false;
        }
    }

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

    @Override
    @SuppressWarnings("unchecked")
    public boolean addAll(Collection values) {
        if (values.size() == 0) {
            return false;
        }
        if (!isOperationQueueEnabled()) {
            write();
            return bag.addAll(values);
        } else {
            for (Object value : values) {
                queueOperation(new SimpleAdd(value));
            }
            return values.size() > 0;
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public boolean removeAll(Collection c) {
        if (c.size() > 0) {
            initialize(true);
            if (bag.removeAll(c)) {
                elementRemoved = true;
                dirty();
                return true;
            } else {
                return false;
            }
        } else {
            return false;
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public boolean retainAll(Collection c) {
        initialize(true);
        if (bag.retainAll(c)) {
            dirty();
            return true;
        } else {
            return false;
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public void clear() {
        if (isClearQueueEnabled()) {
            queueOperation(new Clear());
        } else {
            initialize(true);
            if (!bag.isEmpty()) {
                bag.clear();
                dirty();
            }
        }
    }

    @Override
    public Object getIndex(Object entry, int i, CollectionPersister persister) {
        throw new UnsupportedOperationException("Bags don't have indexes");
    }

    @Override
    public Object getElement(Object entry) {
        return entry;
    }

    @Override
    public Object getSnapshotElement(Object entry, int i) {
        final List sn = (List) getSnapshot();
        return sn.get(i);
    }

    /**
     * Count how many times the given object occurs in the elements
     *
     * @param o The object to check
     *
     * @return The number of occurences.
     */
    @SuppressWarnings("UnusedDeclaration")
    public int occurrences(Object o) {
        read();
        final Iterator itr = bag.iterator();
        int result = 0;
        while (itr.hasNext()) {
            if (o.equals(itr.next())) {
                result++;
            }
        }
        return result;
    }

    // List OPERATIONS:

    @Override
    @SuppressWarnings("unchecked")
    public void add(int i, Object o) {
        write();
        bag.add(i, o);
    }

    @Override
    @SuppressWarnings("unchecked")
    public boolean addAll(int i, Collection c) {
        if (c.size() > 0) {
            write();
            return bag.addAll(i, c);
        } else {
            return false;
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public Object get(int i) {
        read();
        return bag.get(i);
    }

    @Override
    @SuppressWarnings("unchecked")
    public int indexOf(Object o) {
        read();
        return bag.indexOf(o);
    }

    @Override
    @SuppressWarnings("unchecked")
    public int lastIndexOf(Object o) {
        read();
        return bag.lastIndexOf(o);
    }

    @Override
    @SuppressWarnings("unchecked")
    public ListIterator listIterator() {
        read();
        return new ListIteratorProxy(bag.listIterator());
    }

    @Override
    @SuppressWarnings("unchecked")
    public ListIterator listIterator(int i) {
        read();
        return new ListIteratorProxy(bag.listIterator(i));
    }

    @Override
    @SuppressWarnings("unchecked")
    public Object remove(int i) {
        write();
        return bag.remove(i);
    }

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

    @Override
    @SuppressWarnings("unchecked")
    public List subList(int start, int end) {
        read();
        return new ListProxy(bag.subList(start, end));
    }

    @Override
    public boolean entryExists(Object entry, int i) {
        return entry != null;
    }

    @Override
    public String toString() {
        read();
        return bag.toString();
    }

    /**
     * Bag does not respect the collection API and do an
     * JVM instance comparison to do the equals.
     * The semantic is broken not to have to initialize a
     * collection for a simple equals() operation.
     *
     * @see java.lang.Object#equals(java.lang.Object)
     * <p>
     * {@inheritDoc}
     */
    @Override
    public boolean equals(Object obj) {
        return super.equals(obj);
    }

    @Override
    public int hashCode() {
        return super.hashCode();
    }

    final class Clear implements DelayedOperation {
        @Override
        public void operate() {
            bag.clear();
        }

        @Override
        public Object getAddedInstance() {
            return null;
        }

        @Override
        public Object getOrphan() {
            throw new UnsupportedOperationException("queued clear cannot be used with orphan delete");
        }
    }

    final class SimpleAdd extends AbstractValueDelayedOperation {

        public SimpleAdd(Object addedValue) {
            super(addedValue, null);
        }

        @Override
        @SuppressWarnings("unchecked")
        public void operate() {
            bag.add(getAddedInstance());
        }
    }
}