xdev.db.jdbc.CachedVTPageScroller Maven / Gradle / Ivy
package xdev.db.jdbc;
/*-
* #%L
* XDEV Application Framework
* %%
* Copyright (C) 2003 - 2020 XDEV Software
* %%
* This program 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 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* .
* #L%
*/
import java.lang.ref.SoftReference;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import xdev.vt.VirtualTable;
/**
* This class extends VTPageScroller
by a means of caching page VTs
* that have already been loaded.
* The page caching is based on start row index and page row count.
* Cached pages are held in soft references, meaning they are released as soon
* as memory runs low.
*
* @author Thomas Muenz
*/
public class CachedVTPageScroller extends VTPageScroller
{
// /////////////////////////////////////////////////////////////////////////
// instance fields //
// //////////////////
private final HashMap> cachedPages = new HashMap>();
// /////////////////////////////////////////////////////////////////////////
// static methods //
// /////////////////
/**
* Null safe retrieval of a VirtualTable
object from a
* SoftReference
object.
*
* @param ref
* the SoftReference
object that points to a
* VirtualTable
object or null.
* @return null if ref
is null or the
* VirtualTable
object that is referenced by
* ref
.
*/
private static final VirtualTable getVTFromReference(final SoftReference ref)
{
return ref == null ? null : ref.get();
}
/**
* Creates a new Long
object from two ints
* rowIndexFrom
and rowCount
defining a page for
* use as a hash key.
*
* @param rowIndexFrom
* the row index in the original ResultSet
object of
* the first row int the page
* @param rowCount
* the number of rows in the page.
* @return a Long
object combining both int values to a single
* hash key value.
*/
protected static final Long createHashKey(final int rowIndexFrom, final int rowCount)
{
return (((long)rowIndexFrom) << 32) + rowCount;
}
// /////////////////////////////////////////////////////////////////////////
// constructors //
// ///////////////
/**
* Creates a new CachedVTPageScroller
object by calling
* super(rs)
.
*
* @param rs
* the scrollable ResultSet
object to be used for
* the to be created CachedVTPageScroller
object.
* @throws SQLException
* if rs
is not scrollable or
* rs.getType()
throws a SQLExcpetion
*/
public CachedVTPageScroller(final ResultSet rs) throws SQLException
{
super(rs);
}
/**
* Creates a new CachedVTPageScroller
object by calling
* super(rs, rowsPerPage)
.
*
* @param rs
* the scrollable ResultSet
object to be used for
* the to be created CachedVTPageScroller
object.
* @param rowsPerPage
* the number of rows to be retrieved per "page".
* @throws SQLException
* if rs
is not scrollable or
* rs.getType()
throws a SQLExcpetion
*/
public CachedVTPageScroller(final ResultSet rs, int rowsPerPage) throws SQLException
{
super(rs,rowsPerPage);
}
/**
* Creates a new CachedVTPageScroller
object by calling
* super(rs, rowsPerPage, firstRowIndex)
.
*
* @param rs
* the scrollable ResultSet
object to be used for
* the to be created CachedVTPageScroller
object.
* @param rowsPerPage
* the number of rows to be retrieved per "page".
* @param firstRowIndex
* the current row index of the first row in the first "page" to
* be created.
* @throws SQLException
* if rs
is not scrollable or
* rs.getType()
throws a SQLExcpetion
*/
public CachedVTPageScroller(final ResultSet rs, final int rowsPerPage, final int firstRowIndex)
throws SQLException
{
super(rs,rowsPerPage,firstRowIndex);
}
/**
* Creates a new CachedVTPageScroller
object by calling
* super(rs, rowsPerPage, firstRowIndex, totalRows)
.
*
* @param rs
* the scrollable ResultSet
object to be used for
* the to be created CachedVTPageScroller
object.
* @param rowsPerPage
* the number of rows to be retrieved per "page".
* @param firstRowIndex
* the current row index of the first row in the first "page" to
* be created.
* @param totalRows
* a hint to how much rows the ResultSet
object
* provides in total.
* @throws SQLException
* if rs
is not scrollable or
* rs.getType()
throws a SQLExcpetion
*/
public CachedVTPageScroller(final ResultSet rs, final int rowsPerPage, final int firstRowIndex,
final Integer totalRows) throws SQLException
{
super(rs,rowsPerPage,firstRowIndex,totalRows);
}
// /////////////////////////////////////////////////////////////////////////
// getters //
// ///////////////////
/**
* @return the cachedPages
*/
protected HashMap> getCachedPages()
{
return this.cachedPages;
}
// /////////////////////////////////////////////////////////////////////////
// setters //
// ///////////////////
/**
* {@inheritDoc}
*/
@Override
public CachedVTPageScroller setAllowNegativeRowIndex(boolean allowNegativePosition)
{
super.setAllowNegativeRowIndex(allowNegativePosition);
return this;
}
/**
* {@inheritDoc}
*/
@Override
public CachedVTPageScroller setCurrentRowIndex(int currentRowIndex)
{
super.setCurrentRowIndex(currentRowIndex);
return this;
}
/**
* {@inheritDoc}
*/
@Override
public CachedVTPageScroller setRowsPerPage(int rowsPerPage)
{
super.setRowsPerPage(rowsPerPage);
return this;
}
/**
* {@inheritDoc}
*/
@Override
public CachedVTPageScroller setTotalRows(Integer totalRows)
{
super.setTotalRows(totalRows);
return this;
}
// /////////////////////////////////////////////////////////////////////////
// override methods //
// ///////////////////
/**
* Sets the cursor (current row) of the wrapped scrollable
* ResultSet
object to rowIndexFrom
and creates a
* new VirtualTable
object from that row on with a length of
* rowCount
rows.
*
* Additionally, the created VirtualTable
object is cached.
* Cached VT objects will be reused by methods like
* currentPage()
, nextPage()
,
* previousPage()
if rowIndexFrom
and
* rowCount
of the page they request fit to a already cached VT
* object.
*
* @param rowIndexFrom
* the index of the ResultSet
object row to be the
* first row of the created "page".
* @param rowCount
* the amount of rows to be contained in the created "page"
* @return a newly created VirtualTable
object with rows from
* rowIndexFrom
till
* (rowIndexFrom + rowCount)
.
* @throws SQLException
* any SQLException that can be thrown in the process by the
* ResultSet
implementation
*/
@Override
public VirtualTable createVirtualTable(final int rowIndexFrom, final int rowCount)
throws SQLException
{
final VirtualTable vt = super.createVirtualTable(rowIndexFrom,rowCount);
this.putCachedVirtualTable(vt,rowIndexFrom,rowCount);
return vt;
}
/**
* Returns the page with the current getRowsPerPage()
rows as a
* VirtualTable
object.
* If a fitting VT object is found in the cache, it is returned. Otherwise a
* new VirtualTable
object is created.
*
* @return a fitting already existing or else newly created
* VirtualTable
object with the current
* getRowsPerPage()
rows.
* @throws SQLException
* any SQLException
that can be thrown by the
* ResultSet
implementation in the process.
*/
@Override
public VirtualTable currentPage() throws SQLException
{
final VirtualTable vt = this.getCachedVirtualTable(this.getCurrentRowIndex(),
this.getRowsPerPage());
return vt != null ? vt : super.currentPage();
}
/**
* Returns the page with the next getRowsPerPage()
rows as a
* VirtualTable
object.
* If a fitting VT object is found in the cache, it is returned. Otherwise a
* new VirtualTable
object is created.
*
* @return a fitting already existing or else newly created
* VirtualTable
object with the next
* getRowsPerPage()
rows or null if no next
* page is available according to totalRows
.
* @throws SQLException
* any SQLException
that can be thrown by the
* ResultSet
implementation in the process.
*/
@Override
public VirtualTable nextPage() throws SQLException
{
final Boolean b = this.hasNextPage();
if(b == null ? false : !b)
return null;
// if(isFalse(this.hasNextPage())) return null;
final int rowsPerPage = this.getRowsPerPage();
final int nextPageIndex = this.getCurrentRowIndex() + rowsPerPage;
final VirtualTable vt = this.getCachedVirtualTable(nextPageIndex,rowsPerPage);
if(vt != null)
{
this.setCurrentRowIndex(nextPageIndex);
return vt;
}
else
{
return this.nextPageNoCheck();
}
}
/**
* Returns the page with the previous getRowsPerPage()
rows as
* a VirtualTable
object.
* If a fitting VT object is found in the cache, it is returned. Otherwise a
* new VirtualTable
object is created.
*
* @return a fitting already existing or else newly created
* VirtualTable
object with the previous
* getRowsPerPage()
rows.
* @throws SQLException
* any SQLException
that can be thrown by the
* ResultSet
implementation in the process.
*/
@Override
public VirtualTable previousPage() throws SQLException
{
if(!this.hasPreviousPage())
return null;
final int rowsPerPage = this.getRowsPerPage();
final int prevPageIndex = this.getCurrentRowIndex() - rowsPerPage;
final VirtualTable vt = this.getCachedVirtualTable(prevPageIndex,rowsPerPage);
if(vt != null)
{
this.setCurrentRowIndex(prevPageIndex);
return vt;
}
else
{
return this.previousPageNoCheck();
}
}
// /////////////////////////////////////////////////////////////////////////
// declared methods //
// ///////////////////
/**
* Puts vt
into the cache with a key derived from
* rowIndexFrom
and rowCount
.
*
* @param vt
* the VirtualTable
object to be cached.
* @param rowIndexFrom
* the row index in the scrollable ResultSet
object
* used to create vt
that describes the first row of
* vt
.
* @param rowCount
* the number of rows for which vt
has been created.
* @return the VirtualTable
object that was cached for the
* equal key before.
*/
protected VirtualTable putCachedVirtualTable(final VirtualTable vt, final int rowIndexFrom,
final int rowCount)
{
synchronized(this.cachedPages)
{
final Long hashKey = createHashKey(rowIndexFrom,rowCount);
return getVTFromReference(this.cachedPages.put(hashKey,new SoftReference(
vt)));
}
}
/**
* Retrieves the VirtualTable
object from the cache that has
* been cached for rowIndexFrom
and rowCount
.
*
* @param rowIndexFrom
* the row index in the scrollable ResultSet
object
* used to create vt
that describes the first row of
* vt
.
* @param rowCount
* the number of rows the searched vt
shall contain.
* @return
*/
protected VirtualTable getCachedVirtualTable(final int rowIndexFrom, final int rowCount)
{
synchronized(this.cachedPages)
{
return getVTFromReference(this.cachedPages.get(createHashKey(rowIndexFrom,rowCount)));
}
}
}