/*
 * 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.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.SharedSessionContractImplementor;
import org.hibernate.loader.CollectionAliases;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.type.Type;

An IdentifierBag implements "bag" semantics more efficiently than a regular Bag by adding a synthetic identifier column to the table. This identifier is unique for all rows in the table, allowing very efficient updates and deletes. The value of the identifier is never exposed to the application.

IdentifierBags may not be used for a many-to-one association. Furthermore, there is no reason to use inverse="true".
Author:Gavin King
/** * An <tt>IdentifierBag</tt> implements "bag" semantics more efficiently than * a regular <tt>Bag</tt> by adding a synthetic identifier column to the * table. This identifier is unique for all rows in the table, allowing very * efficient updates and deletes. The value of the identifier is never exposed * to the application.<br> * <br> * <tt>IdentifierBag</tt>s may not be used for a many-to-one association. * Furthermore, there is no reason to use <tt>inverse="true"</tt>. * * @author Gavin King */
public class PersistentIdentifierBag extends AbstractPersistentCollection implements List { protected List<Object> values; protected Map<Integer, Object> identifiers;
Constructs a PersistentIdentifierBag. This form needed for SOAP libraries, etc
/** * Constructs a PersistentIdentifierBag. This form needed for SOAP libraries, etc */
@SuppressWarnings("UnusedDeclaration") public PersistentIdentifierBag() { }
Constructs a PersistentIdentifierBag.
Params:
  • session – The session
/** * Constructs a PersistentIdentifierBag. * * @param session The session */
public PersistentIdentifierBag(SharedSessionContractImplementor session) { super( session ); }
Constructs a PersistentIdentifierBag.
Params:
  • session – The session
  • coll – The base elements
/** * Constructs a PersistentIdentifierBag. * * @param session The session * @param coll The base elements */
@SuppressWarnings("unchecked") public PersistentIdentifierBag(SharedSessionContractImplementor session, Collection coll) { super( session ); if (coll instanceof List) { values = (List<Object>) coll; } else { values = new ArrayList<>(); for ( Object element : coll ) { values.add( element ); } } setInitialized(); setDirectlyAccessible( true ); identifiers = new HashMap<>(); } @Override 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 ( int i = 0; i < size; i+=2 ) { identifiers.put( (i/2), persister.getIdentifierType().assemble( array[i], getSession(), owner ) ); values.add( persister.getElementType().assemble( array[i+1], getSession(), owner ) ); } } @Override public Object getIdentifier(Object entry, int i) { return identifiers.get( i ); } @Override public boolean isWrapper(Object collection) { return values==collection; } @Override public boolean add(Object o) { write(); values.add( o ); return true; } @Override public void clear() { initialize( true ); if ( ! values.isEmpty() || ! identifiers.isEmpty() ) { values.clear(); identifiers.clear(); dirty(); } } @Override public boolean contains(Object o) { read(); return values.contains( o ); } @Override public boolean containsAll(Collection c) { read(); return values.containsAll( c ); } @Override public boolean isEmpty() { return readSize() ? getCachedSize()==0 : values.isEmpty(); } @Override public Iterator iterator() { read(); return new IteratorProxy( values.iterator() ); } @Override public boolean remove(Object o) { initialize( true ); final int index = values.indexOf( o ); if ( index >= 0 ) { beforeRemove( index ); values.remove( index ); elementRemoved = true; dirty(); return true; } else { return false; } } @Override public boolean removeAll(Collection c) { if ( c.size() > 0 ) { boolean result = false; for ( Object element : c ) { if ( remove( element ) ) { result = true; } } return result; } else { return false; } } @Override public boolean retainAll(Collection c) { initialize( true ); if ( values.retainAll( c ) ) { dirty(); return true; } else { return false; } } @Override public int size() { return readSize() ? getCachedSize() : values.size(); } @Override public Object[] toArray() { read(); return values.toArray(); } @Override public Object[] toArray(Object[] a) { read(); return values.toArray( a ); } @Override public void beforeInitialize(CollectionPersister persister, int anticipatedSize) { identifiers = anticipatedSize <= 0 ? new HashMap<>() : new HashMap<>( anticipatedSize + 1 + (int) ( anticipatedSize * .75f ), .75f ); values = anticipatedSize <= 0 ? new ArrayList<>() : new ArrayList<>( anticipatedSize ); } @Override public Serializable disassemble(CollectionPersister persister) throws HibernateException { final Serializable[] result = new Serializable[ values.size() * 2 ]; int i = 0; for ( int j=0; j< values.size(); j++ ) { final Object value = values.get( j ); result[i++] = persister.getIdentifierType().disassemble( identifiers.get( j ), getSession(), null ); result[i++] = persister.getElementType().disassemble( value, getSession(), null ); } return result; } @Override public boolean empty() { return values.isEmpty(); } @Override public Iterator entries(CollectionPersister persister) { return values.iterator(); } @Override public boolean entryExists(Object entry, int i) { return entry!=null; } @Override public boolean equalsSnapshot(CollectionPersister persister) throws HibernateException { final Type elementType = persister.getElementType(); final Map snap = (Map) getSnapshot(); if ( snap.size()!= values.size() ) { return false; } for ( int i=0; i<values.size(); i++ ) { final Object value = values.get( i ); final Object id = identifiers.get( i ); if ( id == null ) { return false; } final Object old = snap.get( id ); if ( elementType.isDirty( old, value, getSession() ) ) { return false; } } return true; } @Override public boolean isSnapshotEmpty(Serializable snapshot) { return ( (Map) snapshot ).isEmpty(); } @Override @SuppressWarnings("unchecked") public Iterator getDeletes(CollectionPersister persister, boolean indexIsFormula) throws HibernateException { final Map snap = (Map) getSnapshot(); final List deletes = new ArrayList( snap.keySet() ); for ( int i=0; i<values.size(); i++ ) { if ( values.get( i ) != null ) { deletes.remove( identifiers.get( i ) ); } } return deletes.iterator(); } @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 Map snap = (Map) getSnapshot(); final Object id = identifiers.get( i ); return snap.get( id ); } @Override public boolean needsInserting(Object entry, int i, Type elemType) throws HibernateException { final Map snap = (Map) getSnapshot(); final Object id = identifiers.get( i ); return entry != null && ( id==null || snap.get( id )==null ); } @Override public boolean needsUpdating(Object entry, int i, Type elemType) throws HibernateException { if ( entry == null ) { return false; } final Map snap = (Map) getSnapshot(); final Object id = identifiers.get( i ); if ( id == null ) { return false; } final Object old = snap.get( id ); return old != null && elemType.isDirty( old, entry, getSession() ); } @Override public Object readFrom( ResultSet rs, CollectionPersister persister, CollectionAliases descriptor, Object owner) throws HibernateException, SQLException { final Object element = persister.readElement( rs, owner, descriptor.getSuffixedElementAliases(), getSession() ); final Object old = identifiers.put( values.size(), persister.readIdentifier( rs, descriptor.getSuffixedIdentifierAlias(), getSession() ) ); if ( old == null ) { //maintain correct duplication if loaded in a cartesian product values.add( element ); } return element; } @Override @SuppressWarnings("unchecked") public Serializable getSnapshot(CollectionPersister persister) throws HibernateException { final HashMap map = new HashMap( values.size() ); final Iterator iter = values.iterator(); int i=0; while ( iter.hasNext() ) { final Object value = iter.next(); map.put( identifiers.get( i++ ), persister.getElementType().deepCopy( value, persister.getFactory() ) ); } return map; } @Override public Collection getOrphans(Serializable snapshot, String entityName) throws HibernateException { final Map sn = (Map) snapshot; return getOrphans( sn.values(), values, entityName, getSession() ); } @Override public void preInsert(CollectionPersister persister) throws HibernateException { final Iterator itr = values.iterator(); int i = 0; while ( itr.hasNext() ) { final Object entry = itr.next(); final Integer loc = i++; if ( !identifiers.containsKey( loc ) ) { //TODO: native ids final Serializable id = persister.getIdentifierGenerator().generate( getSession(), entry ); identifiers.put( loc, id ); } } } @Override public void add(int index, Object element) { write(); beforeAdd( index ); values.add( index, element ); } @Override public boolean addAll(int index, Collection c) { if ( c.size() > 0 ) { for ( Object element : c ) { add( index++, element ); } return true; } else { return false; } } @Override public Object get(int index) { read(); return values.get( index ); } @Override public int indexOf(Object o) { read(); return values.indexOf( o ); } @Override public int lastIndexOf(Object o) { read(); return values.lastIndexOf( o ); } @Override public ListIterator listIterator() { read(); return new ListIteratorProxy( values.listIterator() ); } @Override public ListIterator listIterator(int index) { read(); return new ListIteratorProxy( values.listIterator( index ) ); } private void beforeRemove(int index) { final Object removedId = identifiers.get( index ); final int last = values.size()-1; for ( int i=index; i<last; i++ ) { final Object id = identifiers.get( i+1 ); if ( id == null ) { identifiers.remove( i ); } else { identifiers.put( i, id ); } } identifiers.put( last, removedId ); } private void beforeAdd(int index) { for ( int i=index; i<values.size(); i++ ) { identifiers.put( i+1, identifiers.get( i ) ); } identifiers.remove( index ); } @Override public Object remove(int index) { write(); beforeRemove( index ); return values.remove( index ); } @Override public Object set(int index, Object element) { write(); return values.set( index, element ); } @Override public List subList(int fromIndex, int toIndex) { read(); return new ListProxy( values.subList( fromIndex, toIndex ) ); } @Override public boolean addAll(Collection c) { if ( c.size()> 0 ) { write(); return values.addAll( c ); } else { return false; } } @Override public void afterRowInsert( CollectionPersister persister, Object entry, int i) throws HibernateException { //TODO: if we are using identity columns, fetch the identifier } }