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: HashSet.java,v 1.6 2004/01/18 05:46:55 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.SetStore;
020: import java.io.ObjectStreamException;
021: import java.util.ArrayList;
022: import java.util.Collection;
023: import java.util.ConcurrentModificationException;
024: import java.util.Iterator;
025: import java.util.Set;
026: import javax.jdo.JDOFatalInternalException;
027: import javax.jdo.JDOHelper;
028: import org.apache.log4j.Category;
029:
030: /**
031: * A mutable second-class HashSet object.
032: * <p>
033: * SCO fields declared as type java.util.Collection, java.util.Set, or
034: * java.util.HashSet are populated with objects of this type whenever the owning
035: * object is actively being managed by a state manager.
036: * <p>
037: * While an SCO HashSet is owned it is considered either <dfn>transient</dfn> or
038: * <dfn>persistent</dfn> according to whether its owner is in a transient or
039: * persistent state.
040: * <p>
041: * While the owner/set is <em>transient</em>:
042: * <ol>
043: * <li>
044: * Elements are restricted to be of the designated element type.
045: * </li>
046: * <li>
047: * All write operations cause the corresponding field in the owner to be
048: * marked dirty.
049: * </li>
050: * </ol>
051: * <p>
052: * In addition to the above, while the owner/set is <em>persistent</em>:
053: * <ol>
054: * <li>
055: * The contents of the set may or may not be fully loaded in memory.
056: * If they are loaded within a transaction they are considered "up-to-date"
057: * only until the next update of the data store.
058: * </li>
059: * <li>
060: * Some read operations (size(), isEmpty(), contains()) "pass through"
061: * directly to the database if the memory contents are not up-to-date.
062: * This avoids an expensive load from the data store for operations that
063: * don't necessarily involve the entire set.
064: * All other read operations by nature must access the entire set and so
065: * they always load the contents, or reload them if they are not up-to-date.
066: * </li>
067: * <li>
068: * All write operations <em>always</em> pass through directly to the database,
069: * although the memory copy is also updated.
070: * With the exception of retainAll(), no write operation requires the contents
071: * to be loaded.
072: * </li>
073: * </ol>
074: * <p>
075: * An instance of this class is always associated with a backing store, although
076: * the store is only used when the set is persistent.
077: * <p>
078: * The set disconnects from its owner when {@link #unsetOwner} is called.
079: * This occurs automatically in a variety of scenarios, such as when the object
080: * is cloned or when its owning object (the one whose Set field refers to it)
081: * transitions to a unmanaged state.
082: * When a disconnect occurs the object subsequently behaves as a normal HashSet.
083: * Subsequent changes affect only the memory contents.
084: * Once disconnected from its owner an instance can never be reconnected.
085: *
086: * @author <a href="mailto:mmartin5@austin.rr.com">Mike Martin</a>
087: * @version $Revision: 1.6 $
088: */
089:
090: public class HashSet extends java.util.HashSet implements
091: SCOCollection, Cloneable, Queryable {
092: private static final Category LOG = Category
093: .getInstance(HashSet.class);
094:
095: private transient Object owner;
096: private transient PersistenceManager pm;
097: private transient StateManager ownerSM;
098: private transient String fieldName;
099: private transient SetStore setStore;
100: private transient boolean isPersistent;
101: private transient boolean isLoaded; // invariant: !isPersistent -> isLoaded
102: private transient int expectedDSModCount = 0;
103: private transient volatile int modCount = 0;
104:
105: private void init(Object owner, String fieldName, SetStore setStore) {
106: this .owner = owner;
107: this .pm = (PersistenceManager) JDOHelper
108: .getPersistenceManager(owner);
109: this .ownerSM = pm.findStateManager(owner);
110: this .fieldName = fieldName;
111: this .setStore = setStore;
112: this .isPersistent = JDOHelper.isPersistent(owner);
113: }
114:
115: /**
116: * Constructs an SCO HashSet representing an existing persistent set.
117: * The set's contents are initially not loaded.
118: *
119: * @param owner
120: * The object that owns this second-class object.
121: * @param fieldName
122: * The fieldName in the owning object.
123: * @param setStore
124: * The backing store for this set.
125: */
126:
127: public HashSet(Object owner, String fieldName, SetStore setStore) {
128: init(owner, fieldName, setStore);
129:
130: if (!isPersistent)
131: throw new JDOFatalInternalException(
132: "Wrong constructor called, owner object is transient");
133:
134: isLoaded = false;
135: }
136:
137: /**
138: * Constructs an SCO HashSet having the specified initial contents.
139: * <p>
140: * If the owning object is already persistent it is assumed its field is
141: * being assigned an entirely new value.
142: * The existing set contents are cleared in the data store and the new
143: * contents are added.
144: *
145: * @param owner
146: * The object that owns this second-class object.
147: * @param fieldName
148: * The fieldName in the owning object.
149: * @param setStore
150: * The backing store for this set.
151: * @param value
152: * The initial contents of the set.
153: */
154:
155: public HashSet(Object owner, String fieldName, SetStore setStore,
156: Collection value) {
157: init(owner, fieldName, setStore);
158:
159: if (isPersistent) {
160: clearPersistent();
161: addAllPersistent(value);
162: } else
163: addAllInternal(value);
164:
165: setIsLoaded();
166: }
167:
168: private void setIsLoaded() {
169: isLoaded = true;
170: expectedDSModCount = pm.dataStoreModifyCount();
171: }
172:
173: private boolean upToDate() {
174: if (!isPersistent)
175: return true;
176: else if (!isLoaded)
177: return false;
178: else if (!pm.currentTransaction().isActive())
179: return true;
180: else {
181: /*
182: * When loaded up from the store within a transaction, we're only
183: * considered up-to-date until the next data store update.
184: */
185: return pm.dataStoreModifyCount() == expectedDSModCount;
186: }
187: }
188:
189: public Object getOwner() {
190: return owner;
191: }
192:
193: public String getFieldName() {
194: return fieldName;
195: }
196:
197: public Class getElementType() {
198: return setStore.getElementType();
199: }
200:
201: public boolean allowsNulls() {
202: return setStore.allowsNulls();
203: }
204:
205: public void makeDirty() {
206: ++modCount;
207:
208: /*
209: * Even though all write operations pass through, the owning object must
210: * be marked dirty so that the proper state change occurs and its
211: * jdoPreStore() gets called properly.
212: */
213: if (owner != null)
214: JDOHelper.makeDirty(owner, fieldName);
215: }
216:
217: public void applyUpdates() {
218: /*
219: * If we're already persistent there's nothing to do because all writes
220: * immediately pass through. If we're not then all the elements need
221: * to be added to the store.
222: */
223: if (!isPersistent) {
224: setStore.addAll(ownerSM, this );
225: isPersistent = true;
226: expectedDSModCount = pm.dataStoreModifyCount();
227:
228: if (LOG.isDebugEnabled())
229: LOG.debug(toLogString() + " is now persistent");
230: }
231: }
232:
233: public void unsetOwner() {
234: if (owner != null) {
235: owner = null;
236: ownerSM = null;
237: fieldName = null;
238: isPersistent = false;
239:
240: if (LOG.isDebugEnabled())
241: LOG.debug(toLogString() + " is now unowned");
242: }
243: }
244:
245: public Class getCandidateClass() {
246: return getElementType();
247: }
248:
249: public QueryStatement newQueryStatement(Class candidateClass) {
250: if (!isPersistent)
251: throw new QueryUnownedSCOException(this );
252:
253: return setStore.newQueryStatement(ownerSM, candidateClass);
254: }
255:
256: public Query.ResultObjectFactory newResultObjectFactory(
257: QueryStatement stmt) {
258: if (!isPersistent)
259: throw new QueryUnownedSCOException(this );
260:
261: return setStore.newResultObjectFactory(ownerSM, stmt);
262: }
263:
264: /**
265: * Creates and returns a copy of this object.
266: *
267: * <P>Mutable second-class Objects are required to provide a public
268: * clone method in order to allow for copying PersistenceCapable
269: * objects. In contrast to Object.clone(), this method must not throw a
270: * CloneNotSupportedException.
271: */
272:
273: public Object clone() {
274: Object obj = super .clone();
275:
276: ((HashSet) obj).unsetOwner();
277:
278: return obj;
279: }
280:
281: private synchronized void load() {
282: if (!upToDate()) {
283: if (LOG.isDebugEnabled())
284: LOG.debug(toLogString() + " loading from storage");
285:
286: Collection contents = setStore.load(ownerSM);
287: clearInternal();
288: addAllInternal(contents);
289: setIsLoaded();
290: }
291: }
292:
293: private boolean addInternal(Object o) {
294: return super .add(o);
295: }
296:
297: private boolean addAllInternal(Collection c) {
298: boolean modified = false;
299: Iterator i = c.iterator();
300:
301: while (i.hasNext()) {
302: if (super .add(i.next()))
303: modified = true;
304: }
305:
306: return modified;
307: }
308:
309: private Iterator iteratorInternal() {
310: return super .iterator();
311: }
312:
313: private boolean removeInternal(Object o) {
314: return super .remove(o);
315: }
316:
317: private void clearInternal() {
318: super .clear();
319: }
320:
321: private boolean addPersistent(Object o) {
322: if (isPersistent) {
323: addInternal(o);
324: return setStore.add(ownerSM, o);
325: } else
326: return addInternal(o);
327: }
328:
329: private boolean addAllPersistent(Collection c) {
330: SCOHelper.assertAllValidElements(this , c);
331:
332: if (isPersistent) {
333: addAllInternal(c);
334: return setStore.addAll(ownerSM, c);
335: } else
336: return addAllInternal(c);
337: }
338:
339: private boolean removePersistent(Object o) {
340: if (isPersistent) {
341: removeInternal(o);
342: return setStore.remove(ownerSM, o);
343: } else
344: return removeInternal(o);
345: }
346:
347: private void clearPersistent() {
348: if (isPersistent) {
349: setStore.clear(ownerSM);
350: setIsLoaded();
351: }
352:
353: clearInternal();
354: }
355:
356: public Iterator iterator() {
357: return new HashSetIterator();
358: }
359:
360: /* Must use our own Iterator class because of remove(). */
361: private class HashSetIterator implements Iterator {
362: private final Iterator iter;
363: private Object last = null;
364: private int expectedModCount = modCount;
365:
366: public HashSetIterator() {
367: load();
368: ArrayList entries = new ArrayList(size());
369: Iterator i = iteratorInternal();
370:
371: while (i.hasNext())
372: entries.add(i.next());
373:
374: iter = entries.iterator();
375: }
376:
377: public boolean hasNext() {
378: return iter.hasNext();
379: }
380:
381: public Object next() {
382: if (modCount != expectedModCount)
383: throw new ConcurrentModificationException();
384:
385: return last = iter.next();
386: }
387:
388: public void remove() {
389: if (last == null)
390: throw new IllegalStateException();
391: if (modCount != expectedModCount)
392: throw new ConcurrentModificationException();
393:
394: makeDirty();
395: removePersistent(last);
396: last = null;
397: expectedModCount = modCount;
398: }
399: }
400:
401: public int size() {
402: return upToDate() ? super .size() : setStore.size(ownerSM);
403: }
404:
405: public boolean isEmpty() {
406: return upToDate() ? super .isEmpty() : setStore.isEmpty(ownerSM);
407: }
408:
409: public boolean contains(Object o) {
410: if (!SCOHelper.isValidElement(this , o))
411: return false;
412:
413: return upToDate() ? super .contains(o) : setStore.contains(
414: ownerSM, o);
415: }
416:
417: public boolean add(Object o) {
418: SCOHelper.assertIsValidElement(this , o);
419:
420: makeDirty();
421: return addPersistent(o);
422: }
423:
424: public boolean remove(Object o) {
425: if (!SCOHelper.isValidElement(this , o))
426: return false;
427:
428: makeDirty();
429: return removePersistent(o);
430: }
431:
432: public void clear() {
433: makeDirty();
434: clearPersistent();
435: }
436:
437: public boolean containsAll(Collection c) {
438: load();
439: return super .containsAll(c);
440: }
441:
442: public boolean addAll(Collection c) {
443: makeDirty();
444: return addAllPersistent(c);
445: }
446:
447: public boolean removeAll(Collection c) {
448: makeDirty();
449:
450: boolean modified = false;
451: Iterator i = c.iterator();
452:
453: while (i.hasNext()) {
454: Object o = i.next();
455:
456: if (SCOHelper.isValidElement(this , o)
457: && removePersistent(o))
458: modified = true;
459: }
460:
461: return modified;
462: }
463:
464: public boolean retainAll(Collection c) {
465: makeDirty();
466:
467: if (c instanceof SCO)
468: c = new java.util.HashSet(c);
469:
470: boolean modified = false;
471: Iterator i = iterator();
472:
473: while (i.hasNext()) {
474: Object o = i.next();
475:
476: if (!c.contains(o)) {
477: if (SCOHelper.isValidElement(this , o)
478: && removePersistent(o))
479: modified = true;
480: }
481: }
482:
483: return modified;
484: }
485:
486: public Object[] toArray() {
487: load();
488: return super .toArray();
489: }
490:
491: public Object[] toArray(Object a[]) {
492: load();
493: return super .toArray(a);
494: }
495:
496: public boolean equals(Object o) {
497: load();
498: return super .equals(o);
499: }
500:
501: public int hashCode() {
502: load();
503: return super .hashCode();
504: }
505:
506: public String toString() {
507: load();
508: return super .toString();
509: }
510:
511: private String toLogString() {
512: return SCOHelper.toLogString(this );
513: }
514:
515: /**
516: * Replaces the object to be serialized with a java.util.HashSet object.
517: * Invoked by the serialization mechanism to obtain an alternative object
518: * to be used when writing an object to the stream.
519: *
520: * @return
521: * The <code>HashSet</code> to be serialized instead of this object.
522: */
523:
524: protected Object writeReplace() throws ObjectStreamException {
525: return new java.util.HashSet(this);
526: }
527: }
|