/* * JBoss, Home of Professional Open Source. * See the COPYRIGHT.txt file distributed with this work for information * regarding copyright ownership. Some portions may be licensed * to Red Hat, Inc. under one or more contributor license agreements. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ package org.teiid.common.buffer; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.teiid.common.buffer.SPage.SearchResult; import org.teiid.core.TeiidComponentException; import org.teiid.core.TeiidProcessingException; /** * Implements intelligent browsing over a {@link STree} * * TODO: this is not as efficient as it should be over partial matches */ public class TupleBrowser implements TupleSource { private final STree tree; private TupleSource valueSet; private SPage page; private int index; private SPage bound; private int boundIndex = -1; private List<List<?>> values; private boolean updated; private boolean direction; private boolean inPartial; private List<Object> cachedBound; private ArrayList<SearchResult> places = new ArrayList<SearchResult>(); private boolean readOnly = true; /** * Construct a value based browser. The {@link TupleSource} should already be in the * proper direction. * @param sTree * @param valueSet * @param direction */ public TupleBrowser(STree sTree, TupleSource valueSet, boolean direction) { this(sTree, valueSet, direction, true); } /** * Construct a value based browser. The {@link TupleSource} should already be in the * proper direction. * @param sTree * @param valueSet * @param direction * @param readOnly */ public TupleBrowser(STree sTree, TupleSource valueSet, boolean direction, boolean readOnly) { this.tree = sTree; this.direction = direction; this.valueSet = valueSet; this.readOnly = readOnly; } /** * Construct a range based browser * @param sTree * @param lowerBound * @param upperBound * @param direction * @throws TeiidComponentException */ public TupleBrowser(STree sTree, List<Object> lowerBound, List<Object> upperBound, boolean direction, boolean readOnly) throws TeiidComponentException { this.tree = sTree; this.direction = direction; this.readOnly = readOnly; init(lowerBound, upperBound, false); } private void init(List<Object> lowerBound, List<?> upperBound, boolean isPartialKey) throws TeiidComponentException { if (lowerBound != null) { lowerBound.addAll(Collections.nCopies(tree.getKeyLength() - lowerBound.size(), null)); setPage(lowerBound); } else { page = tree.header[0]; } boolean valid = true; if (upperBound != null) { if (!isPartialKey && lowerBound != null && this.tree.comparator.compare(upperBound, lowerBound) < 0) { valid = false; } this.tree.find(upperBound, getPlaces()); SearchResult upper = places.get(places.size() - 1); bound = upper.page; boundIndex = upper.index; if (boundIndex < 0) { //we are guaranteed by find to not get back the -1 index, unless //there are no tuples, in which case a bound of -1 is fine boundIndex = Math.min(upper.values.size(), -boundIndex -1) - 1; } if (!direction) { setValues(upper.values); } if (lowerBound != null && page == bound) { valid = index<=boundIndex; } } else { while (bound == null || bound.children != null) { bound = tree.findChildTail(bound); } if (!direction) { if (page != bound || values == null) { setValues(bound.getValues()); } boundIndex = values.size() - 1; } } if (!direction) { SPage swap = page; page = bound; bound = swap; int upperIndex = boundIndex; boundIndex = index; index = upperIndex; } if (!valid) { page = null; } } private boolean setPage(List<?> lowerBound) throws TeiidComponentException { this.tree.find(lowerBound, getPlaces()); SearchResult sr = places.get(places.size() - 1); page = sr.page; index = sr.index; boolean result = true; if (index < 0) { result = false; index = -index - 1; } setValues(sr.values); return result; } private ArrayList<SearchResult> getPlaces() { places.clear(); return places; } @Override public List<?> nextTuple() throws TeiidComponentException, TeiidProcessingException { for (;;) { //first check for value iteration if (!inPartial && valueSet != null) { List<?> newValue = valueSet.nextTuple(); if (newValue == null) { resetState(); return null; } if (newValue.size() < tree.getKeyLength()) { if (cachedBound == null) { cachedBound = new ArrayList<Object>(tree.getKeyLength()); } cachedBound.clear(); cachedBound.addAll(newValue); init(cachedBound, newValue, true); inPartial = true; continue; } if (values != null) { int possibleIndex = Collections.binarySearch(values, newValue, tree.comparator); if (possibleIndex >= 0) { //value exists in the current page index = possibleIndex; return values.get(possibleIndex); } //check for end/terminal conditions if (direction && possibleIndex == -values.size() -1) { if (page.next == null) { resetState(); return null; } } else if (!direction && possibleIndex == -1) { if (page.prev == null) { resetState(); return null; } } else { //the value simply doesn't exist continue; } } resetState(); if (!setPage(newValue)) { continue; } return values.get(index); } if (page == null) { if (inPartial) { inPartial = false; continue; } return null; } if (values == null) { setValues(page.getValues()); if (direction) { index = 0; } else { index = values.size() - 1; } } if (index >= 0 && index < values.size()) { List<?> result = values.get(index); if (page == bound && index == boundIndex) { resetState(); page = null; //terminate } else { index+=getOffset(); } return result; } resetState(); if (direction) { page = page.next; } else { page = page.prev; } } } public void reset(TupleSource ts) throws TeiidComponentException { this.valueSet = ts; resetState(); } private void resetState() throws TeiidComponentException { if (updated) { page.setValues(values); } updated = false; setValues(null); } private int getOffset() { if (!inPartial && valueSet != null) { return 0; } return direction?1:-1; } /** * Perform an in-place update of the tuple just returned by the next method * WARNING - this must not change the key value * @param tuple * @throws TeiidComponentException */ public void update(List<?> tuple) throws TeiidComponentException { if (readOnly) { throw new AssertionError("read only"); //$NON-NLS-1$ } values.set(index - getOffset(), tuple); updated = true; } /** * Notify the browser that the last value was deleted. * @throws TeiidComponentException */ public void removed() throws TeiidComponentException { if (readOnly) { throw new AssertionError("read only"); //$NON-NLS-1$ } index-=getOffset(); List<?> lowerBound = values.remove(index); //check if the page has been removed if (page != null && page.managedBatch == null && page.values == null) { //navigate to the current position setPage(lowerBound); } if (index == values.size() && page != null) { boolean search = false; if (direction) { search = page.next == null; } else { search = page.prev == null; } if (search) { setPage(lowerBound); } } } @Override public void closeSource() { } private void setValues(List<List<?>> values) { if (updated) { throw new AssertionError("should have committed updates"); //$NON-NLS-1$ } if (readOnly || values == null || values instanceof LightWeightCopyOnWriteList) { this.values = values; } else { //use a lightweight copy if we're possibly mutative //as we'll modify the values in line //-for updates the browser will save back to the tree //-for deletes we let the normal tree logic handle the actual delete //so that the tree can rebalance this.values = new LightWeightCopyOnWriteList<List<?>>(values); } } }