/* * Copyright 2009-2016 Tilmann Zaeschke. All rights reserved. * * This file is part of ZooDB. * * ZooDB is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * ZooDB is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with ZooDB. If not, see <http://www.gnu.org/licenses/>. * * See the README and COPYING files for further information. */ package org.zoodb.internal.server.index; import java.util.ArrayList; import java.util.NoSuchElementException; import org.zoodb.internal.server.index.LongLongIndex.LLEntryIterator; /** * Some thoughts on Iterators: * * JDO has a usecase like this: * Iterator iter = extent.iterator(); * while (iter.hasNext()) { * pm.deletePersistent(iter.next()); * } * * That means: * The iterator needs to support deletion without introducing duplicates and without skipping * objects. It needs to be a perfect iterator. * * According to the spec 2.2., the extent should contain whatever existed a the time of the * execution of the query or creation of the iterator (JDO 2.2). * * So: * - Different sessions should use COW to create locally valid 'copies' of the traversed index. * - Within the same session, iterators should support deletion as described above. * * To support the deletion locally, there are several option: * - one could use COW as well, which would mean that bidirectional iterators would not work, * because the iterator iterates over copies of the original list. * Basically the above example would work, but deletions ahead of the iterator would not * be recognized (desirable?). TODO Check spec. * - Alternative: Update iterator with callbacks from index modification. * This would mean ahead-of-iterator modifications would be recognized (desirable?) * * * * * Version 2.0: * Iterator stores currentElement and immediately moves to next element. For unique indices * this has the advantage, that the will never be buffer pages created, because the index * is invalidated, as soon as it is created. * * @author Tilmann Zaeschke * */ class LLIterator extends AbstractPageIterator<LongLongIndex.LLEntry> implements LLEntryIterator { static class IteratorPos { IteratorPos(LLIndexPage page, short pos) { this.page = page; this.pos = pos; } //This is for the iterator, do _not_ use WeakRefs here. LLIndexPage page; short pos; } private LLIndexPage currentPage = null; private short currentPos = 0; private final long minKey; private final long maxKey; private final ArrayList<IteratorPos> stack = new ArrayList<IteratorPos>(20); private long nextKey; private long nextValue; private boolean hasValue = false; public LLIterator(AbstractPagedIndex ind, long minKey, long maxKey) { super(ind); this.minKey = minKey; this.maxKey = maxKey; this.currentPage = (LLIndexPage) ind.getRoot(); findFirstPosInPage(); } @Override public boolean hasNext() { return hasNextULL(); } /** * Dirty trick to avoid delays from finding the correct method. */ public boolean hasNextULL() { checkValidity(); return hasValue; } private void goToNextPage() { releasePage(currentPage); IteratorPos ip = stack.remove(stack.size()-1); currentPage = ip.page; currentPos = ip.pos; currentPos++; while (currentPos > currentPage.getNKeys()) { releasePage(currentPage); if (stack.isEmpty()) { close(); return;// false; } ip = stack.remove(stack.size()-1); currentPage = ip.page; currentPos = ip.pos; currentPos++; } while (!currentPage.isLeaf) { //we are not on the first page here, so we can assume that pos=0 is correct to //start with //read last page stack.add(new IteratorPos(currentPage, currentPos)); currentPage = (LLIndexPage) findPage(currentPage, currentPos); currentPos = 0; } } private boolean goToFirstPage() { while (!currentPage.isLeaf) { //the following is only for the initial search. //The stored key[i] is the min-key of the according page[i+1} int pos2 = currentPage.binarySearch( currentPos, currentPage.getNKeys(), minKey, Long.MIN_VALUE); if (currentPage.getNKeys() == -1) { return false; } if (pos2 >=0) { pos2++; } else { pos2 = -(pos2+1); } currentPos = (short)pos2; LLIndexPage newPage = (LLIndexPage) findPage(currentPage, currentPos); //are we on the correct branch? //We are searching with LONG_MIN value. If the key[] matches exactly, then the //selected page may not actually contain any valid elements. //In any case this will be sorted out in findFirstPosInPage() stack.add(new IteratorPos(currentPage, currentPos)); currentPage = newPage; currentPos = 0; } return true; } private void gotoPosInPage() { //when we get here, we are on a valid page with a valid position //(TODO check for pos after goToPage()) //we only need to check the value. nextKey = currentPage.getKeys()[currentPos]; nextValue = currentPage.getValues()[currentPos]; hasValue = true; currentPos++; //now progress to next element //first progress to next page, if necessary. if (currentPos >= currentPage.getNKeys()) { goToNextPage(); if (currentPage == null) { return; } } //check for invalid value if (currentPage.getKeys()[currentPos] > maxKey) { close(); } } private void findFirstPosInPage() { //find first page if (!goToFirstPage()) { close(); return; } //find very first element. currentPos = (short) currentPage.binarySearch(currentPos, currentPage.getNKeys(), minKey, Long.MIN_VALUE); if (currentPos < 0) { currentPos = (short) -(currentPos+1); } //check position if (currentPos >= currentPage.getNKeys()) { //maybe we walked down the wrong branch? goToNextPage(); if (currentPage == null) { close(); return; } //okay, try again. currentPos = (short) currentPage.binarySearch(currentPos, currentPage.getNKeys(), minKey, Long.MIN_VALUE); if (currentPos < 0) { currentPos = (short) -(currentPos+1); } } if (currentPos >= currentPage.getNKeys() || currentPage.getKeys()[currentPos] > maxKey) { close(); return; } gotoPosInPage(); } @Override public LongLongIndex.LLEntry next() { return nextULL(); } /** * Dirty trick to avoid delays from finding the correct method. */ public LongLongIndex.LLEntry nextULL() { if (!hasNextULL()) { throw new NoSuchElementException(); } LongLongIndex.LLEntry e = new LongLongIndex.LLEntry(nextKey, nextValue); if (currentPage == null) { hasValue = false; } else { gotoPosInPage(); } return e; } public long nextKey() { if (!hasNextULL()) { throw new NoSuchElementException(); } checkValidity(); long ret = nextKey; if (currentPage == null) { hasValue = false; } else { gotoPosInPage(); } return ret; } @Override public void remove() { //As defined in the JDO 2.2. spec: throw new UnsupportedOperationException(); } /** * This method is possibly not be called if the iterator is used in 'for ( : ext) {}' * constructs! */ @Override public void close() { // after close() everything should throw NoSuchElementException (see 2.2. spec) currentPage = null; } }