KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > triactive > jdo > sco > HashMap


1 /*
2  * Copyright 2004 (C) TJDO.
3  * All rights reserved.
4  *
5  * This software is distributed under the terms of the TJDO License version 1.0.
6  * See the terms of the TJDO License in the documentation provided with this software.
7  *
8  * $Id: HashMap.java,v 1.3 2004/01/18 19:15:30 jackknifebarber Exp $
9  */

10
11 package com.triactive.jdo.sco;
12
13 import com.triactive.jdo.PersistenceManager;
14 import com.triactive.jdo.SCO;
15 import com.triactive.jdo.StateManager;
16 import com.triactive.jdo.store.Query;
17 import com.triactive.jdo.store.Queryable;
18 import com.triactive.jdo.store.QueryStatement;
19 import com.triactive.jdo.store.MapStore;
20 import java.io.ObjectStreamException JavaDoc;
21 import java.util.AbstractSet JavaDoc;
22 import java.util.ArrayList JavaDoc;
23 import java.util.Collection JavaDoc;
24 import java.util.ConcurrentModificationException JavaDoc;
25 import java.util.Iterator JavaDoc;
26 import java.util.Map JavaDoc;
27 import java.util.Map.Entry;
28 import java.util.Set JavaDoc;
29 import javax.jdo.JDOFatalInternalException;
30 import javax.jdo.JDOHelper;
31 import org.apache.log4j.Category;
32
33
34 /**
35  * A mutable second-class HashMap object.
36  * <p>
37  * SCO fields declared as type java.util.Map or java.util.HashMap are populated
38  * with objects of this type whenever the owning object is actively being
39  * managed by a state manager.
40  * <p>
41  * While an SCO HashMap is owned it is considered either <dfn>transient</dfn> or
42  * <dfn>persistent</dfn> according to whether its owner is in a transient or
43  * persistent state.
44  * <p>
45  * While the owner/map is <em>transient</em>:
46  * <ol>
47  * <li>
48  * Entries are restricted to be of the designated key &amp; value type.
49  * </li>
50  * <li>
51  * All write operations cause the corresponding field in the owner to be
52  * marked dirty.
53  * </li>
54  * </ol>
55  * <p>
56  * In addition to the above, while the owner/map is <em>persistent</em>:
57  * <ol>
58  * <li>
59  * The contents of the map may or may not be fully loaded in memory.
60  * If they are loaded within a transaction they are considered "up-to-date"
61  * only until the next update of the data store.
62  * </li>
63  * <li>
64  * Some read operations (get(), size(), isEmpty(), containsKey(),
65  * containsValue()) "pass through" directly to the database if the memory
66  * contents are not up-to-date.
67  * This avoids an expensive load from the data store for operations that
68  * don't necessarily involve the entire map.
69  * All other read operations by nature must access the entire map and so
70  * they always load the contents, or reload them if they are not up-to-date.
71  * </li>
72  * <li>
73  * All write operations <em>always</em> pass through directly to the database,
74  * although the memory copy is also updated.
75  * No write operation requires the contents to be loaded.
76  * </li>
77  * </ol>
78  * <p>
79  * An instance of this class is always associated with a backing store, although
80  * the store is only used when the map is persistent.
81  * <p>
82  * The map disconnects from its owner when {@link #unsetOwner} is called.
83  * This occurs automatically in a variety of scenarios, such as when the object
84  * is cloned or when its owning object (the one whose Map field refers to it)
85  * transitions to a unmanaged state.
86  * When a disconnect occurs the object subsequently behaves as a normal HashMap.
87  * Subsequent changes affect only the memory contents.
88  * Once disconnected from its owner an instance can never be reconnected.
89  *
90  * @author <a HREF="mailto:mmartin5@austin.rr.com">Mike Martin</a>
91  * @version $Revision: 1.3 $
92  */

