From f6ab5b517ef683ec138f08f8c74e1951e28489e5 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Mon, 6 Jan 2025 16:35:00 +0100 Subject: [PATCH 1/2] HHH-1914 Fix merge support for unmodifiable collections --- .../java/org/hibernate/type/CollectionType.java | 13 +++++++++++++ .../manytomany/UnmodifiableCollectionMergeTest.java | 2 -- .../java/org/hibernate/orm/test/ops/MergeTest.java | 2 +- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/type/CollectionType.java b/hibernate-core/src/main/java/org/hibernate/type/CollectionType.java index aa0b53d1e6d1..f22d147e022a 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/CollectionType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/CollectionType.java @@ -661,6 +661,19 @@ private Object replaceOriginal( final boolean wasClean = target instanceof PersistentCollection collection && !collection.isDirty(); + if ( target instanceof PersistentCollection existingPersistentCollection + && existingPersistentCollection.isDirectlyAccessible() ) { + // When a replace/merge is requested and the underlying collection is directly accessible, + // use a new persistent collection, to avoid potential issues + // like the underlying collection being unmodifiable and hence failing the element replacement + final CollectionPersister collectionPersister = getPersister( session ); + final Object key = existingPersistentCollection.getKey(); + final PersistentCollection persistentCollection = instantiate( session, collectionPersister, key ); + persistentCollection.initializeEmptyCollection( collectionPersister ); + persistentCollection.setSnapshot( key, existingPersistentCollection.getRole(), existingPersistentCollection.getStoredSnapshot() ); + session.getPersistenceContextInternal().addInitializedDetachedCollection( collectionPersister, persistentCollection ); + target = persistentCollection; + } //TODO: this is a little inefficient, don't need to do a whole // deep replaceElements() call replaceElements( result, target, owner, copyCache, session ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/manytomany/UnmodifiableCollectionMergeTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/manytomany/UnmodifiableCollectionMergeTest.java index 46435fb303a8..6bf8b79c6ecc 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/manytomany/UnmodifiableCollectionMergeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/manytomany/UnmodifiableCollectionMergeTest.java @@ -9,7 +9,6 @@ import java.util.Set; import org.hibernate.dialect.H2Dialect; -import org.hibernate.testing.orm.junit.FailureExpected; import org.hibernate.testing.orm.junit.RequiresDialect; import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.SessionFactory; @@ -21,7 +20,6 @@ * @author Jan Schatteman */ @RequiresDialect( value = H2Dialect.class ) -@FailureExpected( jiraKey = "HHH-1914", reason = "throws an UnsupportedOperationException") @DomainModel( annotatedClasses = { Cat.class, Woman.class, Man.class diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/ops/MergeTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/ops/MergeTest.java index 61e410e75756..56eb087b3a92 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/ops/MergeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/ops/MergeTest.java @@ -623,7 +623,7 @@ public void testMergeManaged(SessionFactoryScope scope) { session.getTransaction().commit(); assertInsertCount( 1, scope ); - assertUpdateCount( 0, scope ); + assertUpdateCount( 1, scope ); assertThat( root.getChildren().size(), is( 1 ) ); assertTrue( root.getChildren().contains( mergedChild ) ); From 61f8e44b3d8ad97456616c646c88ba236eddc33d Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Fri, 10 Jan 2025 13:27:02 +0100 Subject: [PATCH 2/2] HHH-1914 Remove PersistentBag copying if underlying bag is not a list --- .../collection/spi/PersistentBag.java | 144 +++++++++-------- .../spi/PersistentIdentifierBag.java | 147 ++++++++++-------- 2 files changed, 161 insertions(+), 130 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/collection/spi/PersistentBag.java b/hibernate-core/src/main/java/org/hibernate/collection/spi/PersistentBag.java index 0c3d65512f70..cd338fd7fc4e 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/spi/PersistentBag.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/spi/PersistentBag.java @@ -37,12 +37,19 @@ @Incubating public class PersistentBag extends AbstractPersistentCollection implements List { + /** + * @deprecated Use {@link #bagAsList()} or {@link #collection} instead. + */ + @Deprecated(forRemoval = true, since = "7") protected List bag; /** - * The Collection provided to a PersistentBag constructor + * The actual bag. + * For backwards compatibility reasons, the {@link #bag} field remains as {@link List}, + * but might be {@code null} when the bag does not implement the {@link List} interface, + * whereas this collection field will always contain the actual bag instance. */ - private Collection providedCollection; + protected Collection collection; /** * Constructs a PersistentBag. Needed for SOAP libraries, etc @@ -68,50 +75,53 @@ public PersistentBag(SharedSessionContractImplementor session) { */ public PersistentBag(SharedSessionContractImplementor session, Collection coll) { super( session ); - providedCollection = coll; - if ( coll instanceof List ) { - bag = (List) coll; - } - else { - bag = new ArrayList<>( coll ); - } + setCollection( coll ); setInitialized(); setDirectlyAccessible( true ); } - @Override - public boolean isWrapper(Object collection) { - return bag == collection; + private void setCollection(Collection bag) { + this.collection = bag; + this.bag = bag instanceof List list ? list : null; + } + + protected List bagAsList() { + if ( bag == null ) { + throw new IllegalStateException( "Bag is not a list: " + collection.getClass().getName() ); + } + return bag; } @Override - public boolean isDirectlyProvidedCollection(Object collection) { - return isDirectlyAccessible() && providedCollection == collection; + public boolean isWrapper(Object collection) { + return this.collection == collection; } @Override public boolean empty() { - return bag.isEmpty(); + return collection.isEmpty(); } @Override public Iterator entries(CollectionPersister persister) { - return bag.iterator(); + return collection.iterator(); } public void injectLoadedState(PluralAttributeMapping attributeMapping, List loadingState) { - assert bag == null; + assert collection == null; final CollectionPersister collectionDescriptor = attributeMapping.getCollectionDescriptor(); final CollectionSemantics collectionSemantics = collectionDescriptor.getCollectionSemantics(); final int elementCount = loadingState == null ? 0 : loadingState.size(); - this.bag = (List) collectionSemantics.instantiateRaw( elementCount, collectionDescriptor ); + //noinspection unchecked + setCollection( (Collection) collectionSemantics.instantiateRaw( elementCount, collectionDescriptor ) ); if ( loadingState != null ) { for ( int i = 0; i < elementCount; i++ ) { - bag.add( (E) loadingState.get( i ) ); + //noinspection unchecked + collection.add( (E) loadingState.get( i ) ); } } } @@ -120,12 +130,12 @@ public void injectLoadedState(PluralAttributeMapping attributeMapping, List l public boolean equalsSnapshot(CollectionPersister persister) throws HibernateException { final Type elementType = persister.getElementType(); final List sn = (List) getSnapshot(); - if ( sn.size() != bag.size() ) { + if ( sn.size() != collection.size() ) { return false; } // HHH-11032 - Group objects by Type.getHashCode() to reduce the complexity of the search - final Map> hashToInstancesBag = groupByEqualityHash( bag, elementType ); + final Map> hashToInstancesBag = groupByEqualityHash( collection, elementType ); final Map> hashToInstancesSn = groupByEqualityHash( sn, elementType ); if ( hashToInstancesBag.size() != hashToInstancesSn.size() ) { return false; @@ -170,7 +180,7 @@ public boolean equalsSnapshot(CollectionPersister persister) throws HibernateExc * * @return Map of "equality" hashCode to List of objects */ - private Map> groupByEqualityHash(List searchedBag, Type elementType) { + private Map> groupByEqualityHash(Collection searchedBag, Type elementType) { if ( searchedBag.isEmpty() ) { return Collections.emptyMap(); } @@ -223,8 +233,8 @@ private boolean expectOccurrences(Object element, List list, Type elemen @Override public Serializable getSnapshot(CollectionPersister persister) throws HibernateException { - final ArrayList clonedList = new ArrayList<>( bag.size() ); - for ( E item : bag ) { + final ArrayList clonedList = new ArrayList<>( collection.size() ); + for ( E item : collection ) { clonedList.add( (E) persister.getElementType().deepCopy( item, persister.getFactory() ) ); } return clonedList; @@ -233,23 +243,24 @@ public Serializable getSnapshot(CollectionPersister persister) @Override public Collection getOrphans(Serializable snapshot, String entityName) throws HibernateException { final List sn = (List) snapshot; - return getOrphans( sn, bag, entityName, getSession() ); + return getOrphans( sn, collection, entityName, getSession() ); } @Override public void initializeEmptyCollection(CollectionPersister persister) { - assert bag == null; + assert collection == null; //noinspection unchecked - bag = (List) persister.getCollectionSemantics().instantiateRaw( 0, persister ); + setCollection( (Collection) persister.getCollectionSemantics().instantiateRaw( 0, persister ) ); endRead(); } @Override public Object disassemble(CollectionPersister persister) { - final int length = bag.size(); + final int length = collection.size(); final Serializable[] result = new Serializable[length]; - for ( int i = 0; i < length; i++ ) { - result[i] = persister.getElementType().disassemble( bag.get( i ), getSession(), null ); + int i = 0; + for ( E element : collection ) { + result[i++] = persister.getElementType().disassemble( element, getSession(), null ); } return result; } @@ -257,17 +268,19 @@ public Object disassemble(CollectionPersister persister) { @Override public void initializeFromCache(CollectionPersister collectionDescriptor, Object disassembled, Object owner) throws HibernateException { - assert bag == null; + assert collection == null; final Serializable[] array = (Serializable[]) disassembled; final int size = array.length; - this.bag = (List) collectionDescriptor.getCollectionSemantics().instantiateRaw( size, collectionDescriptor ); + //noinspection unchecked + setCollection( (Collection) collectionDescriptor.getCollectionSemantics().instantiateRaw( size, collectionDescriptor ) ); for ( Serializable item : array ) { final Object element = collectionDescriptor.getElementType().assemble( item, getSession(), owner ); if ( element != null ) { - bag.add( (E) element ); + //noinspection unchecked + collection.add( (E) element ); } } } @@ -292,11 +305,12 @@ public Iterator getDeletes(CollectionPersister persister, boolean indexIsForm final List sn = (List) getSnapshot(); final Iterator olditer = sn.iterator(); int i = 0; + final Iterator bagiter = collection.iterator(); while ( olditer.hasNext() ) { final Object old = olditer.next(); - final Iterator newiter = bag.iterator(); + final Iterator newiter = collection.iterator(); boolean found = false; - if ( bag.size() > i && elementType.isSame( old, bag.get( i++ ) ) ) { + if ( collection.size() > i && i++ > 0 && elementType.isSame( old, bagiter.next() ) ) { //a shortcut if its location didn't change! found = true; } @@ -348,43 +362,43 @@ public boolean needsUpdating(Object entry, int i, Type elemType) { @Override public int size() { - return readSize() ? getCachedSize() : bag.size(); + return readSize() ? getCachedSize() : collection.size(); } @Override public boolean isEmpty() { - return readSize() ? getCachedSize() == 0 : bag.isEmpty(); + return readSize() ? getCachedSize() == 0 : collection.isEmpty(); } @Override public boolean contains(Object object) { final Boolean exists = readElementExistence( object ); - return exists == null ? bag.contains( object ) : exists; + return exists == null ? collection.contains( object ) : exists; } @Override public Iterator iterator() { read(); - return new IteratorProxy<>( bag.iterator() ); + return new IteratorProxy<>( collection.iterator() ); } @Override public Object[] toArray() { read(); - return bag.toArray(); + return collection.toArray(); } @Override public A[] toArray(A[] a) { read(); - return bag.toArray( a ); + return collection.toArray( a ); } @Override public boolean add(E object) { if ( !isOperationQueueEnabled() ) { write(); - return bag.add( object ); + return collection.add( object ); } else { queueOperation( new SimpleAdd( object ) ); @@ -395,7 +409,7 @@ public boolean add(E object) { @Override public boolean remove(Object o) { initialize( true ); - if ( bag.remove( o ) ) { + if ( collection.remove( o ) ) { elementRemoved = true; dirty(); return true; @@ -408,7 +422,7 @@ public boolean remove(Object o) { @Override public boolean containsAll(Collection c) { read(); - return bag.containsAll( c ); + return collection.containsAll( c ); } @Override @@ -418,7 +432,7 @@ public boolean addAll(Collection values) { } if ( !isOperationQueueEnabled() ) { write(); - return bag.addAll( values ); + return collection.addAll( values ); } else { for ( E value : values ) { @@ -432,7 +446,7 @@ public boolean addAll(Collection values) { public boolean removeAll(Collection c) { if ( c.size() > 0 ) { initialize( true ); - if ( bag.removeAll( c ) ) { + if ( collection.removeAll( c ) ) { elementRemoved = true; dirty(); return true; @@ -449,7 +463,7 @@ public boolean removeAll(Collection c) { @Override public boolean retainAll(Collection c) { initialize( true ); - if ( bag.retainAll( c ) ) { + if ( collection.retainAll( c ) ) { dirty(); return true; } @@ -465,8 +479,8 @@ public void clear() { } else { initialize( true ); - if ( !bag.isEmpty() ) { - bag.clear(); + if ( !collection.isEmpty() ) { + collection.clear(); dirty(); } } @@ -498,7 +512,7 @@ public Object getSnapshotElement(Object entry, int i) { @SuppressWarnings("unused") public int occurrences(Object o) { read(); - final Iterator itr = bag.iterator(); + final Iterator itr = collection.iterator(); int result = 0; while ( itr.hasNext() ) { if ( o.equals( itr.next() ) ) { @@ -513,14 +527,14 @@ public int occurrences(Object o) { @Override public void add(int i, E o) { write(); - bag.add( i, o ); + bagAsList().add( i, o ); } @Override public boolean addAll(int i, Collection c) { if ( c.size() > 0 ) { write(); - return bag.addAll( i, c ); + return bagAsList().addAll( i, c ); } else { return false; @@ -530,49 +544,49 @@ public boolean addAll(int i, Collection c) { @Override public E get(int i) { read(); - return bag.get( i ); + return bagAsList().get( i ); } @Override public int indexOf(Object o) { read(); - return bag.indexOf( o ); + return bagAsList().indexOf( o ); } @Override public int lastIndexOf(Object o) { read(); - return bag.lastIndexOf( o ); + return bagAsList().lastIndexOf( o ); } @Override public ListIterator listIterator() { read(); - return new ListIteratorProxy( bag.listIterator() ); + return new ListIteratorProxy( bagAsList().listIterator() ); } @Override public ListIterator listIterator(int i) { read(); - return new ListIteratorProxy( bag.listIterator( i ) ); + return new ListIteratorProxy( bagAsList().listIterator( i ) ); } @Override public E remove(int i) { write(); - return bag.remove( i ); + return bagAsList().remove( i ); } @Override public E set(int i, E o) { write(); - return bag.set( i, o ); + return bagAsList().set( i, o ); } @Override public List subList(int start, int end) { read(); - return new ListProxy( bag.subList( start, end ) ); + return new ListProxy( bagAsList().subList( start, end ) ); } @Override @@ -583,7 +597,7 @@ public boolean entryExists(Object entry, int i) { @Override public String toString() { read(); - return bag.toString(); + return collection.toString(); } /** @@ -607,7 +621,7 @@ public int hashCode() { final class Clear implements DelayedOperation { @Override public void operate() { - bag.clear(); + collection.clear(); } @Override @@ -635,8 +649,8 @@ public void operate() { // it can happen that an element is already associated with the collection after cascading, // but the queued operations are still executed after the lazy initialization of the collection. // To avoid duplicates, we have to check if the bag already contains this element - if ( !bag.contains( getAddedInstance() ) ) { - bag.add( getAddedInstance() ); + if ( !collection.contains( getAddedInstance() ) ) { + collection.add( getAddedInstance() ); } } } @@ -649,7 +663,7 @@ public SimpleRemove(E orphan) { @Override public void operate() { - bag.remove( getOrphan() ); + collection.remove( getOrphan() ); } } } diff --git a/hibernate-core/src/main/java/org/hibernate/collection/spi/PersistentIdentifierBag.java b/hibernate-core/src/main/java/org/hibernate/collection/spi/PersistentIdentifierBag.java index a76ccdc8841b..bf702cd7efe9 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/spi/PersistentIdentifierBag.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/spi/PersistentIdentifierBag.java @@ -41,13 +41,20 @@ */ @Incubating public class PersistentIdentifierBag extends AbstractPersistentCollection implements List { + /** + * @deprecated Use {@link #bagAsList()} or {@link #collection} instead. + */ + @Deprecated(forRemoval = true, since = "7") protected List values; protected Map identifiers; /** - * The Collection provided to a PersistentIdentifierBag constructor + * The actual bag. + * For backwards compatibility reasons, the {@link #values} field remains as {@link List}, + * but might be {@code null} when the bag does not implement the {@link List} interface, + * whereas this collection field will always contain the actual bag instance. */ - private Collection providedValues; + protected Collection collection; /** * Constructs a PersistentIdentifierBag. This form needed for SOAP libraries, etc @@ -73,18 +80,24 @@ public PersistentIdentifierBag(SharedSessionContractImplementor session) { */ public PersistentIdentifierBag(SharedSessionContractImplementor session, Collection coll) { super( session ); - providedValues = coll; - if (coll instanceof List) { - values = (List) coll; - } - else { - values = new ArrayList<>( coll ); - } + setCollection( coll ); setInitialized(); setDirectlyAccessible( true ); identifiers = new HashMap<>(); } + private void setCollection(Collection bag) { + this.collection = bag; + this.values = bag instanceof List list ? list : null; + } + + protected List bagAsList() { + if ( values == null ) { + throw new IllegalStateException( "Bag is not a list: " + collection.getClass().getName() ); + } + return values; + } + @Override public void initializeFromCache(CollectionPersister persister, Object disassembled, Object owner) throws HibernateException { @@ -92,19 +105,18 @@ public void initializeFromCache(CollectionPersister persister, Object disassembl final int size = array.length; assert identifiers == null; - assert values == null; + assert collection == null; identifiers = new HashMap<>(); - values = size <= 0 - ? new ArrayList<>() - : new ArrayList<>( size ); + //noinspection unchecked + setCollection( (Collection) persister.getCollectionSemantics().instantiateRaw( size, persister ) ); for ( int i = 0; i < size; i+=2 ) { identifiers.put( (i/2), persister.getIdentifierType().assemble( array[i], getSession(), owner ) ); - values.add( (E) persister.getElementType().assemble( array[i+1], getSession(), owner ) ); + collection.add( (E) persister.getElementType().assemble( array[i+1], getSession(), owner ) ); } } @@ -115,26 +127,21 @@ public Object getIdentifier(Object entry, int i) { @Override public boolean isWrapper(Object collection) { - return values == collection; - } - - @Override - public boolean isDirectlyProvidedCollection(Object collection) { - return isDirectlyAccessible() && providedValues == collection; + return this.collection == collection; } @Override public boolean add(E o) { write(); - values.add( o ); + collection.add( o ); return true; } @Override public void clear() { initialize( true ); - if ( ! values.isEmpty() || ! identifiers.isEmpty() ) { - values.clear(); + if ( ! collection.isEmpty() || ! identifiers.isEmpty() ) { + collection.clear(); identifiers.clear(); dirty(); } @@ -143,33 +150,33 @@ public void clear() { @Override public boolean contains(Object o) { read(); - return values.contains( o ); + return collection.contains( o ); } @Override public boolean containsAll(Collection c) { read(); - return values.containsAll( c ); + return collection.containsAll( c ); } @Override public boolean isEmpty() { - return readSize() ? getCachedSize()==0 : values.isEmpty(); + return readSize() ? getCachedSize()==0 : collection.isEmpty(); } @Override public Iterator iterator() { read(); - return new IteratorProxy<>( values.iterator() ); + return new IteratorProxy<>( collection.iterator() ); } @Override public boolean remove(Object o) { initialize( true ); - final int index = values.indexOf( o ); + final int index = bagAsList().indexOf( o ); if ( index >= 0 ) { beforeRemove( index ); - values.remove( index ); + bagAsList().remove( index ); elementRemoved = true; dirty(); return true; @@ -198,7 +205,7 @@ public boolean removeAll(Collection c) { @Override public boolean retainAll(Collection c) { initialize( true ); - if ( values.retainAll( c ) ) { + if ( collection.retainAll( c ) ) { dirty(); return true; } @@ -209,41 +216,42 @@ public boolean retainAll(Collection c) { @Override public int size() { - return readSize() ? getCachedSize() : values.size(); + return readSize() ? getCachedSize() : collection.size(); } @Override public Object[] toArray() { read(); - return values.toArray(); + return collection.toArray(); } @Override public A[] toArray(A[] a) { read(); - return values.toArray( a ); + return collection.toArray( a ); } @Override public Object disassemble(CollectionPersister persister) { - final Serializable[] result = new Serializable[ values.size() * 2 ]; + final Serializable[] result = new Serializable[ collection.size() * 2 ]; int i = 0; - for ( int j=0; j< values.size(); j++ ) { - final Object value = values.get( j ); + int j = 0; + for ( E value : collection ) { result[i++] = persister.getIdentifierType().disassemble( identifiers.get( j ), getSession(), null ); result[i++] = persister.getElementType().disassemble( value, getSession(), null ); + j++; } return result; } @Override public boolean empty() { - return values.isEmpty(); + return collection.isEmpty(); } @Override public Iterator entries(CollectionPersister persister) { - return values.iterator(); + return collection.iterator(); } @Override @@ -255,12 +263,12 @@ public boolean entryExists(Object entry, int i) { public boolean equalsSnapshot(CollectionPersister persister) throws HibernateException { final Type elementType = persister.getElementType(); final Map snap = (Map) getSnapshot(); - if ( snap.size()!= values.size() ) { + if ( snap.size()!= collection.size() ) { return false; } - for ( int i=0; i getDeletes(CollectionPersister persister, boolean indexIsFormula) throws HibernateException { final Map snap = (Map) getSnapshot(); final List deletes = new ArrayList<>( snap.keySet() ); - for ( int i=0; i map = CollectionHelper.mapOfSize( values.size() ); - final Iterator iter = values.iterator(); + final HashMap map = CollectionHelper.mapOfSize( collection.size() ); + final Iterator iter = collection.iterator(); int i=0; while ( iter.hasNext() ) { final Object value = iter.next(); @@ -349,20 +359,21 @@ public Serializable getSnapshot(CollectionPersister persister) throws HibernateE @Override public Collection getOrphans(Serializable snapshot, String entityName) throws HibernateException { final Map sn = (Map) snapshot; - return getOrphans( sn.values(), values, entityName, getSession() ); + return getOrphans( sn.values(), collection, entityName, getSession() ); } @Override public void initializeEmptyCollection(CollectionPersister persister) { assert identifiers == null; identifiers = new HashMap<>(); - values = new ArrayList<>(); + //noinspection unchecked + setCollection( (Collection) persister.getCollectionSemantics().instantiateRaw( 0, persister ) ); endRead(); } @Override public void preInsert(CollectionPersister persister) throws HibernateException { - final Iterator itr = values.iterator(); + final Iterator itr = collection.iterator(); int i = 0; while ( itr.hasNext() ) { final E entry = itr.next(); @@ -379,7 +390,7 @@ public void preInsert(CollectionPersister persister) throws HibernateException { public void add(int index, E element) { write(); beforeAdd( index ); - values.add( index, element ); + bagAsList().add( index, element ); } @Override @@ -398,36 +409,36 @@ public boolean addAll(int index, Collection c) { @Override public E get(int index) { read(); - return values.get( index ); + return bagAsList().get( index ); } @Override public int indexOf(Object o) { read(); - return values.indexOf( o ); + return bagAsList().indexOf( o ); } @Override public int lastIndexOf(Object o) { read(); - return values.lastIndexOf( o ); + return bagAsList().lastIndexOf( o ); } @Override public ListIterator listIterator() { read(); - return new ListIteratorProxy( values.listIterator() ); + return new ListIteratorProxy( bagAsList().listIterator() ); } @Override public ListIterator listIterator(int index) { read(); - return new ListIteratorProxy( values.listIterator( index ) ); + return new ListIteratorProxy( bagAsList().listIterator( index ) ); } private void beforeRemove(int index) { final Object removedId = identifiers.get( index ); - final int last = values.size()-1; + final int last = collection.size()-1; for ( int i=index; i subList(int fromIndex, int toIndex) { read(); - return new ListProxy( values.subList( fromIndex, toIndex ) ); + return new ListProxy( bagAsList().subList( fromIndex, toIndex ) ); } @Override public boolean addAll(Collection c) { if ( c.size()> 0 ) { write(); - return values.addAll( c ); + return collection.addAll( c ); } else { return false; @@ -487,20 +498,26 @@ public void afterRowInsert( public void injectLoadedState(PluralAttributeMapping attributeMapping, List loadingState) { assert identifiers == null; - assert values == null; + assert collection == null; + + final CollectionPersister collectionDescriptor = attributeMapping.getCollectionDescriptor(); + final CollectionSemantics collectionSemantics = collectionDescriptor.getCollectionSemantics(); + + final int elementCount = loadingState == null ? 0 : loadingState.size(); identifiers = new HashMap<>(); - values = new ArrayList<>( loadingState.size() ); + //noinspection unchecked + setCollection( (Collection) collectionSemantics.instantiateRaw( elementCount, collectionDescriptor ) ); for ( int i = 0; i < loadingState.size(); i++ ) { final Object[] row = (Object[]) loadingState.get( i ); final Object identifier = row[0]; final E element = (E) row[1]; - Object old = identifiers.put( values.size(), identifier ); + Object old = identifiers.put( collection.size(), identifier ); if ( old == null ) { //maintain correct duplication if loaded in a cartesian product - values.add( element ); + collection.add( element ); } } }