/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.jena.jdbc.results; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import org.apache.jena.jdbc.statements.JenaStatement; import org.apache.jena.query.QueryExecution ; /** * Represents a set of materialized results backed by some * {@link QueryExecution}, materialized results permit scrolling but are not * sensitive to changes in the underlying data * * @param <T> * Type of the underlying result rows * */ public abstract class MaterializedResults<T> extends QueryExecutionResults { private T currItem; private int currRow = 0; /** * Creates new materialized results * * @param statement * Statement that created the result set * @param qe * Query Execution * @param commit * Whether a commit is necessary when the results are closed * @throws SQLException * Thrown if the arguments are invalid */ public MaterializedResults(JenaStatement statement, QueryExecution qe, boolean commit) throws SQLException { super(statement, qe, commit); } /** * Gets the current result row (if any) * * @return Result row, null if not at a row * @throws SQLException * Thrown if the result set is closed */ protected T getCurrentRow() throws SQLException { if (this.isClosed()) throw new SQLException("Result set is closed"); return this.currItem; } /** * Method which derived classes must implement to indicate whether they have * further rows available to move forwards to * * @return True if further rows are available, false otherwise * @throws SQLException * Thrown if an error determining whether rows are available */ protected abstract boolean hasNext() throws SQLException; /** * Method which derived classes must implement to indicate whether they have * further rows available to move backwards to * * @return True if further rows are available, false otherwise * @throws SQLException * Thrown if an error determining whether rows are available */ protected abstract boolean hasPrevious() throws SQLException; /** * Method which derived classes must implement to provide the next row * available * * @return Next row available * @throws SQLException * Thrown if this method is invoked when no further rows are * available */ protected abstract T moveNext() throws SQLException; /** * Method which derived classes must implement to provide the previous row * available * * @return Previous row available * @throws SQLException * Thrown if this method is invoked when no previous rows are * available */ protected abstract T movePrevious() throws SQLException; protected abstract int getTotalRows() throws SQLException; @Override public final boolean absolute(int row) throws SQLException { if (this.isClosed()) { throw new SQLException("Cannot move to a row after the result set has been closed"); } else if (row == 1) { // Try and move to the first row return this.first(); } else if (row == -1) { // Try and move to the last row return this.last(); } else if (row == 0) { // Try and move to before first row this.beforeFirst(); return false; } else if (row > this.getTotalRows()) { // Try and move to after last row this.afterLast(); return false; } else if (row <= 0) { // Move to a row relative to the end of the result set int destRow = this.getTotalRows() + 1 + row; if (destRow < 1) { // Move to before first this.beforeFirst(); return false; } else { // Call ourselves to avoid repeating logic return this.absolute(destRow); } } else if (row == this.currRow) { // Already at the desired row return true; } else if (row < this.currRow) { // After the desired row while (this.hasPrevious() && row < this.currRow) { this.currItem = this.movePrevious(); this.currRow--; } // Since we already checked that the desired row is a valid absolute // row it is always possible to move backwards to the desired row return true; } else { // Before the desired row while (this.hasNext() && this.currRow < row) { this.currItem = this.moveNext(); this.currRow++; } // Since we already checked that the desired row is a valid absolute // row it is always possible to move forwards to the desired row return true; } } @Override public final void afterLast() throws SQLException { if (this.isClosed()) throw new SQLException("Result Set is closed"); // Move to end of results if necessary while (this.hasNext()) { this.currItem = this.moveNext(); this.currRow++; } this.currItem = null; this.currRow = this.getTotalRows() + 1; } @Override public final void beforeFirst() throws SQLException { if (this.isClosed()) throw new SQLException("Result Set is closed"); // Are we after the last row? If so reset current row or movePrevious() // may break if (this.isAfterLast()) this.currRow = this.getTotalRows(); // Move to start of results if necessary while (this.hasPrevious()) { this.currItem = this.movePrevious(); this.currRow--; } this.currItem = null; this.currRow = 0; } @Override protected final void closeInternal() throws SQLException { this.currItem = null; this.closeStreamInternal(); } protected abstract void closeStreamInternal() throws SQLException; @Override public final boolean first() throws SQLException { if (this.isClosed()) throw new SQLException("Result Set is closed"); // If no rows this should always return false if (this.getTotalRows() == 0) return false; // Are we already at the first row? if (this.currRow == 1) return true; // Are we after the last row? If so reset current row or movePrevious() // may break if (this.isAfterLast()) this.currRow = this.getTotalRows(); // Before first row? if (this.isBeforeFirst()) { // Need to move forwards to first row if (this.hasNext()) { this.currItem = this.moveNext(); this.currRow = 1; return true; } else { return false; } } else { // Otherwise move backwards to it while (this.hasPrevious()) { this.currItem = this.movePrevious(); this.currRow--; } return true; } } @Override public final int getFetchDirection() { return ResultSet.FETCH_FORWARD; } @Override public final int getFetchSize() { // TODO Need a buffering wrapper around ResultSet to make this // configurable return 0; } @Override public final int getRow() { return this.currRow; } @Override public final int getType() { return ResultSet.TYPE_SCROLL_INSENSITIVE; } @Override public final boolean isAfterLast() throws SQLException { if (this.isClosed()) throw new SQLException("Result Set is closed"); return this.currRow > this.getTotalRows(); } @Override public final boolean isBeforeFirst() throws SQLException { if (this.isClosed()) throw new SQLException("Result Set is closed"); return this.currRow == 0; } @Override public final boolean isFirst() throws SQLException { if (this.isClosed()) throw new SQLException("Result Set is closed"); return this.currRow == 1; } @Override public final boolean isLast() throws SQLException { if (this.isClosed()) throw new SQLException("Result Set is closed"); return this.currRow == this.getTotalRows(); } @Override public final boolean last() throws SQLException { if (this.isClosed()) throw new SQLException("Result Set is closed"); // If no rows this should always return false if (this.getTotalRows() == 0) return false; // Are we already at the last row? if (this.currRow == this.getTotalRows()) return true; // Are we after the last row? if (this.isAfterLast()) { this.currRow = this.getTotalRows(); // Move backwards to last row if (this.hasPrevious()) { this.currItem = this.movePrevious(); this.currRow = this.getTotalRows(); return true; } else { return false; } } else { // Otherwise move forwards to the last row while (this.hasNext()) { this.currItem = this.moveNext(); this.currRow++; } return true; } } @Override public final boolean next() throws SQLException { if (this.isClosed()) { throw new SQLException("Cannot move to the next row in a closed result set"); } else { if (this.hasNext()) { this.currItem = this.moveNext(); this.currRow++; return true; } else { this.currItem = null; this.currRow = this.getTotalRows() + 1; return false; } } } @Override public final boolean previous() throws SQLException { if (this.isClosed()) { throw new SQLException("Cannot mvoe to the previous row in a closed result set"); } else { if (this.hasPrevious()) { this.currItem = this.movePrevious(); this.currRow--; return true; } else { this.currItem = null; this.currRow = 0; return false; } } } @Override public final boolean relative(int rows) throws SQLException { if (this.isClosed()) { throw new SQLException("Cannot move to a row after the result set has been closed"); } else if (rows == 0) { // Already at the desired row return true; } else if (rows < 0) { // Calculate destination row and use absolute() to move there int destRow = this.currRow + rows; // Need to sanitize resulting values below zero as otherwise // absolute will move us to a row relative to end of results when // actually this means we should be moving to before the first row if (destRow < 0) destRow = 0; return this.absolute(destRow); } else if (this.currRow + rows > this.getTotalRows()) { // Would result in moving beyond the end of the results this.afterLast(); return false; } else { // Before the desired row int moved = 0; while (this.hasNext() && moved < rows) { this.currItem = this.moveNext(); this.currRow++; moved++; } // Since we already checked if the move would take us beyond the end // of the results we will always have reached the desired row return true; } } @Override public final void setFetchDirection(int direction) throws SQLException { if (direction != ResultSet.FETCH_FORWARD) throw new SQLFeatureNotSupportedException("Jena JDBC Result Sets only support forward fetch"); } @Override public final void setFetchSize(int rows) throws SQLException { // TODO Need to provide some buffering wrapper over a ResultSet to make // this possible throw new SQLFeatureNotSupportedException(); } }