93
94 public class HashMap extends java.util.HashMap JavaDoc implements SCOMap, Cloneable JavaDoc
95 {
96     private static final Category LOG = Category.getInstance(HashMap.class);
97
98     private transient Object JavaDoc owner;
99     private transient PersistenceManager pm;
100     private transient StateManager ownerSM;
101     private transient String JavaDoc fieldName;
102     private transient MapStore mapStore;
103     private transient boolean isPersistent;
104     private transient boolean isLoaded; // invariant: !isPersistent -> isLoaded
105
private transient int expectedDSModCount = 0;
106     private transient volatile int modCount = 0;
107
108
109     private void init(Object JavaDoc owner, String JavaDoc fieldName, MapStore mapStore)
110     {
111         this.owner = owner;
112         this.pm = (PersistenceManager)JDOHelper.getPersistenceManager(owner);
113         this.ownerSM = pm.findStateManager(owner);
114         this.fieldName = fieldName;
115         this.mapStore = mapStore;
116         this.isPersistent = JDOHelper.isPersistent(owner);
117     }
118
119
120     /**
121      * Constructs an SCO HashMap representing an existing persistent map.
122      * The map's contents are initially not loaded.
123      *
124      * @param owner
125      * The object that owns this second-class object.
126      * @param fieldName
127      * The fieldName in the owning object.
128      * @param mapStore
129      * The backing store for this map.
130      */

131
132     public HashMap(Object JavaDoc owner, String JavaDoc fieldName, MapStore mapStore)
133     {
134         init(owner, fieldName, mapStore);
135
136         if (!isPersistent)
137             throw new JDOFatalInternalException("Wrong constructor called, owner object is transient");
138
139         isLoaded = false;
140     }
141
142
143     /**
144      * Constructs an SCO HashMap having the specified initial contents.
145      * <p>
146      * If the owning object is already persistent it is assumed its field is
147      * being assigned an entirely new value.
148      * The existing map contents are cleared in the data store and the new
149      * contents are added.
150      *
151      * @param owner
152      * The object that owns this second-class object.
153      * @param fieldName
154      * The fieldName in the owning object.
155      * @param mapStore
156      * The backing store for this map.
157      * @param value
158      * The initial contents of the map.
159      */

160
161     public HashMap(Object JavaDoc owner, String JavaDoc fieldName, MapStore mapStore, Map JavaDoc value)
162     {
163         init(owner, fieldName, mapStore);
164
165         if (isPersistent)
166         {
167             clearPersistent();
168             putAllPersistent(value);
169         }
170         else
171             putAllInternal(value);
172
173         setIsLoaded();
174     }
175
176
177     private void setIsLoaded()
178     {
179         isLoaded = true;
180         expectedDSModCount = pm.dataStoreModifyCount();
181     }
182
183
184     private boolean upToDate()
185     {
186         if (!isPersistent)
187             return true;
188         else if (!isLoaded)
189             return false;
190         else if (!pm.currentTransaction().isActive())
191             return true;
192         else
193         {
194             /*
195              * When loaded up from the store within a transaction, we're only
196              * considered up-to-date until the next data store update.
197              */

198             return pm.dataStoreModifyCount() == expectedDSModCount;
199         }
200     }
201
202
203     public Object JavaDoc getOwner()
204     {
205         return owner;
206     }
207
208
209     public String JavaDoc getFieldName()
210     {
211         return fieldName;
212     }
213
214
215     public Class JavaDoc getKeyType()
216     {
217         return mapStore.getKeyType();
218     }
219
220
221     public Class JavaDoc getValueType()
222     {
223         return mapStore.getValueType();
224     }
225
226
227     public boolean allowsNullValues()
228     {
229         return mapStore.allowsNullValues();
230     }
231
232
233     public void makeDirty()
234     {
235         ++modCount;
236
237         /*
238          * Even though all write operations pass through, the owning object must
239          * be marked dirty so that the proper state change occurs and its
240          * jdoPreStore() gets called properly.
241          */

242         if (owner != null)
243             JDOHelper.makeDirty(owner, fieldName);
244     }
245
246
247     public void applyUpdates()
248     {
249         /*
250          * If we're already persistent there's nothing to do because all writes
251          * immediately pass through. If we're not then all the elements need
252          * to be added to the store.
253          */

254         if (!isPersistent)
255         {
256             mapStore.putAll(ownerSM, this);
257             isPersistent = true;
258             expectedDSModCount = pm.dataStoreModifyCount();
259
260             if (LOG.isDebugEnabled())
261                 LOG.debug(toLogString() + " is now persistent");
262         }
263     }
264
265
266     public void unsetOwner()
267     {
268         if (owner != null)
269         {
270             owner = null;
271             ownerSM = null;
272             fieldName = null;
273             isPersistent = false;
274
275             if (LOG.isDebugEnabled())
276                 LOG.debug(toLogString() + " is now unowned");
277         }
278     }
279
280
281     /**
282      * Creates and returns a copy of this object.
283      *
284      * <P>Mutable second-class Objects are required to provide a public
285      * clone method in order to allow for copying PersistenceCapable
286      * objects. In contrast to Object.clone(), this method must not throw a
287      * CloneNotSupportedException.
288      */

289
290     public Object JavaDoc clone()
291     {
292         Object JavaDoc obj = super.clone();
293
294         ((HashMap)obj).unsetOwner();
295
296         return obj;
297     }
298
299
300     private synchronized void load()
301     {
302         if (!upToDate())
303         {
304             if (LOG.isDebugEnabled())
305                 LOG.debug(toLogString() + " loading from storage");
306
307             Map JavaDoc contents = mapStore.load(ownerSM);
308             clearInternal();
309             putAllInternal(contents);
310             setIsLoaded();
311         }
312     }
313
314
315     private Set JavaDoc entrySetInternal()
316     {
317         return super.entrySet();
318     }
319
320
321     private Object JavaDoc putInternal(Object JavaDoc key, Object JavaDoc value)
322     {
323     return super.put(key, value);
324     }
325
326
327     private void putAllInternal(Map JavaDoc m)
328     {
329         Iterator JavaDoc i = m.entrySet().iterator();
330
331     while (i.hasNext())
332     {
333         Entry e = (Entry)i.next();
334         super.put(e.getKey(), e.getValue());
335     }
336     }
337
338
339     private Object JavaDoc removeInternal(Object JavaDoc key)
340     {
341     return super.remove(key);
342     }
343
344
345     private void clearInternal()
346     {
347     super.clear();
348     }
349
350
351     private Object JavaDoc getPersistent(Object JavaDoc key)
352     {
353         if (isPersistent)
354     {
355             Object JavaDoc value = mapStore.get(ownerSM, key);
356             putInternal(key, value);
357         return value;
358     }
359         else
360             return super.get(key);
361     }
362
363
364     private Object JavaDoc putPersistent(Object JavaDoc key, Object JavaDoc value)
365     {
366         if (isPersistent)
367         {
368             putInternal(key, value);
369             return mapStore.put(ownerSM, key, value);
370         }
371         else
372             return putInternal(key, value);
373     }
374
375
376     private void putAllPersistent(Map JavaDoc m)
377     {
378         SCOHelper.assertAllValidEntries(this, m);
379
380         if (isPersistent)
381             mapStore.putAll(ownerSM, m);
382
383     putAllInternal(m);
384     }
385
386
387     private Object JavaDoc removePersistent(Object JavaDoc key)
388     {
389         if (isPersistent)
390         {
391             removeInternal(key);
392             return mapStore.remove(ownerSM, key);
393         }
394         else
395             return removeInternal(key);
396     }
397
398
399     private void clearPersistent()
400     {
401         if (isPersistent)
402         {
403             mapStore.clear(ownerSM);
404             setIsLoaded();
405         }
406
407         clearInternal();
408     }
409
410
411     public int size()
412     {
413         return upToDate() ? super.size() : mapStore.size(ownerSM);
414     }
415
416
417     public boolean isEmpty()
418     {
419         return upToDate() ? super.isEmpty() : mapStore.isEmpty(ownerSM);
420     }
421
422
423     public boolean containsKey(Object JavaDoc key)
424     {
425         if (!SCOHelper.isValidKey(this, key))
426             return false;
427
428         return upToDate() ? super.containsKey(key) : mapStore.containsKey(ownerSM, key);
429     }
430
431
432     public boolean containsValue(Object JavaDoc value)
433     {
434         if (!SCOHelper.isValidValue(this, value))
435             return false;
436
437         return upToDate() ? super.containsValue(value) : mapStore.containsValue(ownerSM, value);
438     }
439
440
441     private boolean containsEntry(Entry entry)
442     {
443         Object JavaDoc key = entry.getKey();
444         Object JavaDoc value = entry.getValue();
445
446         if (!SCOHelper.isValidKey(this, key))
447             return false;
448         if (!SCOHelper.isValidValue(this, value))
449             return false;
450
451         if (upToDate())
452         {
453             Object JavaDoc resValue = super.get(key);
454
455             return super.containsKey(key) && (value == null ? resValue == null : value.equals(resValue));
456         }
457         else
458             return mapStore.containsEntry(ownerSM, key, value);
459     }
460
461
462     public Object JavaDoc get(Object JavaDoc key)
463     {
464         SCOHelper.assertIsValidKey(this, key);
465
466         return upToDate() ? super.get(key) : getPersistent(key);
467     }
468
469
470     public Object JavaDoc put(Object JavaDoc key, Object JavaDoc value)
471     {
472         SCOHelper.assertIsValidKey(this, key);
473         SCOHelper.assertIsValidValue(this, value);
474
475         makeDirty();
476         return putPersistent(key, value);
477     }
478
479
480     public void putAll(Map JavaDoc m)
481     {
482         SCOHelper.assertAllValidEntries(this, m);
483
484         makeDirty();
485         putAllPersistent(m);
486     }
487
488
489     public Object JavaDoc remove(Object JavaDoc key)
490     {
491         if (!SCOHelper.isValidKey(this, key))
492             return null;
493
494         makeDirty();
495         return removePersistent(key);
496     }
497
498
499     private boolean removeEntry(Entry entry)
500     {
501         Object JavaDoc key = entry.getKey();
502         Object JavaDoc value = entry.getValue();
503
504         if (!SCOHelper.isValidKey(this, key))
505             return false;
506         if (!SCOHelper.isValidValue(this, value))
507             return false;
508
509         makeDirty();
510         boolean modified = false;
511
512         if (isPersistent)
513         {
514             modified = mapStore.removeEntry(ownerSM, key, value);
515
516             if (modified)
517                 removeInternal(key);
518         }
519         else
520         {
521             if (containsKey(key))
522             {
523                 Object JavaDoc resValue = super.get(key);
524
525                 if (value == null ? resValue == null : value.equals(resValue))
526                 {
527                     removeInternal(key);
528                     modified = true;
529                 }
530             }
531         }
532
533         return modified;
534     }
535
536
537     public void clear()
538     {
539         makeDirty();
540         clearPersistent();
541     }
542
543
544     public Set JavaDoc keySet()
545     {
546         return new KeySetView();
547     }
548
549
550     public Collection JavaDoc values()
551     {
552         return new ValuesView();
553     }
554
555
556     public Set JavaDoc entrySet()
557     {
558         return new EntrySetView();
559     }
560
561
562     public boolean equals(Object JavaDoc o)
563     {
564     load();
565     return super.equals(o);
566     }
567
568
569     public int hashCode()
570     {
571     load();
572     return super.hashCode();
573     }
574
575
576     public String JavaDoc toString()
577     {
578     load();
579         return super.toString();
580     }
581
582
583     private String JavaDoc toLogString()
584     {
585         return SCOHelper.toLogString(this);
586     }
587
588
589     /**
590      * Replaces the object to be serialized with a java.util.HashMap object.
591      * Invoked by the serialization mechanism to obtain an alternative object
592      * to be used when writing an object to the stream.
593      *
594      * @return
595      * The <code>HashMap</code> to be serialized instead of this object.
596      */

597
598     protected Object JavaDoc writeReplace() throws ObjectStreamException JavaDoc
599     {
600         return new java.util.HashMap JavaDoc(this);
601     }
602
603
604     private abstract class SetView extends AbstractSet JavaDoc implements Queryable
605     {
606         public int size()
607         {
608             return HashMap.this.size();
609         }
610
611         public void clear()
612         {
613             HashMap.this.clear();
614         }
615
616         protected abstract Queryable storageQuery();
617
618         private void assertIsPersistent()
619         {
620             if (!isPersistent)
621                 throw new QueryUnownedSCOException(HashMap.this);
622         }
623
624         public Class JavaDoc getCandidateClass()
625         {
626             assertIsPersistent();
627             return storageQuery().getCandidateClass();
628         }
629
630         public QueryStatement newQueryStatement(Class JavaDoc candidateClass)
631         {
632             assertIsPersistent();
633             return storageQuery().newQueryStatement(candidateClass);
634         }
635
636         public Query.ResultObjectFactory newResultObjectFactory(QueryStatement stmt)
637         {
638             assertIsPersistent();
639             return storageQuery().newResultObjectFactory(stmt);
640         }
641     }
642
643
644     private class KeySetView extends SetView
645     {
646         public Iterator JavaDoc iterator()
647         {
648             load();
649             return new KeyIterator();
650         }
651
652         public boolean contains(Object JavaDoc o)
653         {
654             return containsKey(o);
655         }
656
657         public boolean remove(Object JavaDoc o)
658         {
659             return HashMap.this.remove(o) != null;
660         }
661
662         protected Queryable storageQuery()
663         {
664             return mapStore.keySetQuery(ownerSM);
665         }
666     }
667
668
669     private class ValuesView extends SetView
670     {
671         public Iterator JavaDoc iterator()
672         {
673             load();
674             return new ValueIterator();
675         }
676
677         public boolean contains(Object JavaDoc o)
678         {
679             return containsValue(o);
680         }
681
682         protected Queryable storageQuery()
683         {
684             return mapStore.valuesQuery(ownerSM);
685         }
686     }
687
688
689     private class EntrySetView extends SetView
690     {
691         public Iterator JavaDoc iterator()
692         {
693             load();
694             return new EntryIterator();
695         }
696
697         public boolean contains(Object JavaDoc o)
698         {
699             if (!(o instanceof Entry))
700                 return false;
701             else
702                 return containsEntry((Entry)o);
703         }
704
705         public boolean remove(Object JavaDoc o)
706         {
707             if (!(o instanceof Entry))
708                 return false;
709             else
710                 return removeEntry((Entry)o);
711         }
712
713         protected Queryable storageQuery()
714         {
715             return mapStore.entrySetQuery(ownerSM);
716         }
717     }
718
719
720     private abstract class MapIterator implements Iterator JavaDoc
721     {
722         private final Iterator JavaDoc iter = new ArrayList JavaDoc(entrySetInternal()).iterator();
723         private Entry last = null;
724         private int expectedModCount = modCount;
725
726         public boolean hasNext() { return iter.hasNext(); }
727
728         protected Entry nextEntry()
729         {
730             if (modCount != expectedModCount)
731                 throw new ConcurrentModificationException JavaDoc();
732
733             return last = (Entry)iter.next();
734         }
735
736         public void remove()
737         {
738             if (last == null)
739                 throw new IllegalStateException JavaDoc();
740             if (modCount != expectedModCount)
741                 throw new ConcurrentModificationException JavaDoc();
742
743         makeDirty();
744             removePersistent(last.getKey());
745             last = null;
746             expectedModCount = modCount;
747         }
748     }
749
750
751     private class KeyIterator extends MapIterator
752     {
753         public Object JavaDoc next() { return nextEntry().getKey(); }
754     }
755
756
757     private class ValueIterator extends MapIterator
758     {
759         public Object JavaDoc next() { return nextEntry().getValue(); }
760     }
761
762
763     private class EntryIterator extends MapIterator
764     {
765         public Object JavaDoc next() { return nextEntry(); }
766     }
767 }
768
Popular Tags