001: /**
002: * Copyright (C) 2007 NetMind Consulting Bt.
003: *
004: * This library is free software; you can redistribute it and/or
005: * modify it under the terms of the GNU Lesser General Public
006: * License as published by the Free Software Foundation; either
007: * version 3 of the License, or (at your option) any later version.
008: *
009: * This library is distributed in the hope that it will be useful,
010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012: * Lesser General Public License for more details.
013: *
014: * You should have received a copy of the GNU Lesser General Public
015: * License along with this library; if not, write to the Free Software
016: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
017: */package hu.netmind.persistence;
018:
019: import java.util.*;
020: import hu.netmind.persistence.parser.*;
021: import hu.netmind.persistence.event.*;
022:
023: /**
024: * Custom set implementation based on lazy lists. This is <strong>not</strong>
025: * thread-safe.
026: * @author Brautigam Robert
027: * @version Revision: $Revision$
028: */
029: public class SetImpl extends AbstractSet implements Container {
030: private StoreContext context;
031: private List originalList;
032: private TimeControl originalTimeControl;
033: private ClassInfo parentInfo;
034: private Object parent;
035: private String parentAttributeName;
036: private ContainerItemClass itemClass;
037: private Long lastSerial;
038:
039: private Set addedItems;
040: private Set removedItems;
041: private boolean cleared = false;
042: private int modCount = 0;
043:
044: /**
045: * Initialize with a default list.
046: */
047: public void init(StoreContext context, ClassInfo classInfo,
048: Object obj, String attributeName, String itemClassName,
049: Long lastSerial, TimeControl timeControl) {
050: this .context = context;
051: this .originalList = null;
052: this .originalTimeControl = new TimeControl(timeControl);
053: this .parentInfo = classInfo;
054: this .parent = obj;
055: this .parentAttributeName = attributeName;
056: this .lastSerial = lastSerial;
057: this .itemClass = new ContainerItemClass(itemClassName);
058: // Model
059: reload();
060: }
061:
062: public String getItemClassName() {
063: return itemClass.getItemClassName();
064: }
065:
066: public void reload() {
067: // Initialize internal change structures
068: modCount++;
069: cleared = false;
070: addedItems = new HashSet();
071: removedItems = new HashSet();
072: // Load list
073: if (getItemClassName() == null) {
074: originalList = new Vector();
075: } else {
076: originalList = context.getStore().find(
077: "find item(" + getItemClassName() + ")"
078: + " where parent("
079: + parentInfo.getSourceEntry().getFullName()
080: + ")" + "." + parentAttributeName
081: + " contains item and parent = ?",
082: new Object[] { parent }, originalTimeControl, null);
083: }
084: }
085:
086: /**
087: * Returns whether the container changes internally since last save().
088: */
089: public boolean hasChanged() {
090: return (addedItems.size() > 0) || (removedItems.size() > 0)
091: || (cleared);
092: }
093:
094: /**
095: * Save the container to database.
096: */
097: public void save(Transaction transaction, Long currentSerial,
098: Set waitingObjects, List events) {
099: // If the list was cleared before, then clear the current values
100: if (cleared) {
101: HashMap keyAttributes = new HashMap();
102: keyAttributes.put("persistence_id", new Long(context
103: .getObjectTracker().getIdentifier(parent)));
104: keyAttributes.put("persistence_end", new Long(
105: DateSerialUtil.getMaxSerial()));
106: keyAttributes.put("persistence_txend", new Long(
107: DateSerialUtil.getMaxSerial()));
108: HashMap removeAttributes = new HashMap();
109: removeAttributes.put("persistence_txend", currentSerial);
110: removeAttributes.put("persistence_txendid", transaction
111: .getSerial());
112: context.getDatabase().save(transaction,
113: parentInfo.getSubTableName(parentAttributeName),
114: keyAttributes, removeAttributes);
115: transaction.addRemoveTable(parentInfo
116: .getSubTableName(parentAttributeName));
117: // Notify listeners
118: events.add(new ClearedContainerEvent(parent,
119: parentAttributeName));
120: }
121: // Remove removed items first
122: Iterator removedItemsIterator = removedItems.iterator();
123: while (removedItemsIterator.hasNext()) {
124: ObjectWrapper wrapper = (ObjectWrapper) removedItemsIterator
125: .next();
126: Object obj = wrapper.getObject();
127: // Remove object from the list
128: HashMap keyAttributes = new HashMap();
129: keyAttributes.put("persistence_id", new Long(context
130: .getObjectTracker().getIdentifier(parent)));
131: keyAttributes.put("persistence_end", new Long(
132: DateSerialUtil.getMaxSerial()));
133: keyAttributes.put("persistence_txend", new Long(
134: DateSerialUtil.getMaxSerial()));
135: keyAttributes.put("value", new Long(context
136: .getObjectTracker().getIdentifier(obj)));
137: HashMap removeAttributes = new HashMap();
138: removeAttributes.put("persistence_txend", currentSerial);
139: removeAttributes.put("persistence_txendid", transaction
140: .getSerial());
141: context.getDatabase().save(transaction,
142: parentInfo.getSubTableName(parentAttributeName),
143: keyAttributes, removeAttributes);
144: transaction.addRemoveTable(parentInfo
145: .getSubTableName(parentAttributeName));
146: // Notify listeners
147: events.add(new RemovedItemEvent(parent,
148: parentAttributeName, obj));
149: }
150: // Add added items
151: Iterator addedItemsIterator = addedItems.iterator();
152: while (addedItemsIterator.hasNext()) {
153: ObjectWrapper wrapper = (ObjectWrapper) addedItemsIterator
154: .next();
155: Object obj = wrapper.getObject();
156: // Item does not exist then put it in the save list
157: if (!context.getObjectTracker().exists(obj))
158: waitingObjects.add(context.getObjectTracker()
159: .getWrapper(obj));
160: // Add item
161: HashMap itemAttributes = new HashMap();
162: itemAttributes.put("persistence_id", new Long(context
163: .getObjectTracker().getIdentifier(parent)));
164: itemAttributes.put("persistence_start", new Long(
165: DateSerialUtil.getMaxSerial()));
166: itemAttributes.put("persistence_end", new Long(
167: DateSerialUtil.getMaxSerial()));
168: itemAttributes.put("persistence_txendid", new Long(0));
169: itemAttributes.put("persistence_txstartid", transaction
170: .getSerial());
171: itemAttributes.put("persistence_txstart", currentSerial);
172: itemAttributes.put("persistence_txend", new Long(
173: DateSerialUtil.getMaxSerial()));
174: itemAttributes.put("value", new Long(context
175: .getObjectTracker().getIdentifier(obj)));
176: context.getDatabase().insert(transaction,
177: parentInfo.getSubTableName(parentAttributeName),
178: itemAttributes);
179: transaction.addSaveTable(parentInfo
180: .getSubTableName(parentAttributeName));
181: // Notify listeners
182: events.add(new AddedItemEvent(parent, parentAttributeName,
183: obj));
184: }
185: // Reload the changed list. Note, that the list should not
186: // be referenced until the Store.save() operation completes, because
187: // the list will not contain nonexisting object currently.
188: originalTimeControl = new TimeControl(currentSerial,
189: transaction.getSerial(), true);
190: lastSerial = currentSerial;
191: }
192:
193: /**
194: * Get the serial number of last modification.
195: */
196: public Long getLastSerial() {
197: return lastSerial;
198: }
199:
200: public boolean retainAll(Object c) {
201: return retainAll((Collection) c);
202: }
203:
204: public boolean addAll(Object c) {
205: return addAll((Collection) c);
206: }
207:
208: /*
209: * Implementing Set
210: */
211:
212: public boolean add(Object item) {
213: // Check item validity
214: if (item == null)
215: throw new IllegalArgumentException(
216: "set does not accept null values.");
217: int type = context.getClassTracker().getType(item.getClass());
218: if ((type != ClassTracker.TYPE_OBJECT)
219: && (type != ClassTracker.TYPE_PRIMITIVE))
220: throw new IllegalArgumentException(
221: "set only handles object or primitive types, but was: "
222: + item + " (" + item.getClass().getName()
223: + ")");
224: // Ensure item exists
225: if (!contains(item)) {
226: // Object does not exist. If it does not exist because it is
227: // in the remove list, then simply remove from remove list.
228: ObjectWrapper wrapper = new ObjectWrapper(item);
229: if (removedItems.contains(wrapper)) {
230: removedItems.remove(wrapper);
231: } else {
232: itemClass.updateItemClassName(originalList, item
233: .getClass(), addedItems.size() == 0);
234: addedItems.add(wrapper);
235: }
236: modCount++;
237: return true;
238: }
239: return false;
240: }
241:
242: public void clear() {
243: // Clear content and mark cleared flag
244: cleared = true;
245: addedItems = new HashSet();
246: removedItems = new HashSet();
247: originalList = new Vector();
248: itemClass.clear();
249: modCount++;
250: }
251:
252: public boolean contains(Object item) {
253: if (item == null)
254: return false;
255: int type = context.getClassTracker().getType(item.getClass());
256: ObjectWrapper wrapper = new ObjectWrapper(item);
257: // If the item is removed, then it is not contained.
258: if (removedItems.contains(wrapper))
259: return false;
260: // If it is added, then it is contained.
261: if (addedItems.contains(wrapper))
262: return true;
263: // Else, check the list
264: if (originalList.size() > LazyList.BATCH_SIZE) {
265: // This means, that the backing lazy list would page if we
266: // were to iterate. So instead, we run a specific query for
267: // the given id.
268: Transaction tx = context.getTransactionTracker()
269: .getTransaction(TransactionTracker.TX_REQUIRED);
270: tx.begin();
271: try {
272: ClassInfo itemInfo = context.getClassTracker()
273: .getClassInfo(item.getClass(), item);
274: String itemTableName = itemInfo.getTableName(itemInfo
275: .getSourceEntry());
276: TableTerm itemTableTerm = new TableTerm(itemTableName,
277: null);
278: String listTableName = parentInfo
279: .getSubTableName(parentAttributeName);
280: TableTerm listTableTerm = new TableTerm(listTableName,
281: null);
282: Expression listExpression = new Expression();
283: listExpression.add(new ReferenceTerm(listTableTerm,
284: "persistence_id"));
285: listExpression.add("=");
286: listExpression.add(new ConstantTerm(new Long(context
287: .getObjectTracker().getIdentifier(parent))));
288: listExpression.add("and");
289: if (type != ClassTracker.TYPE_PRIMITIVE) {
290: listExpression.add(new ReferenceTerm(listTableTerm,
291: "value"));
292: listExpression.add("=");
293: listExpression.add(new ConstantTerm(new Long(
294: wrapper.getIdentifier())));
295: } else {
296: listExpression.add(new ReferenceTerm(itemTableTerm,
297: "value"));
298: listExpression.add("=");
299: listExpression.add(new ConstantTerm(item));
300: }
301: listExpression.add("and");
302: listExpression.add(new ReferenceTerm(itemTableTerm,
303: "persistence_id"));
304: listExpression.add("=");
305: listExpression.add(new ReferenceTerm(listTableTerm,
306: "value"));
307: listExpression.add("and");
308: originalTimeControl
309: .apply(listExpression, itemTableTerm);
310: listExpression.add("and");
311: originalTimeControl
312: .apply(listExpression, listTableTerm);
313: // Execute. If there is a hit, then object exists
314: QueryStatement stmt = new QueryStatement(listTableTerm,
315: listExpression, null);
316: stmt.setTimeControl(originalTimeControl);
317: stmt.setStaticRepresentation("FIND "
318: + listTableTerm
319: + " WHERE persistence_id = "
320: + context.getObjectTracker().getIdentifier(
321: parent) + " and value = "
322: + wrapper.getIdentifier());
323: SearchResult result = context.getStore().find(tx, stmt,
324: new Limits(0, 0, -1));
325: // Check
326: if (result.getResultSize() > 0)
327: return true; // Object exists
328: } finally {
329: tx.commit();
330: }
331: } else {
332: // Set is small, so iterate
333: for (int i = 0; i < originalList.size(); i++) {
334: Object obj = originalList.get(i);
335: if (((type == ClassTracker.TYPE_PRIMITIVE) && (item
336: .equals(obj)))
337: || ((type != ClassTracker.TYPE_PRIMITIVE) && (context
338: .getObjectTracker().getIdentifier(obj) == wrapper
339: .getIdentifier())))
340: return true;
341: }
342: }
343: // Fall through
344: return false;
345: }
346:
347: /**
348: * Determine whether list equals another list in content.
349: */
350: public boolean equals(Object obj) {
351: // If it is not a collection, then it surely does not equal.
352: if (!(obj instanceof Collection))
353: return false;
354: // If they are the same size, and all items equal, then the
355: // collections equal.
356: Collection c = (Collection) obj;
357: return (size() == c.size()) && (containsAll(c));
358: }
359:
360: /**
361: * Get iterator.
362: */
363: public Iterator iterator() {
364: return new SetImplIteratorImpl();
365: }
366:
367: /**
368: * Remove object from set.
369: */
370: public boolean remove(Object item) {
371: // Ensure item exists
372: if (contains(item)) {
373: // Object does exist. If it does exist because it is
374: // in the added list, then simply remove from added list.
375: ObjectWrapper wrapper = new ObjectWrapper(item);
376: if (addedItems.contains(wrapper))
377: addedItems.remove(wrapper);
378: else
379: removedItems.add(wrapper);
380: modCount++;
381: return true;
382: }
383: return false;
384: }
385:
386: public int size() {
387: return originalList.size() - removedItems.size()
388: + addedItems.size();
389: }
390:
391: public String toString() {
392: return originalList.toString();
393: }
394:
395: public class SetImplIteratorImpl implements Iterator {
396: private int ownModCount;
397: private int index;
398: private boolean hasNext;
399: private Object next;
400: private Object current;
401: private Iterator addedIterator;
402:
403: public SetImplIteratorImpl() {
404: index = 0;
405: ownModCount = modCount;
406: addedIterator = addedItems.iterator();
407: // preread
408: read();
409: }
410:
411: private void read() {
412: // Ensure that no modification took place
413: if (ownModCount != modCount)
414: throw new java.util.ConcurrentModificationException(
415: "set was modified while iterating");
416: // Check whether there are elements left in the list
417: while (index < originalList.size()) {
418: Object obj = originalList.get(index);
419: index++; // Next
420: ObjectWrapper wrapper = new ObjectWrapper(obj);
421: if (!removedItems.contains(wrapper)) {
422: // This is a valid item
423: hasNext = true;
424: next = obj;
425: return;
426: }
427: }
428: // If there were no elements in the original list, check through
429: // the added items.
430: if (addedIterator.hasNext()) {
431: hasNext = true;
432: next = ((ObjectWrapper) addedIterator.next())
433: .getObject();
434: return;
435: }
436: // Fall through
437: hasNext = false;
438: next = null;
439: }
440:
441: public void remove() {
442: // Remove the current item
443: if (index <= originalList.size()) {
444: // The current item is in the original list, not in the
445: // added list, insert into the removed list.
446: removedItems.add(new ObjectWrapper(current));
447: } else {
448: // The current item is in the added list, so remove from it.
449: addedIterator.remove();
450: // Modify the modcount, because the whole set changed
451: }
452: modCount++;
453: ownModCount = modCount; // The modcount changed, but we're safe
454: }
455:
456: public boolean hasNext() {
457: return hasNext;
458: }
459:
460: public Object next() {
461: current = next;
462: read(); // Pre-read next
463: return current;
464: }
465: }
466:
467: public class ObjectWrapper {
468: private Object obj;
469: private long id;
470: private int type;
471:
472: public ObjectWrapper(Object obj) {
473: this .obj = obj;
474: context.getObjectTracker().registerObject(obj, 0);
475: this .id = context.getObjectTracker().getIdentifier(obj);
476: this .type = context.getClassTracker().getType(
477: obj.getClass());
478: }
479:
480: public long getIdentifier() {
481: return id;
482: }
483:
484: public Object getObject() {
485: return obj;
486: }
487:
488: public int hashCode() {
489: if (type == ClassTracker.TYPE_PRIMITIVE)
490: return obj.hashCode();
491: else
492: return (int) (id >> 32);
493: }
494:
495: public boolean equals(Object rhs) {
496: if (!(rhs instanceof ObjectWrapper))
497: return false;
498: if (type == ClassTracker.TYPE_PRIMITIVE)
499: return obj.equals(((ObjectWrapper) rhs).obj);
500: else
501: return id == ((ObjectWrapper) rhs).id;
502: }
503:
504: }
505: }
|