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: import org.apache.log4j.Logger;
023:
024: /**
025: * Custom map implementation based on lazy lists. Map is <strong>not</strong>
026: * thread-safe.
027: * @author Brautigam Robert
028: * @version Revision: $Revision$
029: */
030: public class MapImpl extends AbstractMap implements Container {
031: private static Logger logger = Logger.getLogger(MapImpl.class);
032:
033: private StoreContext context;
034: private List originalList;
035: private TimeControl originalTimeControl;
036: private ClassInfo parentInfo;
037: private Object parent;
038: private String parentAttributeName;
039: private Long lastSerial;
040: private ContainerItemClass itemClass;
041:
042: private Map addedItems;
043: private Map removedItems;
044: private boolean cleared = false;
045: private int modCount = 0;
046:
047: /**
048: * Initialize with a default list.
049: */
050: public void init(StoreContext context, ClassInfo classInfo,
051: Object obj, String attributeName, String itemClassName,
052: Long lastSerial, TimeControl timeControl) {
053: this .context = context;
054: this .originalList = null;
055: this .originalTimeControl = new TimeControl(timeControl);
056: this .parentInfo = classInfo;
057: this .parent = obj;
058: this .parentAttributeName = attributeName;
059: this .lastSerial = lastSerial;
060: this .itemClass = new ContainerItemClass(itemClassName);
061: // Model
062: reload();
063: }
064:
065: public String getItemClassName() {
066: return itemClass.getItemClassName();
067: }
068:
069: public void reload() {
070: // Initialize internal change structures
071: modCount++;
072: cleared = false;
073: addedItems = new HashMap();
074: removedItems = new HashMap();
075: // Load list
076: if (getItemClassName() == null) {
077: // No list
078: originalList = new Vector();
079: } else {
080: originalList = context.getStore().find(
081: "find item(" + getItemClassName() + ")"
082: + " where parent("
083: + parentInfo.getSourceEntry().getFullName()
084: + ")" + "." + parentAttributeName
085: + " contains item and parent = ?",
086: new Object[] { parent }, originalTimeControl, null);
087: // Modify the select statements.
088: String subTableName = parentInfo
089: .getSubTableName(parentAttributeName);
090: LazyList lazy = (LazyList) originalList;
091: for (int i = 0; i < lazy.getStmts().size(); i++) {
092: QueryStatement stmt = (QueryStatement) lazy.getStmts()
093: .get(i);
094: // First search for the table term of the subtable
095: Expression expr = stmt.getQueryExpression();
096: TableTerm subTableTerm = expr
097: .getTableTerm(subTableName);
098: // Add this term to selected terms
099: stmt.getSelectTerms()
100: .add(
101: new ReferenceTerm(subTableTerm,
102: "container_key"));
103: // Add the correct order by
104: stmt.getOrderByList().add(
105: 0,
106: new OrderBy(new ReferenceTerm(subTableTerm,
107: "container_key"), OrderBy.ASCENDING));
108: // Correct static string
109: stmt.setStaticRepresentation(stmt
110: .getStaticRepresentation()
111: + " and mapkey");
112: }
113: }
114: }
115:
116: /**
117: * Returns whether the container changes internally since last save().
118: */
119: public boolean hasChanged() {
120: return (addedItems.size() > 0) || (removedItems.size() > 0)
121: || (cleared);
122: }
123:
124: /**
125: * Save the container to database.
126: */
127: public void save(Transaction transaction, Long currentSerial,
128: Set waitingObjects, List events) {
129: // If the list was cleared before, then clear the current values
130: if (cleared) {
131: HashMap keyAttributes = new HashMap();
132: keyAttributes.put("persistence_id", new Long(context
133: .getObjectTracker().getIdentifier(parent)));
134: keyAttributes.put("persistence_end", new Long(
135: DateSerialUtil.getMaxSerial()));
136: keyAttributes.put("persistence_txend", new Long(
137: DateSerialUtil.getMaxSerial()));
138: HashMap removeAttributes = new HashMap();
139: removeAttributes.put("persistence_txend", currentSerial);
140: removeAttributes.put("persistence_txendid", transaction
141: .getSerial());
142: context.getDatabase().save(transaction,
143: parentInfo.getSubTableName(parentAttributeName),
144: keyAttributes, removeAttributes);
145: transaction.addRemoveTable(parentInfo
146: .getSubTableName(parentAttributeName));
147: // Notify listeners
148: events.add(new ClearedContainerEvent(parent,
149: parentAttributeName));
150: }
151: // Remove removed items first
152: Iterator removedItemsIterator = removedItems.entrySet()
153: .iterator();
154: while (removedItemsIterator.hasNext()) {
155: Map.Entry entry = (Map.Entry) removedItemsIterator.next();
156: // Remove object from the list
157: HashMap keyAttributes = new HashMap();
158: keyAttributes.put("persistence_id", new Long(context
159: .getObjectTracker().getIdentifier(parent)));
160: keyAttributes.put("persistence_end", new Long(
161: DateSerialUtil.getMaxSerial()));
162: keyAttributes.put("persistence_txend", new Long(
163: DateSerialUtil.getMaxSerial()));
164: keyAttributes.put("container_key", entry.getKey()
165: .toString());
166: HashMap removeAttributes = new HashMap();
167: removeAttributes.put("persistence_txend", currentSerial);
168: removeAttributes.put("persistence_txendid", transaction
169: .getSerial());
170: context.getDatabase().save(transaction,
171: parentInfo.getSubTableName(parentAttributeName),
172: keyAttributes, removeAttributes);
173: transaction.addRemoveTable(parentInfo
174: .getSubTableName(parentAttributeName));
175: // Notify listeners
176: events.add(new RemovedItemEvent(parent,
177: parentAttributeName, new SimpleMapEntry(entry)));
178: }
179: // Add added items
180: Iterator addedItemsIterator = addedItems.entrySet().iterator();
181: while (addedItemsIterator.hasNext()) {
182: Map.Entry entry = (Map.Entry) addedItemsIterator.next();
183: ObjectWrapper wrapper = (ObjectWrapper) entry.getValue();
184: Object value = wrapper.getObject();
185: // Item does not exist then put it in the save list
186: if (!context.getObjectTracker().exists(value))
187: waitingObjects.add(context.getObjectTracker()
188: .getWrapper(value));
189: // Add item
190: HashMap itemAttributes = new HashMap();
191: itemAttributes.put("persistence_id", new Long(context
192: .getObjectTracker().getIdentifier(parent)));
193: itemAttributes.put("persistence_start", new Long(
194: DateSerialUtil.getMaxSerial()));
195: itemAttributes.put("persistence_end", new Long(
196: DateSerialUtil.getMaxSerial()));
197: itemAttributes.put("persistence_txendid", new Long(0));
198: itemAttributes.put("persistence_txstartid", transaction
199: .getSerial());
200: itemAttributes.put("persistence_txstart", currentSerial);
201: itemAttributes.put("persistence_txend", new Long(
202: DateSerialUtil.getMaxSerial()));
203: itemAttributes.put("container_key", entry.getKey()
204: .toString());
205: itemAttributes.put("value", new Long(context
206: .getObjectTracker().getIdentifier(value)));
207: context.getDatabase().insert(transaction,
208: parentInfo.getSubTableName(parentAttributeName),
209: itemAttributes);
210: transaction.addSaveTable(parentInfo
211: .getSubTableName(parentAttributeName));
212: // Notify listeners
213: events.add(new AddedItemEvent(parent, parentAttributeName,
214: new SimpleMapEntry(entry)));
215: }
216: // Reload the changed list. Note, that the list should not
217: // be referenced until the Store.save() operation completes, because
218: // the list will not contain nonexisting object currently.
219: originalTimeControl = new TimeControl(currentSerial,
220: transaction.getSerial(), true);
221: lastSerial = currentSerial;
222: }
223:
224: /**
225: * Get the serial number of last modification.
226: */
227: public Long getLastSerial() {
228: return lastSerial;
229: }
230:
231: /**
232: * Retain all container elements inside the other container.
233: */
234: public boolean retainAll(Object c) {
235: // Go through all elements, if it's not contained in c,
236: // then remove (using the iterator!)
237: boolean changed = false;
238: Iterator iterator = entrySet().iterator();
239: if (logger.isDebugEnabled())
240: logger.debug("map retain all: " + toString() + " from "
241: + c.toString());
242: while (iterator.hasNext()) {
243: Map.Entry entry = (Map.Entry) iterator.next();
244: if (!((Map) c).containsKey(entry.getKey())) {
245: logger.debug("trying to remove key: " + entry.getKey());
246: iterator.remove();
247: changed = true;
248: }
249: }
250: return changed;
251: }
252:
253: /**
254: * Add all items in the other container.
255: */
256: public boolean addAll(Object c) {
257: putAll((Map) c);
258: return true;
259: }
260:
261: /*
262: * Map implementation.
263: */
264:
265: public void clear() {
266: // Clear content and mark cleared flag
267: cleared = true;
268: addedItems = new HashMap();
269: removedItems = new HashMap();
270: originalList = new Vector();
271: itemClass.clear();
272: modCount++;
273: }
274:
275: /**
276: * Return whether this map contains the specified key.
277: */
278: public boolean containsKey(Object key) {
279: // If the item is removed, then it is not contained.
280: if (removedItems.containsKey(key))
281: return false;
282: // If it is added, then it is contained.
283: if (addedItems.containsKey(key))
284: return true;
285: // Else, check the list
286: if (originalList.size() > LazyList.BATCH_SIZE) {
287: // This means, that the backing lazy list would page if we
288: // were to iterate. So instead, we run a specific query for
289: // the given id.
290: Transaction tx = context.getTransactionTracker()
291: .getTransaction(TransactionTracker.TX_REQUIRED);
292: tx.begin();
293: try {
294: String listTableName = parentInfo
295: .getSubTableName(parentAttributeName);
296: TableTerm listTableTerm = new TableTerm(listTableName,
297: null);
298: Expression listExpression = new Expression();
299: listExpression.add(new ReferenceTerm(listTableTerm,
300: "persistence_id"));
301: listExpression.add("=");
302: listExpression.add(new ConstantTerm(new Long(context
303: .getObjectTracker().getIdentifier(parent))));
304: listExpression.add("and");
305: listExpression.add(new ReferenceTerm(listTableTerm,
306: "container_key"));
307: listExpression.add("=");
308: listExpression.add(new ConstantTerm(key.toString()));
309: listExpression.add("and");
310: originalTimeControl
311: .apply(listExpression, listTableTerm);
312: // Execute. If there is a hit, then object exists
313: QueryStatement stmt = new QueryStatement(listTableTerm,
314: listExpression, null);
315: stmt.setTimeControl(originalTimeControl);
316: stmt.setStaticRepresentation("FIND "
317: + listTableTerm
318: + " WHERE persistence_id = "
319: + context.getObjectTracker().getIdentifier(
320: parent) + " and mapkey = " + key);
321: SearchResult result = context.getStore().find(tx, stmt,
322: new Limits(0, 0, -1));
323: // Check
324: if (result.getResultSize() > 0)
325: return true; // Object exists
326: } finally {
327: tx.commit();
328: }
329: } else {
330: // Set is small, so iterate
331: for (int i = 0; i < originalList.size(); i++) {
332: Map obj = (Map) originalList.get(i);
333: if (key.equals(obj.get("container_key")))
334: return true;
335: }
336: }
337: // Fall through
338: return false;
339: }
340:
341: /**
342: * Returns whether this map contains one or more of the
343: * specified value.
344: */
345: public boolean containsValue(Object value) {
346: if (value == null)
347: return false;
348: int type = context.getClassTracker().getType(value.getClass());
349: ObjectWrapper wrapper = new ObjectWrapper(value);
350: long id = context.getObjectTracker().getIdentifier(value);
351: if (id == 0)
352: return false; // Can not contain value with no id
353: // If the item is removed, then it is not contained.
354: if (removedItems.containsValue(wrapper))
355: return false;
356: // If it is added, then it is contained.
357: if (addedItems.containsValue(wrapper))
358: return true;
359: // Else, check the list
360: if (originalList.size() > LazyList.BATCH_SIZE) {
361: // This means, that the backing lazy list would page if we
362: // were to iterate. So instead, we run a specific query for
363: // the given id.
364: Transaction tx = context.getTransactionTracker()
365: .getTransaction(TransactionTracker.TX_REQUIRED);
366: tx.begin();
367: try {
368: ClassInfo itemInfo = context.getClassTracker()
369: .getClassInfo(value.getClass(), value);
370: String itemTableName = itemInfo.getTableName(itemInfo
371: .getSourceEntry());
372: TableTerm itemTableTerm = new TableTerm(itemTableName,
373: null);
374: String listTableName = parentInfo
375: .getSubTableName(parentAttributeName);
376: TableTerm listTableTerm = new TableTerm(listTableName,
377: null);
378: Expression listExpression = new Expression();
379: listExpression.add(new ReferenceTerm(listTableTerm,
380: "persistence_id"));
381: listExpression.add("=");
382: listExpression.add(new ConstantTerm(new Long(context
383: .getObjectTracker().getIdentifier(parent))));
384: listExpression.add("and");
385: if (type != ClassTracker.TYPE_PRIMITIVE) {
386: listExpression.add(new ReferenceTerm(listTableTerm,
387: "value"));
388: listExpression.add("=");
389: listExpression.add(new ConstantTerm(new Long(id)));
390: } else {
391: listExpression.add(new ReferenceTerm(itemTableTerm,
392: "value"));
393: listExpression.add("=");
394: listExpression.add(new ConstantTerm(value));
395: }
396: listExpression.add("and");
397: listExpression.add(new ReferenceTerm(itemTableTerm,
398: "persistence_id"));
399: listExpression.add("=");
400: listExpression.add(new ReferenceTerm(listTableTerm,
401: "value"));
402: listExpression.add("and");
403: originalTimeControl
404: .apply(listExpression, itemTableTerm);
405: listExpression.add("and");
406: originalTimeControl
407: .apply(listExpression, listTableTerm);
408: // Execute. If there is a hit, then object exists
409: QueryStatement stmt = new QueryStatement(listTableTerm,
410: listExpression, null);
411: stmt.setTimeControl(originalTimeControl);
412: stmt.setStaticRepresentation("FIND "
413: + listTableTerm
414: + " WHERE persistence_id = "
415: + context.getObjectTracker().getIdentifier(
416: parent) + " and value = " + id);
417: SearchResult result = context.getStore().find(tx, stmt,
418: new Limits(0, 0, -1));
419: // Check
420: if (result.getResultSize() > 0)
421: return true; // Object exists
422: } finally {
423: tx.commit();
424: }
425: } else {
426: // Set is small, so iterate
427: for (int i = 0; i < originalList.size(); i++) {
428: Map obj = (Map) originalList.get(i);
429: if (((type == ClassTracker.TYPE_PRIMITIVE) && (value
430: .equals(obj.get("object"))))
431: || ((type != ClassTracker.TYPE_PRIMITIVE) && (id == context
432: .getObjectTracker().getIdentifier(
433: obj.get("object")))))
434: return true;
435: }
436: }
437: // Fall through
438: return false;
439: }
440:
441: /**
442: * Returns a set view of this map.
443: */
444: public Set entrySet() {
445: return new MapImplEntrySet();
446: }
447:
448: /**
449: * Determine whether map equals another map in content.
450: */
451: public boolean equals(Object obj) {
452: // If it is not a map, then it surely does not equal.
453: if (!(obj instanceof Map))
454: return false;
455: // If they are the same size, and all items equal, then the
456: // maps equal.
457: Map c = (Map) obj;
458: return (size() == c.size())
459: && (entrySet().containsAll(c.entrySet()));
460: }
461:
462: /**
463: * Get the value for a mapkey.
464: */
465: public Object get(Object key) {
466: // If contained in the added list, return that
467: ObjectWrapper wrapper = (ObjectWrapper) addedItems.get(key);
468: if (wrapper != null)
469: return wrapper.getObject();
470: // If contained in the remove list, then the result is null.
471: if (removedItems.containsKey(key))
472: return null;
473: // Not found until now, so check the list
474: if (originalList.size() > LazyList.BATCH_SIZE) {
475: // The list is too large, so select the item separately
476: List result = context.getStore()
477: .find(
478: "find item(object)"
479: + " where parent("
480: + parentInfo.getSourceEntry()
481: .getSourceClass().getName()
482: + ")" + "." + parentAttributeName
483: + "['" + key.toString()
484: + "']=item and parent = ?",
485: new Object[] { parent },
486: originalTimeControl, null);
487: if (result.size() < 1)
488: return null;
489: return result.get(0);
490: } else {
491: // List is small, so iterate
492: for (int i = 0; i < originalList.size(); i++) {
493: Map obj = (Map) originalList.get(i);
494: if (key.equals(obj.get("container_key")))
495: return obj.get("object"); // Found key
496: }
497: }
498: // Fall through
499: return null; // Not found
500: }
501:
502: /**
503: * Put an item into the map.
504: */
505: public Object put(Object key, Object value) {
506: // Check value validity
507: if (value == null)
508: throw new IllegalArgumentException(
509: "map implementation not accepting null values.");
510: int type = context.getClassTracker().getType(value.getClass());
511: if ((type != ClassTracker.TYPE_OBJECT)
512: && (type != ClassTracker.TYPE_PRIMITIVE))
513: throw new IllegalArgumentException(
514: "map only handles object or primitive types, but was: "
515: + value + " (" + value.getClass().getName()
516: + ")");
517: // Determine the old value if there was one
518: Object oldValue = get(key);
519: // If the old value is the same, then skip
520: if ((oldValue != null)
521: && (context.getObjectTracker().getIdentifier(value) == context
522: .getObjectTracker().getIdentifier(oldValue)))
523: return oldValue;
524: // Remove the key if there is an oldvalue
525: if (oldValue != null)
526: remove(key);
527: // Then add the entry, now that is it not contained
528: itemClass.updateItemClassName(originalList, value.getClass(),
529: addedItems.size() == 0);
530: addedItems.put(key, new ObjectWrapper(value));
531: // Modification took place
532: modCount++;
533: // Return with the old value of the key
534: logger.debug("added key: " + key);
535: return oldValue;
536: }
537:
538: /**
539: * Remove a key from the map.
540: */
541: public Object remove(Object key) {
542: // Get the old value
543: Object oldValue = get(key);
544: if (oldValue == null)
545: return null; // Not contained, so no op.
546: // Determine whether key is in the added list. If it is,
547: // then simply remove it, so it will not be added. Else, add to
548: // the removed list.
549: if (addedItems.containsKey(key))
550: addedItems.remove(key);
551: else
552: removedItems.put(key, new ObjectWrapper(oldValue));
553: // Modification took place
554: modCount++;
555: // Return with old value
556: logger.debug("removed key: " + key);
557: return oldValue;
558: }
559:
560: public int size() {
561: return originalList.size() - removedItems.size()
562: + addedItems.size();
563: }
564:
565: public String toString() {
566: return originalList.toString();
567: }
568:
569: public class MapImplEntrySet extends AbstractSet {
570: public void clear() {
571: MapImpl.this .clear();
572: }
573:
574: public boolean contains(Object o) {
575: Map.Entry entry = (Map.Entry) o;
576: ObjectWrapper wrapper = new ObjectWrapper(entry.getValue());
577: long id = context.getObjectTracker().getIdentifier(
578: entry.getValue());
579: if (id == 0)
580: return false; // Can not contain value with no id
581: // If the item is removed, then it is not contained.
582: if (removedItems.containsKey(entry.getKey()))
583: return false;
584: // If it is added, then it is contained and is the same value.
585: if (addedItems.containsKey(entry.getKey()))
586: return addedItems.get(entry.getKey()).equals(wrapper);
587: // Else, check the list
588: if (originalList.size() > LazyList.BATCH_SIZE) {
589: // This means, that the backing lazy list would page if we
590: // were to iterate. So instead, we run a specific query for
591: // the given id.
592: Transaction tx = context.getTransactionTracker()
593: .getTransaction(TransactionTracker.TX_REQUIRED);
594: tx.begin();
595: try {
596: String listTableName = parentInfo
597: .getSubTableName(parentAttributeName);
598: TableTerm listTableTerm = new TableTerm(
599: listTableName, null);
600: Expression listExpression = new Expression();
601: listExpression.add(new ReferenceTerm(listTableTerm,
602: "persistence_id"));
603: listExpression.add("=");
604: listExpression.add(new ConstantTerm(new Long(
605: context.getObjectTracker().getIdentifier(
606: parent))));
607: listExpression.add("and");
608: listExpression.add(new ReferenceTerm(listTableTerm,
609: "value"));
610: listExpression.add("=");
611: listExpression.add(new ConstantTerm(new Long(id)));
612: listExpression.add("and");
613: listExpression.add(new ReferenceTerm(listTableTerm,
614: "container_key"));
615: listExpression.add("=");
616: listExpression
617: .add(new ConstantTerm(entry.getKey()));
618: listExpression.add("and");
619: originalTimeControl.apply(listExpression,
620: listTableTerm);
621: // Execute. If there is a hit, then object exists
622: QueryStatement stmt = new QueryStatement(
623: listTableTerm, listExpression, null);
624: stmt.setTimeControl(originalTimeControl);
625: stmt.setStaticRepresentation("FIND "
626: + listTableTerm
627: + " WHERE persistence_id = "
628: + context.getObjectTracker().getIdentifier(
629: parent) + " and value = " + id
630: + ", mapkey = " + entry.getKey());
631: SearchResult result = context.getStore().find(tx,
632: stmt, new Limits(0, 0, -1));
633: // Check
634: if (result.getResultSize() > 0)
635: return true; // Object exists
636: } finally {
637: tx.commit();
638: }
639: } else {
640: // Set is small, so iterate
641: for (int i = 0; i < originalList.size(); i++) {
642: Map obj = (Map) originalList.get(i);
643: if (obj.get("container_key").equals(entry.getKey()))
644: return (id == context.getObjectTracker()
645: .getIdentifier(obj.get("object")));
646: }
647: }
648: // Fall through
649: return false;
650: }
651:
652: public Iterator iterator() {
653: return new MapImplEntrySetIterator();
654: }
655:
656: public boolean remove(Object o) {
657: return MapImpl.this .remove(((Map.Entry) o).getKey()) != null;
658: }
659:
660: public int size() {
661: return MapImpl.this .size();
662: }
663:
664: public String toString() {
665: return originalList.toString();
666: }
667: }
668:
669: public class MapImplEntrySetIterator implements Iterator {
670: private int ownModCount;
671: private int index;
672: private boolean hasNext;
673: private Map.Entry next;
674: private Map.Entry current;
675: private Iterator addedIterator;
676:
677: public MapImplEntrySetIterator() {
678: index = 0;
679: ownModCount = modCount;
680: addedIterator = addedItems.entrySet().iterator();
681: // preread
682: read();
683: }
684:
685: private void read() {
686: // Ensure that no modification took place
687: if (ownModCount != modCount)
688: throw new java.util.ConcurrentModificationException(
689: "map was modified while iterating");
690: // Check whether there are elements left in the list
691: while (index < originalList.size()) {
692: Map obj = (Map) originalList.get(index);
693: index++; // Next
694: Object key = obj.get("container_key");
695: if (!removedItems.containsKey(key)) {
696: // This is a valid item, because it is not removed
697: hasNext = true;
698: next = new MapImplEntry(obj);
699: return;
700: }
701: if (addedItems.containsKey(key)) {
702: // This is a valid item, because it's key is here, but
703: // it was altered with another value. In this case we
704: // place the pair here, in it's original place.
705: hasNext = true;
706: next = new MapImplEntry(key,
707: (ObjectWrapper) addedItems.get(key));
708: return;
709: }
710: }
711: // If there were no elements in the original list, check through
712: // the added items. Note, that if the added key was already iterated
713: // through in the original list, that key has to be skipped here.
714: if (addedIterator.hasNext()) {
715: Map.Entry entry = (Map.Entry) addedIterator.next();
716: if (!removedItems.containsKey(entry.getKey())) {
717: // The key is not removed, meaning it is really a new key,
718: // which was not contained in the original list.
719: hasNext = true;
720: next = new MapImplEntry(entry);
721: return;
722: }
723: }
724: // Fall through
725: hasNext = false;
726: next = null;
727: }
728:
729: public void remove() {
730: // Remove the current item
731: if (index <= originalList.size()) {
732: // The current item is in the original list, not in the
733: // added list, insert into the removed list.
734: removedItems.put(current.getKey(), new ObjectWrapper(
735: current.getValue()));
736: } else {
737: // The current item is in the added list, so remove from it.
738: addedIterator.remove();
739: // Modify the modcount, because the whole set changed
740: }
741: modCount++;
742: ownModCount = modCount; // The modcount changed, but we're safe
743: }
744:
745: public boolean hasNext() {
746: return hasNext;
747: }
748:
749: public Object next() {
750: current = next;
751: read(); // Pre-read next
752: return current;
753: }
754:
755: public class MapImplEntry implements Map.Entry {
756: private Object key;
757: private Object value;
758:
759: public MapImplEntry(Map obj) {
760: key = obj.get("container_key");
761: value = obj.get("object");
762: }
763:
764: public MapImplEntry(Map.Entry entry) {
765: key = entry.getKey();
766: value = ((ObjectWrapper) entry.getValue()).getObject();
767: }
768:
769: public MapImplEntry(Object key, ObjectWrapper wrapper) {
770: this .key = key;
771: this .value = wrapper.getObject();
772: }
773:
774: public Object getKey() {
775: return key;
776: }
777:
778: public Object getValue() {
779: return value;
780: }
781:
782: public Object setValue(Object value) {
783: // This will modify the map, but we're ok, because
784: // it won't hurt our iteration.
785: Object oldValue = this .value;
786: put(key, value);
787: this .value = value;
788: ownModCount = modCount;
789: return oldValue;
790: }
791: }
792: }
793:
794: public static class SimpleMapEntry implements Map.Entry {
795: private Object key;
796: private Object value;
797:
798: public SimpleMapEntry(Map.Entry entry) {
799: this .key = entry.getKey();
800: this .value = ((ObjectWrapper) entry.getValue()).getObject();
801: }
802:
803: public Object getKey() {
804: return key;
805: }
806:
807: public Object getValue() {
808: return value;
809: }
810:
811: public Object setValue(Object value) {
812: Object oldValue = this .value;
813: this .value = value;
814: return oldValue;
815: }
816: }
817:
818: public class ObjectWrapper {
819: private Object obj;
820: private long id;
821: private int type;
822:
823: public ObjectWrapper(Object obj) {
824: this .obj = obj;
825: context.getObjectTracker().registerObject(obj, 0);
826: this .id = context.getObjectTracker().getIdentifier(obj);
827: this .type = context.getClassTracker().getType(
828: obj.getClass());
829: }
830:
831: public long getIdentifier() {
832: return id;
833: }
834:
835: public Object getObject() {
836: return obj;
837: }
838:
839: public int hashCode() {
840: if (type == ClassTracker.TYPE_PRIMITIVE)
841: return obj.hashCode();
842: else
843: return (int) (id >> 32);
844: }
845:
846: public boolean equals(Object rhs) {
847: if (!(rhs instanceof ObjectWrapper))
848: return false;
849: if (type == ClassTracker.TYPE_PRIMITIVE)
850: return obj.equals(((ObjectWrapper) rhs).obj);
851: else
852: return id == ((ObjectWrapper) rhs).id;
853: }
854:
855: }
856: }
|