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.HashMap;
33 import java.util.Iterator;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.Set;
37
38 import org.hibernate.EntityMode;
39 import org.hibernate.HibernateException;
40 import org.hibernate.engine.SessionImplementor;
41 import org.hibernate.loader.CollectionAliases;
42 import org.hibernate.persister.collection.CollectionPersister;
43 import org.hibernate.type.Type;
44
45
46 /**
47 * A persistent wrapper for a <tt>java.util.Map</tt>. Underlying collection
48 * is a <tt>HashMap</tt>.
49 *
50 * @see java.util.HashMap
51 * @author Gavin King
52 */
53 public class PersistentMap extends AbstractPersistentCollection implements Map {
54
55 protected Map map;
56
57 /**
58 * Empty constructor.
59 * <p/>
60 * Note: this form is not ever ever ever used by Hibernate; it is, however,
61 * needed for SOAP libraries and other such marshalling code.
62 */
63 public PersistentMap() {
64 // intentionally empty
65 }
66
67 /**
68 * Instantiates a lazy map (the underlying map is un-initialized).
69 *
70 * @param session The session to which this map will belong.
71 */
72 public PersistentMap(SessionImplementor session) {
73 super(session);
74 }
75
76 /**
77 * Instantiates a non-lazy map (the underlying map is constructed
78 * from the incoming map reference).
79 *
80 * @param session The session to which this map will belong.
81 * @param map The underlying map data.
82 */
83 public PersistentMap(SessionImplementor session, Map map) {
84 super(session);
85 this.map = map;
86 setInitialized();
87 setDirectlyAccessible(true);
88 }
89
90 public Serializable getSnapshot(CollectionPersister persister) throws HibernateException {
91 EntityMode entityMode = getSession().getEntityMode();
92 HashMap clonedMap = new HashMap( map.size() );
93 Iterator iter = map.entrySet().iterator();
94 while ( iter.hasNext() ) {
95 Map.Entry e = (Map.Entry) iter.next();
96 final Object copy = persister.getElementType()
97 .deepCopy( e.getValue(), entityMode, persister.getFactory() );
98 clonedMap.put( e.getKey(), copy );
99 }
100 return clonedMap;
101 }
102
103 public Collection getOrphans(Serializable snapshot, String entityName) throws HibernateException {
104 Map sn = (Map) snapshot;
105 return getOrphans( sn.values(), map.values(), entityName, getSession() );
106 }
107
108 public boolean equalsSnapshot(CollectionPersister persister) throws HibernateException {
109 Type elementType = persister.getElementType();
110 Map xmap = (Map) getSnapshot();
111 if ( xmap.size()!=this.map.size() ) return false;
112 Iterator iter = map.entrySet().iterator();
113 while ( iter.hasNext() ) {
114 Map.Entry entry = (Map.Entry) iter.next();
115 if ( elementType.isDirty( entry.getValue(), xmap.get( entry.getKey() ), getSession() ) ) return false;
116 }
117 return true;
118 }
119
120 public boolean isSnapshotEmpty(Serializable snapshot) {
121 return ( (Map) snapshot ).isEmpty();
122 }
123
124 public boolean isWrapper(Object collection) {
125 return map==collection;
126 }
127
128 public void beforeInitialize(CollectionPersister persister, int anticipatedSize) {
129 this.map = ( Map ) persister.getCollectionType().instantiate( anticipatedSize );
130 }
131
132
133 /**
134 * @see java.util.Map#size()
135 */
136 public int size() {
137 return readSize() ? getCachedSize() : map.size();
138 }
139
140 /**
141 * @see java.util.Map#isEmpty()
142 */
143 public boolean isEmpty() {
144 return readSize() ? getCachedSize()==0 : map.isEmpty();
145 }
146
147 /**
148 * @see java.util.Map#containsKey(Object)
149 */
150 public boolean containsKey(Object key) {
151 Boolean exists = readIndexExistence(key);
152 return exists==null ? map.containsKey(key) : exists.booleanValue();
153 }
154
155 /**
156 * @see java.util.Map#containsValue(Object)
157 */
158 public boolean containsValue(Object value) {
159 Boolean exists = readElementExistence(value);
160 return exists==null ?
161 map.containsValue(value) :
162 exists.booleanValue();
163 }
164
165 /**
166 * @see java.util.Map#get(Object)
167 */
168 public Object get(Object key) {
169 Object result = readElementByIndex(key);
170 return result==UNKNOWN ? map.get(key) : result;
171 }
172
173 /**
174 * @see java.util.Map#put(Object, Object)
175 */
176 public Object put(Object key, Object value) {
177 if ( isPutQueueEnabled() ) {
178 Object old = readElementByIndex( key );
179 if ( old != UNKNOWN ) {
180 queueOperation( new Put( key, value, old ) );
181 return old;
182 }
183 }
184 initialize( true );
185 Object old = map.put( key, value );
186 // would be better to use the element-type to determine
187 // whether the old and the new are equal here; the problem being
188 // we do not necessarily have access to the element type in all
189 // cases
190 if ( value != old ) {
191 dirty();
192 }
193 return old;
194 }
195
196 /**
197 * @see java.util.Map#remove(Object)
198 */
199 public Object remove(Object key) {
200 if ( isPutQueueEnabled() ) {
201 Object old = readElementByIndex( key );
202 queueOperation( new Remove( key, old ) );
203 return old;
204 }
205 else {
206 // TODO : safe to interpret "map.remove(key) == null" as non-dirty?
207 initialize( true );
208 if ( map.containsKey( key ) ) {
209 dirty();
210 }
211 return map.remove( key );
212 }
213 }
214
215 /**
216 * @see java.util.Map#putAll(java.util.Map puts)
217 */
218 public void putAll(Map puts) {
219 if ( puts.size()>0 ) {
220 initialize( true );
221 Iterator itr = puts.entrySet().iterator();
222 while ( itr.hasNext() ) {
223 Map.Entry entry = ( Entry ) itr.next();
224 put( entry.getKey(), entry.getValue() );
225 }
226 }
227 }
228
229 /**
230 * @see java.util.Map#clear()
231 */
232 public void clear() {
233 if ( isClearQueueEnabled() ) {
234 queueOperation( new Clear() );
235 }
236 else {
237 initialize( true );
238 if ( ! map.isEmpty() ) {
239 dirty();
240 map.clear();
241 }
242 }
243 }
244
245 /**
246 * @see java.util.Map#keySet()
247 */
248 public Set keySet() {
249 read();
250 return new SetProxy( map.keySet() );
251 }
252
253 /**
254 * @see java.util.Map#values()
255 */
256 public Collection values() {
257 read();
258 return new SetProxy( map.values() );
259 }
260
261 /**
262 * @see java.util.Map#entrySet()
263 */
264 public Set entrySet() {
265 read();
266 return new EntrySetProxy( map.entrySet() );
267 }
268
269 public boolean empty() {
270 return map.isEmpty();
271 }
272
273 public String toString() {
274 read();
275 return map.toString();
276 }
277
278 public Object readFrom(ResultSet rs, CollectionPersister persister, CollectionAliases descriptor, Object owner)
279 throws HibernateException, SQLException {
280 Object element = persister.readElement( rs, owner, descriptor.getSuffixedElementAliases(), getSession() );
281 Object index = persister.readIndex( rs, descriptor.getSuffixedIndexAliases(), getSession() );
282 if ( element!=null ) map.put(index, element);
283 return element;
284 }
285
286 public Iterator entries(CollectionPersister persister) {
287 return map.entrySet().iterator();
288 }
289
290 /** a wrapper for Map.Entry sets */
291 class EntrySetProxy implements Set {
292 private final Set set;
293 EntrySetProxy(Set set) {
294 this.set=set;
295 }
296 public boolean add(Object entry) {
297 //write(); -- doesn't
298 return set.add(entry);
299 }
300 public boolean addAll(Collection entries) {
301 //write(); -- doesn't
302 return set.addAll(entries);
303 }
304 public void clear() {
305 write();
306 set.clear();
307 }
308 public boolean contains(Object entry) {
309 return set.contains(entry);
310 }
311 public boolean containsAll(Collection entries) {
312 return set.containsAll(entries);
313 }
314 public boolean isEmpty() {
315 return set.isEmpty();
316 }
317 public Iterator iterator() {
318 return new EntryIteratorProxy( set.iterator() );
319 }
320 public boolean remove(Object entry) {
321 write();
322 return set.remove(entry);
323 }
324 public boolean removeAll(Collection entries) {
325 write();
326 return set.removeAll(entries);
327 }
328 public boolean retainAll(Collection entries) {
329 write();
330 return set.retainAll(entries);
331 }
332 public int size() {
333 return set.size();
334 }
335 // amazingly, these two will work because AbstractCollection
336 // uses iterator() to fill the array
337 public Object[] toArray() {
338 return set.toArray();
339 }
340 public Object[] toArray(Object[] array) {
341 return set.toArray(array);
342 }
343 }
344 final class EntryIteratorProxy implements Iterator {
345 private final Iterator iter;
346 EntryIteratorProxy(Iterator iter) {
347 this.iter=iter;
348 }
349 public boolean hasNext() {
350 return iter.hasNext();
351 }
352 public Object next() {
353 return new MapEntryProxy( (Map.Entry) iter.next() );
354 }
355 public void remove() {
356 write();
357 iter.remove();
358 }
359 }
360
361 final class MapEntryProxy implements Map.Entry {
362 private final Map.Entry me;
363 MapEntryProxy( Map.Entry me ) {
364 this.me = me;
365 }
366 public Object getKey() { return me.getKey(); }
367 public Object getValue() { return me.getValue(); }
368 public boolean equals(Object o) { return me.equals(o); }
369 public int hashCode() { return me.hashCode(); }
370 // finally, what it's all about...
371 public Object setValue(Object value) {
372 write();
373 return me.setValue(value);
374 }
375 }
376
377 public void initializeFromCache(CollectionPersister persister, Serializable disassembled, Object owner)
378 throws HibernateException {
379 Serializable[] array = ( Serializable[] ) disassembled;
380 int size = array.length;
381 beforeInitialize( persister, size );
382 for ( int i = 0; i < size; i+=2 ) {
383 map.put(
384 persister.getIndexType().assemble( array[i], getSession(), owner ),
385 persister.getElementType().assemble( array[i+1], getSession(), owner )
386 );
387 }
388 }
389
390 public Serializable disassemble(CollectionPersister persister) throws HibernateException {
391
392 Serializable[] result = new Serializable[ map.size() * 2 ];
393 Iterator iter = map.entrySet().iterator();
394 int i=0;
395 while ( iter.hasNext() ) {
396 Map.Entry e = (Map.Entry) iter.next();
397 result[i++] = persister.getIndexType().disassemble( e.getKey(), getSession(), null );
398 result[i++] = persister.getElementType().disassemble( e.getValue(), getSession(), null );
399 }
400 return result;
401
402 }
403
404 public Iterator getDeletes(CollectionPersister persister, boolean indexIsFormula)
405 throws HibernateException {
406 List deletes = new ArrayList();
407 Iterator iter = ( (Map) getSnapshot() ).entrySet().iterator();
408 while ( iter.hasNext() ) {
409 Map.Entry e = (Map.Entry) iter.next();
410 Object key = e.getKey();
411 if ( e.getValue()!=null && map.get(key)==null ) {
412 deletes.add( indexIsFormula ? e.getValue() : key );
413 }
414 }
415 return deletes.iterator();
416 }
417
418 public boolean needsInserting(Object entry, int i, Type elemType)
419 throws HibernateException {
420 final Map sn = (Map) getSnapshot();
421 Map.Entry e = (Map.Entry) entry;
422 return e.getValue()!=null && sn.get( e.getKey() )==null;
423 }
424
425 public boolean needsUpdating(Object entry, int i, Type elemType)
426 throws HibernateException {
427 final Map sn = (Map) getSnapshot();
428 Map.Entry e = (Map.Entry) entry;
429 Object snValue = sn.get( e.getKey() );
430 return e.getValue()!=null &&
431 snValue!=null &&
432 elemType.isDirty( snValue, e.getValue(), getSession() );
433 }
434
435
436 public Object getIndex(Object entry, int i, CollectionPersister persister) {
437 return ( (Map.Entry) entry ).getKey();
438 }
439
440 public Object getElement(Object entry) {
441 return ( (Map.Entry) entry ).getValue();
442 }
443
444 public Object getSnapshotElement(Object entry, int i) {
445 final Map sn = (Map) getSnapshot();
446 return sn.get( ( (Map.Entry) entry ).getKey() );
447 }
448
449 public boolean equals(Object other) {
450 read();
451 return map.equals(other);
452 }
453
454 public int hashCode() {
455 read();
456 return map.hashCode();
457 }
458
459 public boolean entryExists(Object entry, int i) {
460 return ( (Map.Entry) entry ).getValue()!=null;
461 }
462
463 final class Clear implements DelayedOperation {
464 public void operate() {
465 map.clear();
466 }
467 public Object getAddedInstance() {
468 return null;
469 }
470 public Object getOrphan() {
471 throw new UnsupportedOperationException("queued clear cannot be used with orphan delete");
472 }
473 }
474
475 final class Put implements DelayedOperation {
476 private Object index;
477 private Object value;
478 private Object old;
479
480 public Put(Object index, Object value, Object old) {
481 this.index = index;
482 this.value = value;
483 this.old = old;
484 }
485 public void operate() {
486 map.put(index, value);
487 }
488 public Object getAddedInstance() {
489 return value;
490 }
491 public Object getOrphan() {
492 return old;
493 }
494 }
495
496 final class Remove implements DelayedOperation {
497 private Object index;
498 private Object old;
499
500 public Remove(Object index, Object old) {
501 this.index = index;
502 this.old = old;
503 }
504 public void operate() {
505 map.remove(index);
506 }
507 public Object getAddedInstance() {
508 return null;
509 }
510 public Object getOrphan() {
511 return old;
512 }
513 }
514 }