0001: /**
0002: * Copyright (C) 2007 NetMind Consulting Bt.
0003: *
0004: * This library is free software; you can redistribute it and/or
0005: * modify it under the terms of the GNU Lesser General Public
0006: * License as published by the Free Software Foundation; either
0007: * version 3 of the License, or (at your option) any later version.
0008: *
0009: * This library is distributed in the hope that it will be useful,
0010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0012: * Lesser General Public License for more details.
0013: *
0014: * You should have received a copy of the GNU Lesser General Public
0015: * License along with this library; if not, write to the Free Software
0016: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
0017: */package hu.netmind.persistence;
0018:
0019: import java.util.*;
0020: import hu.netmind.persistence.parser.*;
0021: import hu.netmind.persistence.event.*;
0022: import org.apache.log4j.Logger;
0023:
0024: /**
0025: * Custom list implementation based on lazy lists. List is <strong>not</strong>
0026: * thread-safe.
0027: * @author Brautigam Robert
0028: * @version Revision: $Revision$
0029: */
0030: public class ListImpl extends AbstractList implements Container,
0031: LazyListHooks {
0032: private static Logger logger = Logger.getLogger(ListImpl.class);
0033: private static final long INTERVAL = Integer.MAX_VALUE;
0034:
0035: private StoreContext context;
0036: private List originalList;
0037: private TimeControl originalTimeControl;
0038: private ClassInfo parentInfo;
0039: private Object parent;
0040: private String parentAttributeName;
0041: private Long lastSerial;
0042: private ContainerItemClass itemClass;
0043:
0044: private Vector changes; // Change entries of index layout
0045: private LinkedList addedItems;
0046: private LinkedList removedItems;
0047: private boolean cleared = false;
0048:
0049: /**
0050: * Initialize with a default list.
0051: */
0052: public void init(StoreContext context, ClassInfo classInfo,
0053: Object obj, String attributeName, String itemClassName,
0054: Long lastSerial, TimeControl timeControl) {
0055: this .context = context;
0056: this .originalList = null;
0057: this .originalTimeControl = new TimeControl(timeControl);
0058: this .parentInfo = classInfo;
0059: this .parent = obj;
0060: this .parentAttributeName = attributeName;
0061: this .lastSerial = lastSerial;
0062: this .itemClass = new ContainerItemClass(itemClassName);
0063: // Model
0064: reload();
0065: }
0066:
0067: public String getItemClassName() {
0068: return itemClass.getItemClassName();
0069: }
0070:
0071: public void reload() {
0072: // Clear model
0073: modCount++;
0074: changes = new Vector();
0075: addedItems = new LinkedList();
0076: removedItems = new LinkedList();
0077: cleared = false;
0078: // Reload list
0079: if (getItemClassName() == null) {
0080: originalList = new Vector();
0081: } else {
0082: originalList = context.getStore().find(
0083: "find item(" + getItemClassName() + ")"
0084: + " where parent("
0085: + parentInfo.getSourceEntry().getFullName()
0086: + ")" + "." + parentAttributeName
0087: + " contains item and parent = ?",
0088: new Object[] { parent }, originalTimeControl, null);
0089: // Modify the select statements.
0090: String subTableName = parentInfo
0091: .getSubTableName(parentAttributeName);
0092: LazyList lazy = (LazyList) originalList;
0093: lazy.setHooks(this );
0094: for (int i = 0; i < lazy.getStmts().size(); i++) {
0095: QueryStatement stmt = (QueryStatement) lazy.getStmts()
0096: .get(i);
0097: // First search for the table term of the subtable
0098: Expression expr = stmt.getQueryExpression();
0099: TableTerm subTableTerm = expr
0100: .getTableTerm(subTableName);
0101: // Add this term to selected terms
0102: stmt.getSelectTerms().add(
0103: new ReferenceTerm(subTableTerm,
0104: "container_index"));
0105: // Add the correct order by
0106: stmt.getOrderByList().add(
0107: 0,
0108: new OrderBy(new ReferenceTerm(subTableTerm,
0109: "container_index"), OrderBy.ASCENDING));
0110: // Correct static string
0111: stmt.setStaticRepresentation(stmt
0112: .getStaticRepresentation()
0113: + " and index");
0114: }
0115: }
0116: }
0117:
0118: public int preIndexing(Map session, int startIndex) {
0119: // Always use all statements
0120: return 0;
0121: }
0122:
0123: /**
0124: * This method generates a sub-page select statement which will select
0125: * the next items. If this is a linear iteration, then we already
0126: * know the previous index to select from, if not, we have to
0127: * select the list 'index' just before the page start.
0128: */
0129: public QueryStatement preSelect(Map session, QueryStatement stmt,
0130: List previousList, Limits limits, Limits pageLimits) {
0131: QueryStatement result = stmt;
0132: if (logger.isDebugEnabled())
0133: logger.debug("list impl pre select is running, limits is: "
0134: + limits + ", page limits: " + pageLimits);
0135: // Determining the index this query should start with.
0136: Long lastIndex = (Long) session.get("lastIndex");
0137: if ((pageLimits.getOffset() > 0) && (lastIndex == null)) {
0138: // If the offset is greater than null, then we need
0139: // to determine the last item's index from the previous
0140: // page.
0141: if (previousList.size() > 0) {
0142: // We're lucky, this is a linear iteration, so we can
0143: // determine the last index by checking the last item
0144: // in the previous page
0145: lastIndex = (Long) ((Map) previousList.get(previousList
0146: .size() - 1)).get("container_index");
0147: } else {
0148: if (logger.isDebugEnabled())
0149: logger
0150: .debug("preselect is running pre-selecting query.");
0151: // Well, no luck here. We must select the last index.
0152: String subTableName = parentInfo
0153: .getSubTableName(parentAttributeName);
0154: TableTerm subTableTerm = new TableTerm(subTableName,
0155: null);
0156: Vector selectTerms = new Vector();
0157: selectTerms.add(new ReferenceTerm(subTableTerm,
0158: "container_index"));
0159: Expression expr = new Expression();
0160: expr.add(new ReferenceTerm(subTableTerm,
0161: "persistence_id"));
0162: expr.add("=");
0163: expr.add(new ConstantTerm(new Long(context
0164: .getObjectTracker().getIdentifier(parent))));
0165: expr.add("and");
0166: originalTimeControl.apply(expr, subTableTerm);
0167: Vector orderBys = new Vector();
0168: orderBys.add(new OrderBy(new ReferenceTerm(
0169: subTableTerm, "container_index"),
0170: OrderBy.ASCENDING));
0171: QueryStatement indexStmt = new QueryStatement(
0172: selectTerms, expr, orderBys);
0173: Transaction tx = context.getTransactionTracker()
0174: .getTransaction(TransactionTracker.TX_REQUIRED);
0175: tx.begin();
0176: try {
0177: SearchResult qresult = context.getDatabase()
0178: .search(
0179: tx,
0180: indexStmt,
0181: new Limits((int) (pageLimits
0182: .getOffset() - 1), 1, 0));
0183: lastIndex = (Long) ((Map) qresult.getResult()
0184: .get(0)).get("container_index");
0185: } finally {
0186: tx.commit();
0187: }
0188: }
0189: if (logger.isDebugEnabled())
0190: logger
0191: .debug("pre select determined previous index to be: "
0192: + lastIndex);
0193: session.put("lastIndex", lastIndex);
0194: }
0195: // Insert explicit conditions on the 'index' field, if the last index
0196: // is known (because then we got to select only greater indexes).
0197: // If the index is not given, the database will start from the lowest
0198: // index automatically, because of the sql order by.
0199: if (lastIndex != null) {
0200: result = stmt.deepCopy();
0201: result.setStaticRepresentation(result
0202: .getStaticRepresentation()
0203: + " and index > " + lastIndex);
0204: // Get the index's reference term
0205: ReferenceTerm indexTerm = ((OrderBy) result
0206: .getOrderByList().get(0)).getReferenceTerm();
0207: // Insert new condition
0208: Expression expr = result.getQueryExpression();
0209: expr.add("and");
0210: expr.add(indexTerm);
0211: expr.add(">");
0212: expr.add(new ConstantTerm(lastIndex));
0213: }
0214: // Offset is always 0, because we alter the select to include
0215: // just the target items
0216: limits.setOffset(0);
0217: // Always get full result set, so limit is always on max. This
0218: // is because we can not know in advance, whether the following
0219: // sub-page contains items which by ordering belong to the
0220: // final list or not. So we have to select all sub-pages
0221: // with max count, and order and truncate them in the end.
0222: limits.setLimit(pageLimits.getLimit());
0223: // Return statement
0224: return result;
0225: }
0226:
0227: /**
0228: * The result list is now unordered, because the result was assembled
0229: * from possibly multiple query results. So we need to order by index.
0230: */
0231: public boolean postSelect(Map session, List list, Limits limits) {
0232: if (logger.isDebugEnabled())
0233: logger.debug("post select running, size is: " + list.size()
0234: + ", limits: " + limits);
0235: // Order the result by index ascending
0236: Collections.sort(list, new Comparator() {
0237: public int compare(Object o1, Object o2) {
0238: long diff = ((Long) ((Map) o1).get("container_index"))
0239: .longValue()
0240: - ((Long) ((Map) o2).get("container_index"))
0241: .longValue();
0242: if (diff < 0)
0243: return -1;
0244: else if (diff > 0)
0245: return +1;
0246: return 0;
0247: }
0248: });
0249: // At this point, the list can be larger than specified by limits,
0250: // because it is possibly assembled from multiple sub-pages which
0251: // were all selected with batchsize. We simply truncate the list.
0252: // The items at the end are sure to be _not_ part of the final
0253: // result anyway.
0254: if (limits.getLimit() < list.size())
0255: list.subList((int) limits.getLimit(), list.size()).clear();
0256: // Override always
0257: return true;
0258: }
0259:
0260: /**
0261: * Returns whether the container changes internally since last save().
0262: */
0263:
0264: public boolean hasChanged() {
0265: return (addedItems.size() > 0) || (removedItems.size() > 0)
0266: || (cleared);
0267: }
0268:
0269: /**
0270: * Save the container to database.
0271: */
0272: public void save(Transaction transaction, Long currentSerial,
0273: Set waitingObjects, List events) {
0274: // If the list was cleared before, then clear the current values
0275: if (cleared) {
0276: logger.debug("clearing list...");
0277: HashMap keyAttributes = new HashMap();
0278: keyAttributes.put("persistence_id", new Long(context
0279: .getObjectTracker().getIdentifier(parent)));
0280: keyAttributes.put("persistence_end", new Long(
0281: DateSerialUtil.getMaxSerial()));
0282: keyAttributes.put("persistence_txend", new Long(
0283: DateSerialUtil.getMaxSerial()));
0284: HashMap removeAttributes = new HashMap();
0285: removeAttributes.put("persistence_txend", currentSerial);
0286: removeAttributes.put("persistence_txendid", transaction
0287: .getSerial());
0288: context.getDatabase().save(transaction,
0289: parentInfo.getSubTableName(parentAttributeName),
0290: keyAttributes, removeAttributes);
0291: transaction.addRemoveTable(parentInfo
0292: .getSubTableName(parentAttributeName));
0293: // Notify listeners
0294: events.add(new ClearedContainerEvent(parent,
0295: parentAttributeName));
0296: }
0297: // Remove removed items first
0298: if (logger.isDebugEnabled())
0299: logger.debug("removing " + removedItems.size()
0300: + " items from list...");
0301: Iterator removedItemsIterator = removedItems.iterator();
0302: while (removedItemsIterator.hasNext()) {
0303: ObjectWrapper wrapper = (ObjectWrapper) removedItemsIterator
0304: .next();
0305: Object obj = wrapper.getObject();
0306: // Remove object from the list
0307: HashMap keyAttributes = new HashMap();
0308: keyAttributes.put("persistence_id", new Long(context
0309: .getObjectTracker().getIdentifier(parent)));
0310: keyAttributes.put("persistence_end", new Long(
0311: DateSerialUtil.getMaxSerial()));
0312: keyAttributes.put("persistence_txend", new Long(
0313: DateSerialUtil.getMaxSerial()));
0314: keyAttributes.put("container_index", new Long(wrapper
0315: .getOriginalIndex()));
0316: HashMap removeAttributes = new HashMap();
0317: removeAttributes.put("persistence_txend", currentSerial);
0318: removeAttributes.put("persistence_txendid", transaction
0319: .getSerial());
0320: context.getDatabase().save(transaction,
0321: parentInfo.getSubTableName(parentAttributeName),
0322: keyAttributes, removeAttributes);
0323: transaction.addRemoveTable(parentInfo
0324: .getSubTableName(parentAttributeName));
0325: // Notify listeners
0326: events.add(new RemovedItemEvent(parent,
0327: parentAttributeName, obj));
0328: }
0329: // Re-index list items. This only occurs, if an item addition
0330: // would get the index of an already existing item. In this case,
0331: // all subsequent items are re-indexed. So find the first reindexing
0332: // point
0333: ChangeEntry reindexEntry = null;
0334: int changeIndex = 0;
0335: while ((changeIndex < changes.size()) && (reindexEntry == null)) {
0336: ChangeEntry current = (ChangeEntry) changes
0337: .get(changeIndex);
0338: if ((current.getChange() == ChangeEntry.ADDED)
0339: && (current.getReindex()))
0340: reindexEntry = current;
0341: changeIndex++;
0342: }
0343: if (reindexEntry != null) {
0344: changeIndex--; // Back one step, if found
0345: // Found a reindex entry
0346: if (logger.isDebugEnabled())
0347: logger.debug("re-indexing range, change: "
0348: + reindexEntry);
0349: // All old items' indexes between this change and the next
0350: // are to be re-indexed. Get all the entries after the reindexing
0351: // item.
0352: String subTableName = parentInfo
0353: .getSubTableName(parentAttributeName);
0354: TableTerm subTableTerm = new TableTerm(subTableName, null);
0355: Vector selectTerms = new Vector();
0356: selectTerms.add(new ReferenceTerm(subTableTerm,
0357: "container_index"));
0358: selectTerms.add(new ReferenceTerm(subTableTerm, "value"));
0359: Expression expr = new Expression();
0360: expr.add(new ReferenceTerm(subTableTerm, "persistence_id"));
0361: expr.add("=");
0362: expr.add(new ConstantTerm(new Long(context
0363: .getObjectTracker().getIdentifier(parent))));
0364: expr.add("and");
0365: expr
0366: .add(new ReferenceTerm(subTableTerm,
0367: "container_index"));
0368: expr.add(">=");
0369: expr.add(new ConstantTerm(new Long(reindexEntry.getItem()
0370: .getOriginalIndex())));
0371: expr.add("and");
0372: originalTimeControl.apply(expr, subTableTerm);
0373: Vector orderBys = new Vector();
0374: orderBys.add(new OrderBy(new ReferenceTerm(subTableTerm,
0375: "container_index"), OrderBy.ASCENDING));
0376: QueryStatement stmt = new QueryStatement(selectTerms, expr,
0377: orderBys);
0378: SearchResult result = context.getDatabase().search(
0379: transaction, stmt, null);
0380: // Go through each item entry and modify it (first close the old
0381: // one, and create a new entry with new index)
0382: // Note, that because this is low-level, the paging mechanism
0383: // is questionable. We depend here on the fetchsize parameter
0384: // of the jdbc driver, so it is assumed, that large result sets
0385: // can be read.
0386: // The cycle reads the resultset, and the changeset in parallel,
0387: // each is pre-read.
0388: // Implementation note: On first iteration, the reindexEntry is
0389: // always selected, because it has the lowest index.
0390: long newIndex = reindexEntry.getItem().getOriginalIndex()
0391: + INTERVAL - 1;
0392: ChangeEntry currentEntry = reindexEntry;
0393: int resultIndex = 0;
0394: Map currentResult = null;
0395: if (resultIndex < result.getResult().size())
0396: currentResult = (Map) result.getResult().get(
0397: resultIndex);
0398: while ((currentResult != null) || (currentEntry != null)) {
0399: // Determine whether the next item is a change, or a database
0400: // result.
0401: if ((currentResult != null)
0402: && ((currentEntry == null) || (((Long) currentResult
0403: .get("container_index")).longValue() < currentEntry
0404: .getItem().getOriginalIndex()))) {
0405: // The database result entry has a smaller index than
0406: // the change (if there is a change), this means, the
0407: // next index belongs to this database entry.
0408: Long index = (Long) currentResult
0409: .get("container_index");
0410: Long value = (Long) currentResult.get("value");
0411: // Close last occurence of index
0412: HashMap keyAttributes = new HashMap();
0413: keyAttributes.put("persistence_id", new Long(
0414: context.getObjectTracker().getIdentifier(
0415: parent)));
0416: keyAttributes.put("persistence_end", new Long(
0417: DateSerialUtil.getMaxSerial()));
0418: keyAttributes.put("persistence_txend", new Long(
0419: DateSerialUtil.getMaxSerial()));
0420: keyAttributes.put("container_index", index);
0421: HashMap removeAttributes = new HashMap();
0422: removeAttributes.put("persistence_txend",
0423: currentSerial);
0424: removeAttributes.put("persistence_txendid",
0425: transaction.getSerial());
0426: context
0427: .getDatabase()
0428: .save(
0429: transaction,
0430: parentInfo
0431: .getSubTableName(parentAttributeName),
0432: keyAttributes, removeAttributes);
0433: transaction.addRemoveTable(parentInfo
0434: .getSubTableName(parentAttributeName));
0435: // Insert new version
0436: HashMap itemAttributes = new HashMap();
0437: itemAttributes.put("persistence_id", new Long(
0438: context.getObjectTracker().getIdentifier(
0439: parent)));
0440: itemAttributes.put("persistence_start", new Long(
0441: DateSerialUtil.getMaxSerial()));
0442: itemAttributes.put("persistence_end", new Long(
0443: DateSerialUtil.getMaxSerial()));
0444: itemAttributes.put("persistence_txendid", new Long(
0445: 0));
0446: itemAttributes.put("persistence_txstartid",
0447: transaction.getSerial());
0448: itemAttributes.put("persistence_txstart",
0449: currentSerial);
0450: itemAttributes.put("persistence_txend", new Long(
0451: DateSerialUtil.getMaxSerial()));
0452: itemAttributes.put("value", value);
0453: itemAttributes.put("container_index", new Long(
0454: newIndex));
0455: context
0456: .getDatabase()
0457: .insert(
0458: transaction,
0459: parentInfo
0460: .getSubTableName(parentAttributeName),
0461: itemAttributes);
0462: transaction.addSaveTable(parentInfo
0463: .getSubTableName(parentAttributeName));
0464: // Pre-read the next result
0465: resultIndex++;
0466: if (resultIndex < result.getResult().size())
0467: currentResult = (Map) result.getResult().get(
0468: resultIndex);
0469: else
0470: currentResult = null;
0471: } else {
0472: // This branch is active if an ADDED (yet non existing)
0473: // item is next. In this case we must re-index the entry
0474: // which will be already created with this new index later.
0475: currentEntry.getItem().setOriginalIndex(newIndex);
0476: // Pre-read next entry
0477: changeIndex++;
0478: while ((changeIndex < changes.size())
0479: && (((ChangeEntry) changes.get(changeIndex))
0480: .getChange() != ChangeEntry.ADDED))
0481: changeIndex++;
0482: if (changeIndex < changes.size())
0483: currentEntry = (ChangeEntry) changes
0484: .get(changeIndex);
0485: else
0486: currentEntry = null;
0487: }
0488: // Increment index
0489: newIndex += INTERVAL;
0490: }
0491: }
0492: // Add added items
0493: if (logger.isDebugEnabled())
0494: logger.debug("adding " + addedItems.size()
0495: + " items to list...");
0496: Iterator addedItemsIterator = addedItems.iterator();
0497: while (addedItemsIterator.hasNext()) {
0498: ObjectWrapper wrapper = (ObjectWrapper) addedItemsIterator
0499: .next();
0500: Object obj = wrapper.getObject();
0501: // Item does not exist then put it in the save list
0502: if (!context.getObjectTracker().exists(obj))
0503: waitingObjects.add(context.getObjectTracker()
0504: .getWrapper(obj));
0505: // Add item
0506: HashMap itemAttributes = new HashMap();
0507: itemAttributes.put("persistence_id", new Long(context
0508: .getObjectTracker().getIdentifier(parent)));
0509: itemAttributes.put("persistence_start", new Long(
0510: DateSerialUtil.getMaxSerial()));
0511: itemAttributes.put("persistence_end", new Long(
0512: DateSerialUtil.getMaxSerial()));
0513: itemAttributes.put("persistence_txendid", new Long(0));
0514: itemAttributes.put("persistence_txstartid", transaction
0515: .getSerial());
0516: itemAttributes.put("persistence_txstart", currentSerial);
0517: itemAttributes.put("persistence_txend", new Long(
0518: DateSerialUtil.getMaxSerial()));
0519: itemAttributes.put("container_index", new Long(wrapper
0520: .getOriginalIndex()));
0521: itemAttributes.put("value", new Long(context
0522: .getObjectTracker().getIdentifier(obj)));
0523: context.getDatabase().insert(transaction,
0524: parentInfo.getSubTableName(parentAttributeName),
0525: itemAttributes);
0526: transaction.addSaveTable(parentInfo
0527: .getSubTableName(parentAttributeName));
0528: // Notify listeners
0529: events.add(new AddedItemEvent(parent, parentAttributeName,
0530: obj));
0531: }
0532: // Reload the changed list. Note, that the list should not
0533: // be referenced until the Store.save() operation completes, because
0534: // the list currently might contain nonexisting objects, which will
0535: // be inserted _after_ this code.
0536: originalTimeControl = new TimeControl(currentSerial,
0537: transaction.getSerial(), true);
0538: lastSerial = currentSerial;
0539: }
0540:
0541: /**
0542: * Get the serial number of last modification.
0543: */
0544: public Long getLastSerial() {
0545: return lastSerial;
0546: }
0547:
0548: public boolean retainAll(Object c) {
0549: return retainAll((Collection) c);
0550: }
0551:
0552: public boolean addAll(Object c) {
0553: return addAll((Collection) c);
0554: }
0555:
0556: /*
0557: * List implementation.
0558: */
0559:
0560: /**
0561: * Add modification to this list.
0562: */
0563: private void addChange(int index, int change, ObjectWrapper item,
0564: boolean reindex) {
0565: // Search for the correct entry. The correct entry is the
0566: // one which is concerned with at least one index higher item.
0567: int topIndex = 0;
0568: int prevTopIndex = 0;
0569: int changesIndex = 0;
0570: ChangeEntry nextEntry = null;
0571: while ((topIndex <= index) && (changesIndex < changes.size())) {
0572: nextEntry = (ChangeEntry) changes.get(changesIndex);
0573: changesIndex++;
0574: // Determine the index of interest of this entry
0575: prevTopIndex = topIndex;
0576: topIndex += nextEntry.getOffset();
0577: if (nextEntry.getChange() == ChangeEntry.ADDED)
0578: topIndex++;
0579: }
0580: // Add modification to changes list
0581: ChangeEntry previousEntry = null;
0582: if (changesIndex > 1)
0583: previousEntry = (ChangeEntry) changes.get(changesIndex - 2);
0584: ChangeEntry newEntry = null;
0585: if (topIndex <= index) {
0586: // This means, that there is no next entry, this is the last.
0587: newEntry = new ChangeEntry(index - topIndex, change, item,
0588: reindex);
0589: nextEntry = null;
0590: } else {
0591: // There are changes after this index, so adjust offsets.
0592: newEntry = new ChangeEntry(index - prevTopIndex, change,
0593: item, reindex);
0594: nextEntry.setOffset(nextEntry.getOffset()
0595: - (index - prevTopIndex));
0596: changesIndex--;
0597: }
0598: if ((newEntry.getOffset() == 0)
0599: && (newEntry.getChange() == ChangeEntry.REMOVED)
0600: && (previousEntry != null)
0601: && (previousEntry.getChange() == ChangeEntry.ADDED)) {
0602: // The new entry is a REMOVED entry, and the previous one is an
0603: // ADDED on the same index. This means, the added item was removed
0604: // even before save, so we remove both.
0605: changes.remove(changesIndex - 2);
0606: if (nextEntry != null) {
0607: // We must re-calculate the next entry's offset, with
0608: // it's previous 2 entries removed
0609: nextEntry.setOffset(nextEntry.getOffset()
0610: + newEntry.getOffset()
0611: + previousEntry.getOffset());
0612: }
0613: } else {
0614: // The new entry must be added
0615: changes.add(changesIndex, newEntry);
0616: // With the new entry, the indexes after this change changed, so
0617: // adjust
0618: int diff = 0;
0619: if (newEntry.getChange() == ChangeEntry.ADDED)
0620: diff = +1;
0621: else
0622: diff = -1;
0623: for (int i = changesIndex + 1; i < changes.size(); i++) {
0624: ObjectWrapper ow = ((ChangeEntry) changes.get(i))
0625: .getItem();
0626: ow.setIndex(item.getIndex() + 1);
0627: }
0628: }
0629: }
0630:
0631: public void add(int index, Object item) {
0632: if (logger.isDebugEnabled())
0633: logger.debug("list impl adding item at index: " + index
0634: + ", item: " + item);
0635: // Check item validity
0636: if (item == null)
0637: throw new IllegalArgumentException(
0638: "list does not accept null values.");
0639: int type = context.getClassTracker().getType(item.getClass());
0640: if ((type != ClassTracker.TYPE_OBJECT)
0641: && (type != ClassTracker.TYPE_PRIMITIVE))
0642: throw new IllegalArgumentException(
0643: "list only handles object or primitive types, but was: "
0644: + item + " (" + item.getClass().getName()
0645: + ")");
0646: // Determine whether it is in the removed list
0647: ObjectWrapper wrapper = new ObjectWrapper(index, item);
0648: boolean reindex = false;
0649: if (removedItems.contains(wrapper)) {
0650: // It was removed once, so remove remove entry
0651: removedItems.remove(wrapper);
0652: } else {
0653: // Also, calculate insert index, aka. 'original index'.
0654: // Algorithm: get the previous item, and the one we're
0655: // inserting at, if both exist, calculate the middle.
0656: // If either side do not exist then grow with INTERVAL.
0657: ObjectWrapper prev = null;
0658: if (index > 0)
0659: prev = getInternal(index - 1);
0660: ObjectWrapper under = null;
0661: if (index < size()) // Here we must calculate size, shame
0662: under = getInternal(index);
0663: if ((prev != null) && (under != null)) {
0664: // This means, there is a previous index and a next one,
0665: // try to get between them if possible.
0666: if (under.getOriginalIndex() - prev.getOriginalIndex() < 2) {
0667: // No place between them, so we must give the new entry
0668: // the index of 'under', and mark the change for reindexing.
0669: wrapper.setOriginalIndex(under.getOriginalIndex());
0670: reindex = true;
0671: } else {
0672: // We have luck, we don't have to reindex the whole
0673: // list.
0674: wrapper
0675: .setOriginalIndex((under.getOriginalIndex() + prev
0676: .getOriginalIndex()) / 2);
0677: }
0678: } else if ((prev == null) && (under != null)) {
0679: // This means we insert at the beginning (no previous item)
0680: if (Long.MIN_VALUE + INTERVAL > under
0681: .getOriginalIndex())
0682: throw new StoreException(
0683: "sorry, list ran out of useful indexes on the beginning, the first index is: "
0684: + under.getOriginalIndex());
0685: wrapper.setOriginalIndex(under.getOriginalIndex()
0686: - INTERVAL);
0687: } else if ((prev != null) && (under == null)) {
0688: // The insert occured at the end
0689: if (Long.MAX_VALUE - INTERVAL < prev.getOriginalIndex())
0690: throw new StoreException(
0691: "sorry, list ran out of useful indexes on the end, the last index is: "
0692: + prev.getOriginalIndex());
0693: wrapper.setOriginalIndex(prev.getOriginalIndex()
0694: + INTERVAL);
0695: } else {
0696: // There are no items in the list yet, start at 0
0697: wrapper.setOriginalIndex(0);
0698: }
0699: // Add the item
0700: addedItems.add(wrapper);
0701: }
0702: // Ensure item exists
0703: if (logger.isDebugEnabled())
0704: logger.debug("adding list item to list: " + index
0705: + ", object: " + item);
0706: itemClass.updateItemClassName(originalList, item.getClass(),
0707: addedItems.size() == 0);
0708: addChange(index, ChangeEntry.ADDED, wrapper, reindex);
0709: modCount++;
0710: }
0711:
0712: /**
0713: * Clear all entries from the list.
0714: */
0715: public void clear() {
0716: // Clear content and mark cleared flag
0717: cleared = true;
0718: addedItems = new LinkedList();
0719: removedItems = new LinkedList();
0720: originalList = new Vector();
0721: changes = new Vector();
0722: itemClass.clear();
0723: modCount++;
0724: }
0725:
0726: private Long searchInternal(Object item, Vector order) {
0727: if (item == null)
0728: return null;
0729: int type = context.getClassTracker().getType(item.getClass());
0730: long id = context.getObjectTracker().getIdentifier(item);
0731: Transaction tx = context.getTransactionTracker()
0732: .getTransaction(TransactionTracker.TX_REQUIRED);
0733: tx.begin();
0734: try {
0735: ClassInfo itemInfo = context.getClassTracker()
0736: .getClassInfo(item.getClass(), item);
0737: String itemTableName = itemInfo.getTableName(itemInfo
0738: .getSourceEntry());
0739: TableTerm itemTableTerm = new TableTerm(itemTableName, null);
0740: String listTableName = parentInfo
0741: .getSubTableName(parentAttributeName);
0742: TableTerm listTableTerm = new TableTerm(listTableName, null);
0743: Expression listExpression = new Expression();
0744: listExpression.add(new ReferenceTerm(listTableTerm,
0745: "persistence_id"));
0746: listExpression.add("=");
0747: listExpression.add(new ConstantTerm(new Long(context
0748: .getObjectTracker().getIdentifier(parent))));
0749: listExpression.add("and");
0750: if (type != ClassTracker.TYPE_PRIMITIVE) {
0751: listExpression.add(new ReferenceTerm(listTableTerm,
0752: "value"));
0753: listExpression.add("=");
0754: listExpression.add(new ConstantTerm(new Long(id)));
0755: } else {
0756: listExpression.add(new ReferenceTerm(itemTableTerm,
0757: "value"));
0758: listExpression.add("=");
0759: listExpression.add(new ConstantTerm(item));
0760: }
0761: listExpression.add("and");
0762: listExpression.add(new ReferenceTerm(itemTableTerm,
0763: "persistence_id"));
0764: listExpression.add("=");
0765: listExpression
0766: .add(new ReferenceTerm(listTableTerm, "value"));
0767: listExpression.add("and");
0768: originalTimeControl.apply(listExpression, itemTableTerm);
0769: listExpression.add("and");
0770: originalTimeControl.apply(listExpression, listTableTerm);
0771: // Execute. If there is a hit, then object exists
0772: QueryStatement stmt = new QueryStatement(listTableTerm,
0773: listExpression, order);
0774: stmt.setTimeControl(originalTimeControl);
0775: stmt.setStaticRepresentation("FIND " + listTableTerm
0776: + " WHERE persistence_id = "
0777: + context.getObjectTracker().getIdentifier(parent)
0778: + " and value = " + id + ", order=" + order);
0779: SearchResult result = context.getStore().find(tx, stmt,
0780: new Limits(0, 1, 0));
0781: // Check
0782: if (result.getResult().size() > 0) {
0783: Long index = (Long) ((Map) result.getResult().get(0))
0784: .get("container_index");
0785: if (logger.isDebugEnabled())
0786: logger.debug("search internal result index is: "
0787: + index + ", for item: " + item);
0788: return index;
0789: }
0790: } finally {
0791: tx.commit();
0792: }
0793: if (logger.isDebugEnabled())
0794: logger
0795: .debug("search internal did not found the item given: "
0796: + item);
0797: return null;
0798: }
0799:
0800: public boolean contains(Object item) {
0801: if (item == null)
0802: return false;
0803: int type = context.getClassTracker().getType(item.getClass());
0804: // Check the list
0805: long id = context.getObjectTracker().getIdentifier(item);
0806: ObjectWrapper wrapper = new ObjectWrapper(item);
0807: // If it is added, then it is contained.
0808: if (addedItems.contains(wrapper))
0809: return true;
0810: // Check the list
0811: if (originalList.size() > LazyList.BATCH_SIZE) {
0812: // This means, that the backing lazy list would page if we
0813: // were to iterate. So instead, we run a specific query for
0814: // the given id.
0815: Long index = searchInternal(item, null);
0816: return index != null;
0817: } else {
0818: // Set is small, so iterate
0819: for (int i = 0; i < originalList.size(); i++) {
0820: Object obj = ((Map) originalList.get(i)).get("object");
0821: if (((type == ClassTracker.TYPE_PRIMITIVE) && (item
0822: .equals(obj)))
0823: || ((type != ClassTracker.TYPE_PRIMITIVE) && (context
0824: .getObjectTracker().getIdentifier(obj) == id)))
0825: return true;
0826: }
0827: }
0828: // Fall through
0829: return false;
0830: }
0831:
0832: /**
0833: * Determine whether list equals another list in content.
0834: */
0835: public boolean equals(Object obj) {
0836: // If it is not a collection, then it surely does not equal.
0837: if (!(obj instanceof List))
0838: return false;
0839: if (obj == this )
0840: return true; // They equals if they are the same object
0841: List list = (List) obj;
0842: if (list.size() != size())
0843: return false; // They don't equals if size differs
0844: // They only equals if the items equals one-by-one
0845: Iterator oneIterator = iterator();
0846: Iterator twoIterator = list.iterator();
0847: while (oneIterator.hasNext() && twoIterator.hasNext()) {
0848: Object one = oneIterator.next();
0849: Object two = twoIterator.next();
0850: if (context.getObjectTracker().getIdentifier(one) != context
0851: .getObjectTracker().getIdentifier(two))
0852: return false; // The two items do not equal
0853: }
0854: return true; // All items were equal
0855: }
0856:
0857: public Object get(int index) {
0858: ObjectWrapper wrapper = getInternal(index);
0859: if (wrapper == null)
0860: return null;
0861: return wrapper.getObject();
0862: }
0863:
0864: private ObjectWrapper getInternal(int index) {
0865: try {
0866: // Search for the correct entry.
0867: int topIndex = 0;
0868: int lastTopIndex = 0;
0869: int lastBottomIndex = 0;
0870: int bottomIndex = 0;
0871: int changesIndex = 0;
0872: ChangeEntry exactEntry = null;
0873: while ((topIndex <= index)
0874: && (changesIndex < changes.size())) {
0875: ChangeEntry nextEntry = (ChangeEntry) changes
0876: .get(changesIndex);
0877: changesIndex++;
0878: // Determine the index of interest of this entry
0879: lastTopIndex = topIndex;
0880: lastBottomIndex = bottomIndex;
0881: topIndex += nextEntry.getOffset();
0882: bottomIndex += nextEntry.getOffset();
0883: // If this entry is the exact index, then remember
0884: if (topIndex == index)
0885: exactEntry = nextEntry;
0886: // Modify top and bottom indexes
0887: if (nextEntry.getChange() == ChangeEntry.ADDED)
0888: topIndex++;
0889: else
0890: bottomIndex++;
0891: }
0892: if (topIndex <= index)
0893: bottomIndex = bottomIndex + (index - topIndex);
0894: else
0895: bottomIndex = lastBottomIndex + (index - lastTopIndex);
0896: // If the last exact match was an ADD, then that is the
0897: // result, else the backing list is used.
0898: if ((exactEntry != null)
0899: && (exactEntry.getChange() == ChangeEntry.ADDED)) {
0900: return exactEntry.getItem();
0901: } else {
0902: Object obj = ((Map) originalList.get(bottomIndex))
0903: .get("object");
0904: ObjectWrapper result = new ObjectWrapper(index, obj);
0905: result.setOriginalIndex(((Long) ((Map) originalList
0906: .get(bottomIndex)).get("container_index"))
0907: .longValue());
0908: return result;
0909: }
0910: } catch (IndexOutOfBoundsException e) {
0911: logger.error("array index out-of-bound", e);
0912: throw e;
0913: }
0914: }
0915:
0916: public int indexOf(Object item) {
0917: if (item == null)
0918: return -1;
0919: int type = context.getClassTracker().getType(item.getClass());
0920: long itemId = context.getObjectTracker().getIdentifier(item);
0921: if ((itemId == 0) && (type != ClassTracker.TYPE_PRIMITIVE))
0922: return -1; // Object does not have identifier, it is not contained.
0923: if (originalList.size() > LazyList.BATCH_SIZE) {
0924: // The list is large, do a select to determine index
0925: Transaction tx = context.getTransactionTracker()
0926: .getTransaction(TransactionTracker.TX_REQUIRED);
0927: tx.begin();
0928: try {
0929: String listTableName = parentInfo
0930: .getSubTableName(parentAttributeName);
0931: TableTerm listTableTerm = new TableTerm(listTableName,
0932: null);
0933: Vector order = new Vector();
0934: order.add(new OrderBy(new ReferenceTerm(listTableTerm,
0935: "container_index"), OrderBy.ASCENDING));
0936: Long oldIndex = searchInternal(item, order);
0937: // Check
0938: if (oldIndex != null) {
0939: // Determine how many indexes are before this index
0940: Expression listExpression = new Expression();
0941: listExpression.add(new ReferenceTerm(listTableTerm,
0942: "persistence_id"));
0943: listExpression.add("=");
0944: listExpression.add(new ConstantTerm(new Long(
0945: context.getObjectTracker().getIdentifier(
0946: parent))));
0947: listExpression.add("and");
0948: listExpression.add(new ReferenceTerm(listTableTerm,
0949: "container_index"));
0950: listExpression.add("<");
0951: listExpression.add(new ConstantTerm(oldIndex));
0952: listExpression.add("and");
0953: originalTimeControl.apply(listExpression,
0954: listTableTerm);
0955: QueryStatement stmt = new QueryStatement(
0956: listTableTerm, listExpression, null);
0957: stmt.setTimeControl(originalTimeControl);
0958: stmt.setStaticRepresentation("FIND "
0959: + listTableTerm
0960: + " WHERE persistence_id = "
0961: + context.getObjectTracker().getIdentifier(
0962: parent) + " and container_index < "
0963: + oldIndex);
0964: SearchResult result = context.getStore().find(tx,
0965: stmt, new Limits(0, 0, -1));
0966: return (int) result.getResultSize();
0967: }
0968: } finally {
0969: tx.commit();
0970: }
0971: } else {
0972: // Iterate and find the object
0973: for (int i = 0; i < size(); i++) {
0974: Object obj = get(i);
0975: if (((type == ClassTracker.TYPE_PRIMITIVE) && (item
0976: .equals(obj)))
0977: || ((type != ClassTracker.TYPE_PRIMITIVE) && (context
0978: .getObjectTracker().getIdentifier(obj) == itemId)))
0979: return i;
0980: }
0981: }
0982: return -1;
0983: }
0984:
0985: public int lastIndexOf(Object item) {
0986: if (item == null)
0987: return -1;
0988: int type = context.getClassTracker().getType(item.getClass());
0989: long itemId = context.getObjectTracker().getIdentifier(item);
0990: if ((itemId == 0) && (type != ClassTracker.TYPE_PRIMITIVE))
0991: return -1; // Object does not have identifier, it is not contained.
0992: if (originalList.size() > LazyList.BATCH_SIZE) {
0993: // The list is large, do a select to determine index
0994: Transaction tx = context.getTransactionTracker()
0995: .getTransaction(TransactionTracker.TX_REQUIRED);
0996: tx.begin();
0997: try {
0998: String listTableName = parentInfo
0999: .getSubTableName(parentAttributeName);
1000: TableTerm listTableTerm = new TableTerm(listTableName,
1001: null);
1002: Vector order = new Vector();
1003: order.add(new OrderBy(new ReferenceTerm(listTableTerm,
1004: "container_index"), OrderBy.DESCENDING));
1005: Long oldIndex = searchInternal(item, order);
1006: // Check
1007: if (oldIndex != null) {
1008: // Determine how many indexes are before this index
1009: Expression listExpression = new Expression();
1010: listExpression.add(new ReferenceTerm(listTableTerm,
1011: "persistence_id"));
1012: listExpression.add("=");
1013: listExpression.add(new ConstantTerm(new Long(
1014: context.getObjectTracker().getIdentifier(
1015: parent))));
1016: listExpression.add("and");
1017: listExpression.add(new ReferenceTerm(listTableTerm,
1018: "container_index"));
1019: listExpression.add("<");
1020: listExpression.add(new ConstantTerm(oldIndex));
1021: listExpression.add("and");
1022: originalTimeControl.apply(listExpression,
1023: listTableTerm);
1024: QueryStatement stmt = new QueryStatement(
1025: listTableTerm, listExpression, null);
1026: stmt.setTimeControl(originalTimeControl);
1027: stmt.setStaticRepresentation("FIND "
1028: + listTableTerm
1029: + " WHERE persistence_id = "
1030: + context.getObjectTracker().getIdentifier(
1031: parent) + " and container_index < "
1032: + oldIndex);
1033: SearchResult result = context.getStore().find(tx,
1034: stmt, new Limits(0, 0, -1));
1035: return (int) result.getResultSize();
1036: }
1037: } finally {
1038: tx.commit();
1039: }
1040: } else {
1041: // Iterate and find the object
1042: for (int i = size(); i > 0; i++) {
1043: Object obj = get(i - 1);
1044: if (((type == ClassTracker.TYPE_PRIMITIVE) && (item
1045: .equals(obj)))
1046: || ((type != ClassTracker.TYPE_PRIMITIVE) && (context
1047: .getObjectTracker().getIdentifier(obj) == itemId)))
1048: return i - 1;
1049: }
1050: }
1051: return -1;
1052: }
1053:
1054: public Object remove(int index) {
1055: // Get the object at the given index
1056: ObjectWrapper wrapper = getInternal(index);
1057: // Object is present, determine, whether it is in the added list
1058: if (addedItems.contains(wrapper)) {
1059: // It was added, so remove added entry
1060: addedItems.remove(wrapper);
1061: } else {
1062: // It was not yet added, so add
1063: removedItems.add(wrapper);
1064: }
1065: // Insert modification entry
1066: if (logger.isDebugEnabled())
1067: logger.debug("removed index: " + index + ", object: "
1068: + wrapper.getObject());
1069: addChange(index, ChangeEntry.REMOVED, wrapper, false);
1070: modCount++;
1071: return wrapper.getObject();
1072: }
1073:
1074: public boolean remove(Object item) {
1075: int index = indexOf(item);
1076: if (index == -1)
1077: return false;
1078: remove(index);
1079: return true;
1080: }
1081:
1082: public Object set(int index, Object item) {
1083: // Simply remove first, then add to the same position
1084: Object oldItem = remove(index);
1085: add(index, item);
1086: return oldItem;
1087: }
1088:
1089: public int size() {
1090: return originalList.size() - removedItems.size()
1091: + addedItems.size();
1092: }
1093:
1094: public String toString() {
1095: return originalList.toString();
1096: }
1097:
1098: private class ChangeEntry {
1099: public static final int ADDED = 1;
1100: public static final int REMOVED = 2;
1101:
1102: private int offset;
1103: private int change;
1104: private boolean reindex;
1105: private ObjectWrapper item;
1106:
1107: public ChangeEntry(int offset, int change, ObjectWrapper item,
1108: boolean reindex) {
1109: this .offset = offset;
1110: this .change = change;
1111: this .item = item;
1112: this .reindex = reindex;
1113: }
1114:
1115: public boolean getReindex() {
1116: return reindex;
1117: }
1118:
1119: public String toString() {
1120: return "[Change: " + change + ", offset: " + offset
1121: + ", item: " + item + "]";
1122: }
1123:
1124: public int getOffset() {
1125: return offset;
1126: }
1127:
1128: public void setOffset(int offset) {
1129: this .offset = offset;
1130: }
1131:
1132: public int getChange() {
1133: return change;
1134: }
1135:
1136: public void setChange(int change) {
1137: this .change = change;
1138: }
1139:
1140: public ObjectWrapper getItem() {
1141: return item;
1142: }
1143:
1144: public void setItem(ObjectWrapper item) {
1145: this .item = item;
1146: }
1147: }
1148:
1149: public class ObjectWrapper {
1150: private Object obj;
1151: private long id;
1152: private long originalIndex;
1153: private int index;
1154: private boolean indexGiven;
1155: private int type;
1156:
1157: public ObjectWrapper(int index, Object obj) {
1158: this .indexGiven = true;
1159: this .index = index;
1160: this .obj = obj;
1161: context.getObjectTracker().registerObject(obj, 0);
1162: this .id = context.getObjectTracker().getIdentifier(obj);
1163: this .type = context.getClassTracker().getType(
1164: obj.getClass());
1165: }
1166:
1167: public ObjectWrapper(Object obj) {
1168: this .indexGiven = false;
1169: this .obj = obj;
1170: context.getObjectTracker().registerObject(obj, 0);
1171: this .id = context.getObjectTracker().getIdentifier(obj);
1172: this .type = context.getClassTracker().getType(
1173: obj.getClass());
1174: }
1175:
1176: public int getIndex() {
1177: return index;
1178: }
1179:
1180: public void setIndex(int index) {
1181: this .index = index;
1182: }
1183:
1184: public long getIdentifier() {
1185: return id;
1186: }
1187:
1188: public Object getObject() {
1189: return obj;
1190: }
1191:
1192: public int hashCode() {
1193: if (type == ClassTracker.TYPE_PRIMITIVE)
1194: return obj.hashCode();
1195: else
1196: return (int) (id >> 32);
1197: }
1198:
1199: public boolean equals(Object rhs) {
1200: if (!(rhs instanceof ObjectWrapper))
1201: return false;
1202: if (type == ClassTracker.TYPE_PRIMITIVE) {
1203: if (!obj.equals(((ObjectWrapper) rhs).obj))
1204: return false;
1205: } else {
1206: if (id != ((ObjectWrapper) rhs).id)
1207: return false;
1208: }
1209: if (indexGiven)
1210: return index == ((ObjectWrapper) rhs).index;
1211: return true;
1212: }
1213:
1214: public long getOriginalIndex() {
1215: return originalIndex;
1216: }
1217:
1218: public void setOriginalIndex(long originalIndex) {
1219: this.originalIndex = originalIndex;
1220: }
1221:
1222: }
1223: }
|