1 /*
2 * Hibernate, Relational Persistence for Idiomatic Java
3 *
4 * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
5 * indicated by the @author tags or express copyright attribution
6 * statements applied by the authors. All third-party contributions are
7 * distributed under license by Red Hat Middleware LLC.
8 *
9 * This copyrighted material is made available to anyone wishing to use, modify,
10 * copy, or redistribute it subject to the terms and conditions of the GNU
11 * Lesser General Public License, as published by the Free Software Foundation.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
16 * for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public License
19 * along with this distribution; if not, write to:
20 * Free Software Foundation, Inc.
21 * 51 Franklin Street, Fifth Floor
22 * Boston, MA 02110-1301 USA
23 *
24 */
25 package org.hibernate.collection;
26
27 import java.io.Serializable;
28 import java.sql.ResultSet;
29 import java.sql.SQLException;
30 import java.util.ArrayList;
31 import java.util.Collection;
32 import java.util.Iterator;
33 import java.util.List;
34 import java.util.ListIterator;
35
36 import org.hibernate.EntityMode;
37 import org.hibernate.HibernateException;
38 import org.hibernate.engine.SessionImplementor;
39 import org.hibernate.loader.CollectionAliases;
40 import org.hibernate.persister.collection.CollectionPersister;
41 import org.hibernate.type.Type;
42
43 /**
44 * An unordered, unkeyed collection that can contain the same element
45 * multiple times. The Java collections API, curiously, has no <tt>Bag</tt>.
46 * Most developers seem to use <tt>List</tt>s to represent bag semantics,
47 * so Hibernate follows this practice.
48 *
49 * @author Gavin King
50 */
51 public class PersistentBag extends AbstractPersistentCollection implements List {
52
53 protected List bag;
54
55 public PersistentBag(SessionImplementor session) {
56 super(session);
57 }
58
59 public PersistentBag(SessionImplementor session, Collection coll) {
60 super(session);
61 if (coll instanceof List) {
62 bag = (List) coll;
63 }
64 else {
65 bag = new ArrayList();
66 Iterator iter = coll.iterator();
67 while ( iter.hasNext() ) {
68 bag.add( iter.next() );
69 }
70 }
71 setInitialized();
72 setDirectlyAccessible(true);
73 }
74
75 public PersistentBag() {} //needed for SOAP libraries, etc
76
77 public boolean isWrapper(Object collection) {
78 return bag==collection;
79 }
80 public boolean empty() {
81 return bag.isEmpty();
82 }
83
84 public Iterator entries(CollectionPersister persister) {
85 return bag.iterator();
86 }
87
88 public Object readFrom(ResultSet rs, CollectionPersister persister, CollectionAliases descriptor, Object owner)
89 throws HibernateException, SQLException {
90 // note that if we load this collection from a cartesian product
91 // the multiplicity would be broken ... so use an idbag instead
92 Object element = persister.readElement( rs, owner, descriptor.getSuffixedElementAliases(), getSession() ) ;
93 if (element!=null) bag.add(element);
94 return element;
95 }
96
97 public void beforeInitialize(CollectionPersister persister, int anticipatedSize) {
98 this.bag = ( List ) persister.getCollectionType().instantiate( anticipatedSize );
99 }
100
101 public boolean equalsSnapshot(CollectionPersister persister) throws HibernateException {
102 Type elementType = persister.getElementType();
103 EntityMode entityMode = getSession().getEntityMode();
104 List sn = (List) getSnapshot();
105 if ( sn.size()!=bag.size() ) return false;
106 Iterator iter = bag.iterator();
107 while ( iter.hasNext() ) {
108 Object elt = iter.next();
109 final boolean unequal = countOccurrences(elt, bag, elementType, entityMode) !=
110 countOccurrences(elt, sn, elementType, entityMode);
111 if ( unequal ) return false;
112 }
113 return true;
114 }
115
116 public boolean isSnapshotEmpty(Serializable snapshot) {
117 return ( (Collection) snapshot ).isEmpty();
118 }
119
120 private int countOccurrences(Object element, List list, Type elementType, EntityMode entityMode)
121 throws HibernateException {
122 Iterator iter = list.iterator();
123 int result=0;
124 while ( iter.hasNext() ) {
125 if ( elementType.isSame( element, iter.next(), entityMode ) ) result++;
126 }
127 return result;
128 }
129
130 public Serializable getSnapshot(CollectionPersister persister)
131 throws HibernateException {
132 EntityMode entityMode = getSession().getEntityMode();
133 ArrayList clonedList = new ArrayList( bag.size() );
134 Iterator iter = bag.iterator();
135 while ( iter.hasNext() ) {
136 clonedList.add( persister.getElementType().deepCopy( iter.next(), entityMode, persister.getFactory() ) );
137 }
138 return clonedList;
139 }
140
141 public Collection getOrphans(Serializable snapshot, String entityName) throws HibernateException {
142 List sn = (List) snapshot;
143 return getOrphans( sn, bag, entityName, getSession() );
144 }
145
146
147 public Serializable disassemble(CollectionPersister persister)
148 throws HibernateException {
149
150 int length = bag.size();
151 Serializable[] result = new Serializable[length];
152 for ( int i=0; i<length; i++ ) {
153 result[i] = persister.getElementType().disassemble( bag.get(i), getSession(), null );
154 }
155 return result;
156 }
157
158 public void initializeFromCache(CollectionPersister persister, Serializable disassembled, Object owner)
159 throws HibernateException {
160 Serializable[] array = (Serializable[]) disassembled;
161 int size = array.length;
162 beforeInitialize( persister, size );
163 for ( int i = 0; i < size; i++ ) {
164 Object element = persister.getElementType().assemble( array[i], getSession(), owner );
165 if ( element!=null ) {
166 bag.add( element );
167 }
168 }
169 }
170
171 public boolean needsRecreate(CollectionPersister persister) {
172 return !persister.isOneToMany();
173 }
174
175
176 // For a one-to-many, a <bag> is not really a bag;
177 // it is *really* a set, since it can't contain the
178 // same element twice. It could be considered a bug
179 // in the mapping dtd that <bag> allows <one-to-many>.
180
181 // Anyway, here we implement <set> semantics for a
182 // <one-to-many> <bag>!
183
184 public Iterator getDeletes(CollectionPersister persister, boolean indexIsFormula) throws HibernateException {
185 //if ( !persister.isOneToMany() ) throw new AssertionFailure("Not implemented for Bags");
186 Type elementType = persister.getElementType();
187 EntityMode entityMode = getSession().getEntityMode();
188 ArrayList deletes = new ArrayList();
189 List sn = (List) getSnapshot();
190 Iterator olditer = sn.iterator();
191 int i=0;
192 while ( olditer.hasNext() ) {
193 Object old = olditer.next();
194 Iterator newiter = bag.iterator();
195 boolean found = false;
196 if ( bag.size()>i && elementType.isSame( old, bag.get(i++), entityMode ) ) {
197 //a shortcut if its location didn't change!
198 found = true;
199 }
200 else {
201 //search for it
202 //note that this code is incorrect for other than one-to-many
203 while ( newiter.hasNext() ) {
204 if ( elementType.isSame( old, newiter.next(), entityMode ) ) {
205 found = true;
206 break;
207 }
208 }
209 }
210 if (!found) deletes.add(old);
211 }
212 return deletes.iterator();
213 }
214
215 public boolean needsInserting(Object entry, int i, Type elemType) throws HibernateException {
216 //if ( !persister.isOneToMany() ) throw new AssertionFailure("Not implemented for Bags");
217 List sn = (List) getSnapshot();
218 final EntityMode entityMode = getSession().getEntityMode();
219 if ( sn.size()>i && elemType.isSame( sn.get(i), entry, entityMode ) ) {
220 //a shortcut if its location didn't change!
221 return false;
222 }
223 else {
224 //search for it
225 //note that this code is incorrect for other than one-to-many
226 Iterator olditer = sn.iterator();
227 while ( olditer.hasNext() ) {
228 Object old = olditer.next();
229 if ( elemType.isSame( old, entry, entityMode ) ) return false;
230 }
231 return true;
232 }
233 }
234
235 public boolean isRowUpdatePossible() {
236 return false;
237 }
238
239 public boolean needsUpdating(Object entry, int i, Type elemType) {
240 //if ( !persister.isOneToMany() ) throw new AssertionFailure("Not implemented for Bags");
241 return false;
242 }
243
244 /**
245 * @see java.util.Collection#size()
246 */
247 public int size() {
248 return readSize() ? getCachedSize() : bag.size();
249 }
250
251 /**
252 * @see java.util.Collection#isEmpty()
253 */
254 public boolean isEmpty() {
255 return readSize() ? getCachedSize()==0 : bag.isEmpty();
256 }
257
258 /**
259 * @see java.util.Collection#contains(Object)
260 */
261 public boolean contains(Object object) {
262 Boolean exists = readElementExistence(object);
263 return exists==null ?
264 bag.contains(object) :
265 exists.booleanValue();
266 }
267
268 /**
269 * @see java.util.Collection#iterator()
270 */
271 public Iterator iterator() {
272 read();
273 return new IteratorProxy( bag.iterator() );
274 }
275
276 /**
277 * @see java.util.Collection#toArray()
278 */
279 public Object[] toArray() {
280 read();
281 return bag.toArray();
282 }
283
284 /**
285 * @see java.util.Collection#toArray(Object[])
286 */
287 public Object[] toArray(Object[] a) {
288 read();
289 return bag.toArray(a);
290 }
291
292 /**
293 * @see java.util.Collection#add(Object)
294 */
295 public boolean add(Object object) {
296 if ( !isOperationQueueEnabled() ) {
297 write();
298 return bag.add(object);
299 }
300 else {
301 queueOperation( new SimpleAdd(object) );
302 return true;
303 }
304 }
305
306 /**
307 * @see java.util.Collection#remove(Object)
308 */
309 public boolean remove(Object o) {
310 initialize( true );
311 if ( bag.remove( o ) ) {
312 dirty();
313 return true;
314 }
315 else {
316 return false;
317 }
318 }
319
320 /**
321 * @see java.util.Collection#containsAll(Collection)
322 */
323 public boolean containsAll(Collection c) {
324 read();
325 return bag.containsAll(c);
326 }
327
328 /**
329 * @see java.util.Collection#addAll(Collection)
330 */
331 public boolean addAll(Collection values) {
332 if ( values.size()==0 ) return false;
333 if ( !isOperationQueueEnabled() ) {
334 write();
335 return bag.addAll(values);
336 }
337 else {
338 Iterator iter = values.iterator();
339 while ( iter.hasNext() ) {
340 queueOperation( new SimpleAdd( iter.next() ) );
341 }
342 return values.size()>0;
343 }
344 }
345
346 /**
347 * @see java.util.Collection#removeAll(Collection)
348 */
349 public boolean removeAll(Collection c) {
350 if ( c.size()>0 ) {
351 initialize( true );
352 if ( bag.removeAll( c ) ) {
353 dirty();
354 return true;
355 }
356 else {
357 return false;
358 }
359 }
360 else {
361 return false;
362 }
363 }
364
365 /**
366 * @see java.util.Collection#retainAll(Collection)
367 */
368 public boolean retainAll(Collection c) {
369 initialize( true );
370 if ( bag.retainAll( c ) ) {
371 dirty();
372 return true;
373 }
374 else {
375 return false;
376 }
377 }
378
379 /**
380 * @see java.util.Collection#clear()
381 */
382 public void clear() {
383 if ( isClearQueueEnabled() ) {
384 queueOperation( new Clear() );
385 }
386 else {
387 initialize( true );
388 if ( ! bag.isEmpty() ) {
389 bag.clear();
390 dirty();
391 }
392 }
393 }
394
395 public Object getIndex(Object entry, int i, CollectionPersister persister) {
396 throw new UnsupportedOperationException("Bags don't have indexes");
397 }
398
399 public Object getElement(Object entry) {
400 return entry;
401 }
402
403 public Object getSnapshotElement(Object entry, int i) {
404 List sn = (List) getSnapshot();
405 return sn.get(i);
406 }
407
408 public int occurrences(Object o) {
409 read();
410 Iterator iter = bag.iterator();
411 int result=0;
412 while ( iter.hasNext() ) {
413 if ( o.equals( iter.next() ) ) result++;
414 }
415 return result;
416 }
417
418 // List OPERATIONS:
419
420 /**
421 * @see java.util.List#add(int, Object)
422 */
423 public void add(int i, Object o) {
424 write();
425 bag.add(i, o);
426 }
427
428 /**
429 * @see java.util.List#addAll(int, Collection)
430 */
431 public boolean addAll(int i, Collection c) {
432 if ( c.size()>0 ) {
433 write();
434 return bag.addAll(i, c);
435 }
436 else {
437 return false;
438 }
439 }
440
441 /**
442 * @see java.util.List#get(int)
443 */
444 public Object get(int i) {
445 read();
446 return bag.get(i);
447 }
448
449 /**
450 * @see java.util.List#indexOf(Object)
451 */
452 public int indexOf(Object o) {
453 read();
454 return bag.indexOf(o);
455 }
456
457 /**
458 * @see java.util.List#lastIndexOf(Object)
459 */
460 public int lastIndexOf(Object o) {
461 read();
462 return bag.lastIndexOf(o);
463 }
464
465 /**
466 * @see java.util.List#listIterator()
467 */
468 public ListIterator listIterator() {
469 read();
470 return new ListIteratorProxy( bag.listIterator() );
471 }
472
473 /**
474 * @see java.util.List#listIterator(int)
475 */
476 public ListIterator listIterator(int i) {
477 read();
478 return new ListIteratorProxy( bag.listIterator(i) );
479 }
480
481 /**
482 * @see java.util.List#remove(int)
483 */
484 public Object remove(int i) {
485 write();
486 return bag.remove(i);
487 }
488
489 /**
490 * @see java.util.List#set(int, Object)
491 */
492 public Object set(int i, Object o) {
493 write();
494 return bag.set(i, o);
495 }
496
497 /**
498 * @see java.util.List#subList(int, int)
499 */
500 public List subList(int start, int end) {
501 read();
502 return new ListProxy( bag.subList(start, end) );
503 }
504
505 public String toString() {
506 read();
507 return bag.toString();
508 }
509
510 /*public boolean equals(Object other) {
511 read();
512 return bag.equals(other);
513 }
514
515 public int hashCode(Object other) {
516 read();
517 return bag.hashCode();
518 }*/
519
520 public boolean entryExists(Object entry, int i) {
521 return entry!=null;
522 }
523
524 /**
525 * Bag does not respect the collection API and do an
526 * JVM instance comparison to do the equals.
527 * The semantic is broken not to have to initialize a
528 * collection for a simple equals() operation.
529 * @see java.lang.Object#equals(java.lang.Object)
530 */
531 public boolean equals(Object obj) {
532 return super.equals(obj);
533 }
534
535 /**
536 * @see java.lang.Object#hashCode()
537 */
538 public int hashCode() {
539 return super.hashCode();
540 }
541
542 final class Clear implements DelayedOperation {
543 public void operate() {
544 bag.clear();
545 }
546 public Object getAddedInstance() {
547 return null;
548 }
549 public Object getOrphan() {
550 throw new UnsupportedOperationException("queued clear cannot be used with orphan delete");
551 }
552 }
553
554 final class SimpleAdd implements DelayedOperation {
555 private Object value;
556
557 public SimpleAdd(Object value) {
558 this.value = value;
559 }
560 public void operate() {
561 bag.add(value);
562 }
563 public Object getAddedInstance() {
564 return value;
565 }
566 public Object getOrphan() {
567 return null;
568 }
569 }
570
571 }