SimpleResults.java
/*
** Module : SimpleResults.java
** Abstract : Simple implementation of an abstraction layer for query results
**
** Copyright (c) 2004-2024, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- ----------------Description-----------------------------------------
** 001 ECF 20060214 @24712 Created initial version. An implementation of
** Results which uses a List object to store the
** backing row data.
** 002 ECF 20060725 @28191 Added cleanup() method.
** 003 ECF 20070508 @33462 Fixed cleanup() method. Needed a call to
** reset the results.
** 004 ECF 20070711 @34449 Added sessionClosing() method. This is a
** no-op implementation required by the Results
** interface.
** 005 ECF 20080124 @37003 Changed constructor signature. Parameter data
** type is more specific.
** 006 ECF 20080310 @37485 Made backing list of rows less specific.
** 007 SVL 20090723 @43404 Added getNumberOfLoadedRows() function.
** 008 SVL 20141015 Fixed edge cases. Added deleteRow.
** 009 ECF 20160330 Fixed NPE in get(int).
** 010 ECF 20190427 Fixed results caching.
** 011 SVL 20191002 Added addRow.
** 012 ECF 20200906 New ORM implementation.
** 013 AL2 20231027 Made SimpleResults implement FullResults and implement isEmpty.
** 014 AL2 20240906 Implemented get(forceOnlyId).
*/
/*
** 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 may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
**
** Additional terms under GNU Affero GPL version 3 section 7:
**
** Under Section 7 of the GNU Affero GPL version 3, the following additional
** terms apply to the works covered under the License. These additional terms
** are non-permissive additional terms allowed under Section 7 of the GNU
** Affero GPL version 3 and may not be removed by you.
**
** 0. Attribution Requirement.
**
** You must preserve all legal notices or author attributions in the covered
** work or Appropriate Legal Notices displayed by works containing the covered
** work. You may not remove from the covered work any author or developer
** credit already included within the covered work.
**
** 1. No License To Use Trademarks.
**
** This license does not grant any license or rights to use the trademarks
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
** of Golden Code Development Corporation. You are not authorized to use the
** name Golden Code, FWD, or the names of any author or contributor, for
** publicity purposes without written authorization.
**
** 2. No Misrepresentation of Affiliation.
**
** You may not represent yourself as Golden Code Development Corporation or FWD.
**
** You may not represent yourself for publicity purposes as associated with
** Golden Code Development Corporation, FWD, or any author or contributor to
** the covered work, without written authorization.
**
** 3. No Misrepresentation of Source or Origin.
**
** You may not represent the covered work as solely your work. All modified
** versions of the covered work must be marked in a reasonable way to make it
** clear that the modified work is not originating from Golden Code Development
** Corporation or FWD. All modified versions must contain the notices of
** attribution required in this license.
*/
package com.goldencode.p2j.persist;
import java.util.*;
/**
* A simple implementation of {@code Results} which stores row data in a list provided to the
* constructor. The row data consists of a list of array objects. Each object in the array is
* either a {@link Record} object (a DMO instance) or a {@code Long} which
* represents a DMO's primary key ID. Note that this implementation may have a heavy memory
* requirement for large results sets, especially if the entire DMO is stored and not just its
* primary key ID.
*/
class SimpleResults
implements FullResults
{
/** Results list; each row is an array of primary keys or DMOs */
private final List<Object[]> rows;
/** Index of current position in results list; -1 is off end */
private int position = -1;
/**
* Constructor.
*
* @param rows
* List of row data.
*/
public SimpleResults(List<Object[]> rows)
{
this.rows = (rows == null ? new ArrayList<>() : rows);
}
/**
* Move cursor to the first results row.
*
* @return {@code true} if there are any results.
*/
public boolean first()
{
if (rows.isEmpty())
{
return false;
}
position = 0;
return true;
}
/**
* Move cursor to the last results row.
*
* @return {@code true} if there are any results.
*/
public boolean last()
{
if (rows.isEmpty())
{
return false;
}
position = getNumberOfLoadedRows() - 1;
return true;
}
/**
* Move cursor to the next results row.
*
* @return {@code true} if there is a result under the cursor under the move.
*/
public boolean next()
{
position++;
if (position >= getNumberOfLoadedRows())
{
position = getNumberOfLoadedRows();
return false;
}
return true;
}
/**
* Move cursor to the previous results row.
*
* @return {@code true} if there is a result under the cursor under the move.
*/
public boolean previous()
{
position--;
if (position < 0)
{
position = -1;
return false;
}
return true;
}
/**
* Is the cursor on the first row in the results set?
*
* @return {@code true} if the cursor is on the first row.
*/
public boolean isFirst()
{
return (position == 0);
}
/**
* Is the cursor on the last row in the results set?
*
* @return {@code true} if the cursor is on the first row.
*/
public boolean isLast()
{
return (position >= 0 && position == getNumberOfLoadedRows() - 1);
}
/**
* Returns the row at the current position. It can be either only an array of PK or
* an array of DMOs.
*
* @param forceOnlyId
* Indicates whether it's only the primary keys that must be returned,
* or original data found is returned (either PK or DMO).
*
* @return Object array or {@code null}.
*/
public Object[] get(boolean forceOnlyId)
{
if (position < 0 || position > getNumberOfLoadedRows() - 1)
{
return null;
}
Object res = rows.get(position);
if (!(res instanceof Object[]))
{
res = new Object[] { res };
}
if (forceOnlyId)
{
Object[] results = new Object[((Object[]) res).length];
for (int i = 0; i < results.length; i++)
{
if (results[i] instanceof Record)
{
results[i] = ((Record) results[i]).primaryKey();
}
else
{
results[i] = ((Object[]) res)[i];
}
}
return results;
}
return (Object[]) res;
}
/**
* Get the object at the current result row, at the specified column.
*
* @param column
* Zero-based index column of the desired object.
*
* @return Object at {@code column} or {@code null}.
*/
public Object get(int column)
{
if (position < 0)
{
return null;
}
Object[] row = get();
return (row != null) ? row[column] : null;
}
/**
* Get the primary key ID at the current result row, at the specified column.
*
* @param column
* Zero-based index column of the desired ID.
*
* @return ID of the record or {@code null}.
*/
public Long getID(int column)
{
Object result = get(column);
if (result instanceof Record)
{
return ((Record) result).primaryKey();
}
return (Long) result;
}
/**
* Get the row number currently under the cursor.
*
* @return Zero-based index of the current row, or {@code -1} if the cursor is not currently
* on a result.
*/
public int getRowNumber()
{
return position;
}
/**
* Set the row number currently under the cursor.
*
* @param row
* Zero-based index of the row to be set as the current row.
*
* @return {@code true} if there is a row at the specified row number, else {@code false}.
*/
public boolean setRowNumber(int row)
{
int size = getNumberOfLoadedRows();
if (row < 0)
{
position = -1;
return false;
}
else if (row >= size)
{
position = size;
return false;
}
position = row;
return true;
}
/**
* Scroll the cursor ahead by the specified number of rows. If the number is negative, the
* cursor is moved backward. This may put the cursor off the end (either end) of the query's
* results.
*
* @param rows
* Number of rows to jump ahead or back.
*
* @return {@code true} if there is a row at the new location, else {@code false}.
*/
public boolean scroll(int rows)
{
return setRowNumber(position + rows);
}
/**
* Reset the cursor to its natural starting position, before the first result row.
*/
public void reset()
{
position = -1;
}
/**
* Required by the {@code Results} interface, but this is a no-op implementation.
*/
public void sessionClosing()
{
}
/**
* Clear the internal list of result rows.
*/
public void cleanup()
{
rows.clear();
reset();
}
/**
* Indicate whether the full result set is cached in this object.
*
* @return {@code true} if all results are contained this object; {@code false} if not or
* if unknown (such as for a scrolling or cursored result set. Return value for this
* default implementation is {@code true}.
*/
@Override
public boolean isResultSetCached()
{
return true;
}
/**
* Returns the number of rows which have been loaded by this {@code Results}.
*
* @return See above.
*/
public int getNumberOfLoadedRows()
{
return rows.size();
}
/**
* Delete the current row.
*
* @return {@code true} if the row was deleted. {@code false} if there is no row at
* the current position.
*/
public boolean deleteRow()
{
if (position < 0 || position > getNumberOfLoadedRows() - 1)
{
return false;
}
rows.remove(position);
return true;
}
/**
* Add a new row.
*
* @param row
* Row data.
* @param beforeRow
* <code>true</code> if the current position is before the row. <code>false</code> if
* the current position is on the row.
*/
public void addRow(Object[] row, boolean beforeRow)
{
if (position < 0)
{
position = 0;
}
else if (!beforeRow && position < getNumberOfLoadedRows())
{
position++;
}
rows.add(position, row);
}
/**
* Get the list of row data which backs this results object.
*
* @return List of rows.
*/
protected List<Object[]> getRows()
{
return rows;
}
/**
* Check if this has records. This is part of the {@link FullRecords} implementation.
*
* @return {@code true} if there are rows inside this results.
*/
@Override
public boolean isEmpty()
{
return rows.isEmpty();
}
}