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