/*
* 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.jdbc;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
/**
* @since 4.3
*/
public class BatchResults {
public interface BatchFetcher {
Batch requestBatch(int beginRow) throws SQLException;
}
static class Batch{
private List<?>[] batch;
private int beginRow;
private int endRow;
private boolean isLast;
private int lastRow = -1;
Batch(List<?>[] batch, int beginRow, int endRow){
this.batch = batch;
this.beginRow = beginRow;
this.endRow = this.beginRow + this.batch.length - 1;
if (endRow != this.endRow) {
this.isLast = true;
}
}
int getLastRow() {
return lastRow;
}
void setLastRow(int lastRow) {
this.lastRow = lastRow;
}
int getLength() {
return batch.length;
}
List<?> getRow(int index) {
return batch[index - beginRow];
}
int getBeginRow() {
return beginRow;
}
int getEndRow() {
return endRow;
}
boolean isLast() {
return isLast;
}
}
static final int DEFAULT_SAVED_BATCHES = 3;
private ArrayList<Batch> batches = new ArrayList<Batch>();
private int currentRowNumber;
private List<?> currentRow;
private int lastRowNumber = -1;
private int highestRowNumber;
private BatchFetcher batchFetcher;
private int savedBatches = DEFAULT_SAVED_BATCHES;
private boolean tailLast;
public BatchResults(BatchFetcher batchFetcher, Batch batch, int savedBatches) {
this.batchFetcher = batchFetcher;
this.savedBatches = savedBatches;
this.setBatch(batch);
}
/**
* Moving forward through the results it's expected that the batches are arbitrarily size.
* Moving backward through the results it's expected that the batches will match the fetch size.
*/
public List<?> getCurrentRow() throws SQLException {
if (currentRow != null) {
return currentRow;
}
if (this.currentRowNumber == 0 || (lastRowNumber != -1 && this.currentRowNumber > lastRowNumber)) {
return null;
}
for (int i = 0; i < batches.size(); i++) {
Batch batch = batches.get(i);
if (this.currentRowNumber < batch.getBeginRow()) {
continue;
}
if (this.currentRowNumber > batch.getEndRow()) {
continue;
}
if (i != 0) {
batches.add(0, batches.remove(i));
}
setCurrentRow(batch);
return currentRow;
}
requestBatchAndWait(this.currentRowNumber);
Batch batch = batches.get(0);
setCurrentRow(batch);
return currentRow;
}
private void setCurrentRow(Batch batch) {
currentRow = batch.getRow(this.currentRowNumber);
if (batch.isLast() && batch.getEndRow() == this.currentRowNumber) {
currentRow = null;
}
}
private void requestNextBatch() throws SQLException {
requestBatchAndWait(highestRowNumber + 1);
}
public boolean next() throws SQLException{
if (hasNext()) {
setCurrentRowNumber(this.currentRowNumber + 1);
getCurrentRow();
return true;
}
if (this.currentRowNumber == highestRowNumber) {
setCurrentRowNumber(this.currentRowNumber + 1);
}
return false;
}
public boolean hasPrevious() {
return (this.currentRowNumber != 0 && this.currentRowNumber != 1);
}
public boolean previous() {
if (hasPrevious()) {
setCurrentRowNumber(this.currentRowNumber - 1);
return true;
}
if (this.currentRowNumber == 1) {
setCurrentRowNumber(this.currentRowNumber - 1);
}
return false;
}
public void setBatchFetcher(BatchFetcher batchFetcher) {
this.batchFetcher = batchFetcher;
}
public boolean absolute(int row) throws SQLException {
return absolute(row, 0);
}
public boolean absolute(int row, int offset) throws SQLException {
if(row == 0) {
setCurrentRowNumber(0);
return false;
}
if (row > 0) {
if (row + offset > highestRowNumber && lastRowNumber == -1) {
requestBatchAndWait(row + offset);
}
if (row + offset <= highestRowNumber) {
setCurrentRowNumber(row);
return true;
}
setCurrentRowNumber(lastRowNumber + 1 - offset);
return false;
}
row -= offset;
if (lastRowNumber == -1) {
requestBatchAndWait(Integer.MAX_VALUE);
}
int positiveRow = lastRowNumber + row + 1;
if (positiveRow <= 0) {
setCurrentRowNumber(0);
return false;
}
setCurrentRowNumber(positiveRow);
return true;
}
public int getCurrentRowNumber() {
return currentRowNumber;
}
private void requestBatchAndWait(int beginRow) throws SQLException{
setBatch(batchFetcher.requestBatch(beginRow));
}
void setBatch(Batch batch) {
if (batches.size() == savedBatches) {
batches.remove(savedBatches - 1);
}
if (batch.getLastRow() != -1) {
this.lastRowNumber = batch.getLastRow();
this.highestRowNumber = batch.getLastRow();
} else {
highestRowNumber = Math.max(batch.getEndRow(), highestRowNumber);
tailLast = batch.isLast();
}
this.batches.add(0, batch);
}
public boolean hasNext() throws SQLException {
return hasNext(1, true);
}
public Boolean hasNext(int next, boolean wait) throws SQLException {
while (this.currentRowNumber + next > highestRowNumber && lastRowNumber == -1) {
if (!wait) {
return null;
}
requestNextBatch();
}
boolean result = this.currentRowNumber + next <= highestRowNumber;
if (result && !wait) {
for (int i = 0; i < batches.size(); i++) {
Batch batch = batches.get(i);
if (this.currentRowNumber + next < batch.getBeginRow()) {
continue;
}
if (this.currentRowNumber + next> batch.getEndRow()) {
continue;
}
return Boolean.TRUE;
}
return null; //needs to be fetched
}
return result;
}
public int getFinalRowNumber() {
return lastRowNumber;
}
public int getHighestRowNumber() {
return highestRowNumber;
}
private void setCurrentRowNumber(int currentRowNumber) {
if (currentRowNumber != this.currentRowNumber) {
this.currentRow = null;
}
this.currentRowNumber = currentRowNumber;
}
public boolean isTailLast() {
return tailLast;
}
}