/**
* Copyright (C) 2009-2013 FoundationDB, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.foundationdb.server.spatial;
import com.foundationdb.qp.operator.BindingsAwareCursor;
import com.foundationdb.qp.operator.CursorBase;
import com.foundationdb.qp.operator.QueryBindings;
import com.foundationdb.qp.row.IndexRow;
import com.foundationdb.qp.row.Row;
import com.foundationdb.server.api.dml.ColumnSelector;
import com.geophile.z.space.SpaceImpl;
// Allows cursor to be reset to the beginning, as long as next hasn't been called at least
// CACHE_SIZE times. Useful for wrapping IndexCursorUnidirectional's for use by geophile with
// CACHE_SIZE = 1. Geophile may do a random access, then probe the same key as an ancestor
// (retrieving one record), and then probe it again to prepare for sequential accesses.
public class CachingCursor implements BindingsAwareCursor
{
// CursorBase interface
@Override
public void open()
{
input.open();
}
@Override
public IndexRow next()
{
IndexRow next;
if (cachePosition < cachePositionsFilled) {
next = cachedRecord(cachePosition++);
} else {
next = (IndexRow) input.next();
if (cachePosition < CACHE_SIZE) {
recordCache[cachePosition++] = next;
cachePositionsFilled = cachePosition;
} else if (cachePosition == CACHE_SIZE) {
resettable = false;
}
}
return next;
}
public void close()
{
input.close();
}
@Override
public boolean isIdle()
{
return input.isIdle();
}
@Override
public boolean isActive()
{
return input.isActive();
}
@Override
public boolean isClosed()
{
return input.isClosed();
}
@Override
public void setIdle()
{
input.setIdle();
}
// BindingsAwareCursor interface
@Override
public void rebind(QueryBindings bindings)
{
assert input instanceof BindingsAwareCursor;
((BindingsAwareCursor) input).rebind(bindings);
}
// RowOrientedCursorBase interface
@Override
public void jump(Row row, ColumnSelector columnSelector)
{
assert row instanceof IndexRow;
IndexRow indexRow = (IndexRow) row;
if (indexRow.z() != z) {
throw new NotResettableException(indexRow.z());
}
if (!resettable) {
throw new NotResettableException();
}
cachePosition = 0;
}
// CachingCursor interface
public CachingCursor(long z, CursorBase<? extends Row> indexCursor)
{
// input is actually an IndexCursorUnidirectional. But this class only uses next(),
// and declaring CursorBase greatly simplifies testing.
this.z = z;
this.input = indexCursor;
}
// For use by this class
private IndexRow cachedRecord(int i)
{
return (IndexRow) recordCache[i];
}
// Class state
private static final int CACHE_SIZE = 1;
// Object state
private final long z;
private final CursorBase<? extends Row> input;
private final Object[] recordCache = new Object[CACHE_SIZE];
private int cachePosition = 0;
private int cachePositionsFilled = 0;
private boolean resettable = true;
// Inner classes
public static class NotResettableException extends RuntimeException
{
public NotResettableException()
{}
public NotResettableException(long z)
{
super(SpaceImpl.formatZ(z));
}
}
}