/*
* 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 <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
*/
@SuppressWarnings("UnusedDeclaration")
public PersistentIdentifierBag() {
}
/**
* Constructs a PersistentIdentifierBag.
*
* @param session The session
*/
public PersistentIdentifierBag(SharedSessionContractImplementor session) {
super( session );
}
/**
* 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 );
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
}
}