001: /*
002: * Copyright 2004 (C) TJDO.
003: * All rights reserved.
004: *
005: * This software is distributed under the terms of the TJDO License version 1.0.
006: * See the terms of the TJDO License in the documentation provided with this software.
007: *
008: * $Id: HashMap.java,v 1.3 2004/01/18 19:15:30 jackknifebarber Exp $
009: */
010:
011: package com.triactive.jdo.sco;
012:
013: import com.triactive.jdo.PersistenceManager;
014: import com.triactive.jdo.SCO;
015: import com.triactive.jdo.StateManager;
016: import com.triactive.jdo.store.Query;
017: import com.triactive.jdo.store.Queryable;
018: import com.triactive.jdo.store.QueryStatement;
019: import com.triactive.jdo.store.MapStore;
020: import java.io.ObjectStreamException;
021: import java.util.AbstractSet;
022: import java.util.ArrayList;
023: import java.util.Collection;
024: import java.util.ConcurrentModificationException;
025: import java.util.Iterator;
026: import java.util.Map;
027: import java.util.Map.Entry;
028: import java.util.Set;
029: import javax.jdo.JDOFatalInternalException;
030: import javax.jdo.JDOHelper;
031: import org.apache.log4j.Category;
032:
033: /**
034: * A mutable second-class HashMap object.
035: * <p>
036: * SCO fields declared as type java.util.Map or java.util.HashMap are populated
037: * with objects of this type whenever the owning object is actively being
038: * managed by a state manager.
039: * <p>
040: * While an SCO HashMap is owned it is considered either <dfn>transient</dfn> or
041: * <dfn>persistent</dfn> according to whether its owner is in a transient or
042: * persistent state.
043: * <p>
044: * While the owner/map is <em>transient</em>:
045: * <ol>
046: * <li>
047: * Entries are restricted to be of the designated key & value type.
048: * </li>
049: * <li>
050: * All write operations cause the corresponding field in the owner to be
051: * marked dirty.
052: * </li>
053: * </ol>
054: * <p>
055: * In addition to the above, while the owner/map is <em>persistent</em>:
056: * <ol>
057: * <li>
058: * The contents of the map may or may not be fully loaded in memory.
059: * If they are loaded within a transaction they are considered "up-to-date"
060: * only until the next update of the data store.
061: * </li>
062: * <li>
063: * Some read operations (get(), size(), isEmpty(), containsKey(),
064: * containsValue()) "pass through" directly to the database if the memory
065: * contents are not up-to-date.
066: * This avoids an expensive load from the data store for operations that
067: * don't necessarily involve the entire map.
068: * All other read operations by nature must access the entire map and so
069: * they always load the contents, or reload them if they are not up-to-date.
070: * </li>
071: * <li>
072: * All write operations <em>always</em> pass through directly to the database,
073: * although the memory copy is also updated.
074: * No write operation requires the contents to be loaded.
075: * </li>
076: * </ol>
077: * <p>
078: * An instance of this class is always associated with a backing store, although
079: * the store is only used when the map is persistent.
080: * <p>
081: * The map disconnects from its owner when {@link #unsetOwner} is called.
082: * This occurs automatically in a variety of scenarios, such as when the object
083: * is cloned or when its owning object (the one whose Map field refers to it)
084: * transitions to a unmanaged state.
085: * When a disconnect occurs the object subsequently behaves as a normal HashMap.
086: * Subsequent changes affect only the memory contents.
087: * Once disconnected from its owner an instance can never be reconnected.
088: *
089: * @author <a href="mailto:mmartin5@austin.rr.com">Mike Martin</a>
090: * @version $Revision: 1.3 $
091: */
092:
093: public class HashMap extends java.util.HashMap implements SCOMap,
094: Cloneable {
095: private static final Category LOG = Category
096: .getInstance(HashMap.class);
097:
098: private transient Object owner;
099: private transient PersistenceManager pm;
100: private transient StateManager ownerSM;
101: private transient String 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: private void init(Object owner, String fieldName, MapStore mapStore) {
109: this .owner = owner;
110: this .pm = (PersistenceManager) JDOHelper
111: .getPersistenceManager(owner);
112: this .ownerSM = pm.findStateManager(owner);
113: this .fieldName = fieldName;
114: this .mapStore = mapStore;
115: this .isPersistent = JDOHelper.isPersistent(owner);
116: }
117:
118: /**
119: * Constructs an SCO HashMap representing an existing persistent map.
120: * The map's contents are initially not loaded.
121: *
122: * @param owner
123: * The object that owns this second-class object.
124: * @param fieldName
125: * The fieldName in the owning object.
126: * @param mapStore
127: * The backing store for this map.
128: */
129:
130: public HashMap(Object owner, String fieldName, MapStore mapStore) {
131: init(owner, fieldName, mapStore);
132:
133: if (!isPersistent)
134: throw new JDOFatalInternalException(
135: "Wrong constructor called, owner object is transient");
136:
137: isLoaded = false;
138: }
139:
140: /**
141: * Constructs an SCO HashMap having the specified initial contents.
142: * <p>
143: * If the owning object is already persistent it is assumed its field is
144: * being assigned an entirely new value.
145: * The existing map contents are cleared in the data store and the new
146: * contents are added.
147: *
148: * @param owner
149: * The object that owns this second-class object.
150: * @param fieldName
151: * The fieldName in the owning object.
152: * @param mapStore
153: * The backing store for this map.
154: * @param value
155: * The initial contents of the map.
156: */
157:
158: public HashMap(Object owner, String fieldName, MapStore mapStore,
159: Map value) {
160: init(owner, fieldName, mapStore);
161:
162: if (isPersistent) {
163: clearPersistent();
164: putAllPersistent(value);
165: } else
166: putAllInternal(value);
167:
168: setIsLoaded();
169: }
170:
171: private void setIsLoaded() {
172: isLoaded = true;
173: expectedDSModCount = pm.dataStoreModifyCount();
174: }
175:
176: private boolean upToDate() {
177: if (!isPersistent)
178: return true;
179: else if (!isLoaded)
180: return false;
181: else if (!pm.currentTransaction().isActive())
182: return true;
183: else {
184: /*
185: * When loaded up from the store within a transaction, we're only
186: * considered up-to-date until the next data store update.
187: */
188: return pm.dataStoreModifyCount() == expectedDSModCount;
189: }
190: }
191:
192: public Object getOwner() {
193: return owner;
194: }
195:
196: public String getFieldName() {
197: return fieldName;
198: }
199:
200: public Class getKeyType() {
201: return mapStore.getKeyType();
202: }
203:
204: public Class getValueType() {
205: return mapStore.getValueType();
206: }
207:
208: public boolean allowsNullValues() {
209: return mapStore.allowsNullValues();
210: }
211:
212: public void makeDirty() {
213: ++modCount;
214:
215: /*
216: * Even though all write operations pass through, the owning object must
217: * be marked dirty so that the proper state change occurs and its
218: * jdoPreStore() gets called properly.
219: */
220: if (owner != null)
221: JDOHelper.makeDirty(owner, fieldName);
222: }
223:
224: public void applyUpdates() {
225: /*
226: * If we're already persistent there's nothing to do because all writes
227: * immediately pass through. If we're not then all the elements need
228: * to be added to the store.
229: */
230: if (!isPersistent) {
231: mapStore.putAll(ownerSM, this );
232: isPersistent = true;
233: expectedDSModCount = pm.dataStoreModifyCount();
234:
235: if (LOG.isDebugEnabled())
236: LOG.debug(toLogString() + " is now persistent");
237: }
238: }
239:
240: public void unsetOwner() {
241: if (owner != null) {
242: owner = null;
243: ownerSM = null;
244: fieldName = null;
245: isPersistent = false;
246:
247: if (LOG.isDebugEnabled())
248: LOG.debug(toLogString() + " is now unowned");
249: }
250: }
251:
252: /**
253: * Creates and returns a copy of this object.
254: *
255: * <P>Mutable second-class Objects are required to provide a public
256: * clone method in order to allow for copying PersistenceCapable
257: * objects. In contrast to Object.clone(), this method must not throw a
258: * CloneNotSupportedException.
259: */
260:
261: public Object clone() {
262: Object obj = super .clone();
263:
264: ((HashMap) obj).unsetOwner();
265:
266: return obj;
267: }
268:
269: private synchronized void load() {
270: if (!upToDate()) {
271: if (LOG.isDebugEnabled())
272: LOG.debug(toLogString() + " loading from storage");
273:
274: Map contents = mapStore.load(ownerSM);
275: clearInternal();
276: putAllInternal(contents);
277: setIsLoaded();
278: }
279: }
280:
281: private Set entrySetInternal() {
282: return super .entrySet();
283: }
284:
285: private Object putInternal(Object key, Object value) {
286: return super .put(key, value);
287: }
288:
289: private void putAllInternal(Map m) {
290: Iterator i = m.entrySet().iterator();
291:
292: while (i.hasNext()) {
293: Entry e = (Entry) i.next();
294: super .put(e.getKey(), e.getValue());
295: }
296: }
297:
298: private Object removeInternal(Object key) {
299: return super .remove(key);
300: }
301:
302: private void clearInternal() {
303: super .clear();
304: }
305:
306: private Object getPersistent(Object key) {
307: if (isPersistent) {
308: Object value = mapStore.get(ownerSM, key);
309: putInternal(key, value);
310: return value;
311: } else
312: return super .get(key);
313: }
314:
315: private Object putPersistent(Object key, Object value) {
316: if (isPersistent) {
317: putInternal(key, value);
318: return mapStore.put(ownerSM, key, value);
319: } else
320: return putInternal(key, value);
321: }
322:
323: private void putAllPersistent(Map m) {
324: SCOHelper.assertAllValidEntries(this , m);
325:
326: if (isPersistent)
327: mapStore.putAll(ownerSM, m);
328:
329: putAllInternal(m);
330: }
331:
332: private Object removePersistent(Object key) {
333: if (isPersistent) {
334: removeInternal(key);
335: return mapStore.remove(ownerSM, key);
336: } else
337: return removeInternal(key);
338: }
339:
340: private void clearPersistent() {
341: if (isPersistent) {
342: mapStore.clear(ownerSM);
343: setIsLoaded();
344: }
345:
346: clearInternal();
347: }
348:
349: public int size() {
350: return upToDate() ? super .size() : mapStore.size(ownerSM);
351: }
352:
353: public boolean isEmpty() {
354: return upToDate() ? super .isEmpty() : mapStore.isEmpty(ownerSM);
355: }
356:
357: public boolean containsKey(Object key) {
358: if (!SCOHelper.isValidKey(this , key))
359: return false;
360:
361: return upToDate() ? super .containsKey(key) : mapStore
362: .containsKey(ownerSM, key);
363: }
364:
365: public boolean containsValue(Object value) {
366: if (!SCOHelper.isValidValue(this , value))
367: return false;
368:
369: return upToDate() ? super .containsValue(value) : mapStore
370: .containsValue(ownerSM, value);
371: }
372:
373: private boolean containsEntry(Entry entry) {
374: Object key = entry.getKey();
375: Object value = entry.getValue();
376:
377: if (!SCOHelper.isValidKey(this , key))
378: return false;
379: if (!SCOHelper.isValidValue(this , value))
380: return false;
381:
382: if (upToDate()) {
383: Object resValue = super .get(key);
384:
385: return super .containsKey(key)
386: && (value == null ? resValue == null : value
387: .equals(resValue));
388: } else
389: return mapStore.containsEntry(ownerSM, key, value);
390: }
391:
392: public Object get(Object key) {
393: SCOHelper.assertIsValidKey(this , key);
394:
395: return upToDate() ? super .get(key) : getPersistent(key);
396: }
397:
398: public Object put(Object key, Object value) {
399: SCOHelper.assertIsValidKey(this , key);
400: SCOHelper.assertIsValidValue(this , value);
401:
402: makeDirty();
403: return putPersistent(key, value);
404: }
405:
406: public void putAll(Map m) {
407: SCOHelper.assertAllValidEntries(this , m);
408:
409: makeDirty();
410: putAllPersistent(m);
411: }
412:
413: public Object remove(Object key) {
414: if (!SCOHelper.isValidKey(this , key))
415: return null;
416:
417: makeDirty();
418: return removePersistent(key);
419: }
420:
421: private boolean removeEntry(Entry entry) {
422: Object key = entry.getKey();
423: Object value = entry.getValue();
424:
425: if (!SCOHelper.isValidKey(this , key))
426: return false;
427: if (!SCOHelper.isValidValue(this , value))
428: return false;
429:
430: makeDirty();
431: boolean modified = false;
432:
433: if (isPersistent) {
434: modified = mapStore.removeEntry(ownerSM, key, value);
435:
436: if (modified)
437: removeInternal(key);
438: } else {
439: if (containsKey(key)) {
440: Object resValue = super .get(key);
441:
442: if (value == null ? resValue == null : value
443: .equals(resValue)) {
444: removeInternal(key);
445: modified = true;
446: }
447: }
448: }
449:
450: return modified;
451: }
452:
453: public void clear() {
454: makeDirty();
455: clearPersistent();
456: }
457:
458: public Set keySet() {
459: return new KeySetView();
460: }
461:
462: public Collection values() {
463: return new ValuesView();
464: }
465:
466: public Set entrySet() {
467: return new EntrySetView();
468: }
469:
470: public boolean equals(Object o) {
471: load();
472: return super .equals(o);
473: }
474:
475: public int hashCode() {
476: load();
477: return super .hashCode();
478: }
479:
480: public String toString() {
481: load();
482: return super .toString();
483: }
484:
485: private String toLogString() {
486: return SCOHelper.toLogString(this );
487: }
488:
489: /**
490: * Replaces the object to be serialized with a java.util.HashMap object.
491: * Invoked by the serialization mechanism to obtain an alternative object
492: * to be used when writing an object to the stream.
493: *
494: * @return
495: * The <code>HashMap</code> to be serialized instead of this object.
496: */
497:
498: protected Object writeReplace() throws ObjectStreamException {
499: return new java.util.HashMap(this );
500: }
501:
502: private abstract class SetView extends AbstractSet implements
503: Queryable {
504: public int size() {
505: return HashMap.this .size();
506: }
507:
508: public void clear() {
509: HashMap.this .clear();
510: }
511:
512: protected abstract Queryable storageQuery();
513:
514: private void assertIsPersistent() {
515: if (!isPersistent)
516: throw new QueryUnownedSCOException(HashMap.this );
517: }
518:
519: public Class getCandidateClass() {
520: assertIsPersistent();
521: return storageQuery().getCandidateClass();
522: }
523:
524: public QueryStatement newQueryStatement(Class candidateClass) {
525: assertIsPersistent();
526: return storageQuery().newQueryStatement(candidateClass);
527: }
528:
529: public Query.ResultObjectFactory newResultObjectFactory(
530: QueryStatement stmt) {
531: assertIsPersistent();
532: return storageQuery().newResultObjectFactory(stmt);
533: }
534: }
535:
536: private class KeySetView extends SetView {
537: public Iterator iterator() {
538: load();
539: return new KeyIterator();
540: }
541:
542: public boolean contains(Object o) {
543: return containsKey(o);
544: }
545:
546: public boolean remove(Object o) {
547: return HashMap.this .remove(o) != null;
548: }
549:
550: protected Queryable storageQuery() {
551: return mapStore.keySetQuery(ownerSM);
552: }
553: }
554:
555: private class ValuesView extends SetView {
556: public Iterator iterator() {
557: load();
558: return new ValueIterator();
559: }
560:
561: public boolean contains(Object o) {
562: return containsValue(o);
563: }
564:
565: protected Queryable storageQuery() {
566: return mapStore.valuesQuery(ownerSM);
567: }
568: }
569:
570: private class EntrySetView extends SetView {
571: public Iterator iterator() {
572: load();
573: return new EntryIterator();
574: }
575:
576: public boolean contains(Object o) {
577: if (!(o instanceof Entry))
578: return false;
579: else
580: return containsEntry((Entry) o);
581: }
582:
583: public boolean remove(Object o) {
584: if (!(o instanceof Entry))
585: return false;
586: else
587: return removeEntry((Entry) o);
588: }
589:
590: protected Queryable storageQuery() {
591: return mapStore.entrySetQuery(ownerSM);
592: }
593: }
594:
595: private abstract class MapIterator implements Iterator {
596: private final Iterator iter = new ArrayList(entrySetInternal())
597: .iterator();
598: private Entry last = null;
599: private int expectedModCount = modCount;
600:
601: public boolean hasNext() {
602: return iter.hasNext();
603: }
604:
605: protected Entry nextEntry() {
606: if (modCount != expectedModCount)
607: throw new ConcurrentModificationException();
608:
609: return last = (Entry) iter.next();
610: }
611:
612: public void remove() {
613: if (last == null)
614: throw new IllegalStateException();
615: if (modCount != expectedModCount)
616: throw new ConcurrentModificationException();
617:
618: makeDirty();
619: removePersistent(last.getKey());
620: last = null;
621: expectedModCount = modCount;
622: }
623: }
624:
625: private class KeyIterator extends MapIterator {
626: public Object next() {
627: return nextEntry().getKey();
628: }
629: }
630:
631: private class ValueIterator extends MapIterator {
632: public Object next() {
633: return nextEntry().getValue();
634: }
635: }
636:
637: private class EntryIterator extends MapIterator {
638: public Object next() {
639: return nextEntry();
640: }
641: }
642: }
|