FQLBundle.java
/*
** Module : FQLBundle.java
** Abstract : FQL data necessary for random access query navigation
**
** Copyright (c) 2005-2022, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- -----------------------------------Description-----------------------------------
** 001 ECF 20051007 @23166 Created initial version. A bundle of HQL statements and method objects created by
** an HQLHelper and used by a RandomAccessQuery.
** 002 ECF 20060317 @25098 Added substitution indices feature. This array contains the index mapping of
** query substitution parameters, which may have been reordered during HQL statement
** preprocessing.
** 003 ECF 20080330 @37733 Code cleanup. Integrated generics. Replaced StringBuffer with StringBuilder.
** 004 ECF 20080516 @38499 Added getStatements() method and made class public.
** 005 ECF 20080606 @38604 Added support for dirty database HQL. Changed getStatements() to
** getDirtyStatements(). Added freeze() method to conserve a little memory.
** 006 ECF 20081009 @40076 Integrated ParameterIndices. This object replaces a simple array of integers.
** 007 GES 20090424 @41939 Import change.
** 008 EVL 20160223 Javadoc fixes to make compatible with Oracle Java 8 for Solaris 10.
** 009 ECF 20200906 New ORM implementation.
** 010 OM 20221103 Renamed class to FQLBundle.
*/
/*
** 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.lang.reflect.*;
import java.util.*;
import com.goldencode.util.*;
/**
* An assemblage of the various FQL statements, corresponding, reflective
* methods, and an index of the runtime query substitution parameters, which
* are necessary to satisfy a specific record retrieval request for a random
* access query.
* <p>
* Each bundle contains a list of one or more FQL statements which are
* executed in sequence by a {@link RandomAccessQuery} instance to obtain
* a result for a specific record retrieval (e.g., first, last, next, etc.)
* request. These statements include as their restriction criteria (i.e.,
* where clause), the query's original, base where clause (if any), augmented
* with sequentially less-specific restriction criteria composed using the
* query's sort criteria. If any parameterized substitutions are necessary
* to supply these statements with data extracted from the current record,
* the appropriate list of "getter" methods is supplied by the bundle as
* well.
* <p>
* To satisfy the random access query's single-record retrieval semantic,
* each statement is executed in turn, until a non-null result is returned.
* For each statement, as many of the getter methods are executed against the
* current record DMO, as are necessary to provided the substitutions
* required for that statement (for the augmented portion only). These
* values are added to the existing parameter array (if any) for the base
* where clause, at the time the statement is executed.
* <p>
* For example, given the following query criteria and a request to retrieve
* the "next" record...
* <pre>
* DMO: Customer
* alias: customer
* where: customer.number = ? and customer.creditLimit = ?
* order by: customer.name asc
* </pre>
* <p>
* ...the FQL bundle might contain the following list of FQL statements...
* <pre>
* 1) from Customer as customer
* where (customer.number = ? and customer.creditLimit = ?)
* and (customer.name = ? and customer.recid > ?)
*
* 2) from Customer as customer
* where (customer.number = ? and customer.creditLimit = ?)
* and (customer.name > ?)
* </pre>
* <p>
* ...and the following list of DMO "getter" methods...
* <pre>
* 1) Customer.getName()
* 2) Customer.primaryKey()
* </pre>
* <p>
* The methods are only used to satisfy the <code>customer.name</code> and
* <code>customer.id</code> substitutions; the <code>customer.number</code>
* and <code>customer.creditLimit</code> parameter substitution values must
* be provided by the client code which creates the query.
* <p>
* A bundle is unaware of the navigation/retrieval mode with which it has
* been tasked (e.g., first, last, next, etc.); it is simply a container
* for the information described above.
*
* @see FQLHelper
*/
public final class FQLBundle
{
/** DMO getter methods used to get sort criteria parameter values */
private List<Method> getters = new ArrayList<>();
/** FQL statements to be executed to satisfy a record retrieval request */
private List<String> statements = new ArrayList<>();
/** Parallel set of FQL statements for dirty database use */
private List<String> dirtyStatements = null;
/** Mapping of indices in the array of query substitution parameters */
private ParameterIndices paramIndices = null;
/**
* Default constructor.
*/
FQLBundle()
{
}
/**
* Get an unmodifiable view on the list of FQL statements in this bundle,
* which are for dirty database use. If no dirty statements have been
* stored in this bundle, the list of primary database FQL statements is
* returned.
*
* @return Unmodifiable list of FQL statements using dirty database's dialect.
*/
public List<String> getDirtyStatements()
{
return dirtyStatements;
}
/**
* Get a string representation of the internal state of this object,
* primarily for debugging purposes.
*
* @return String representing this object's state.
*/
public String toString()
{
String sep = System.getProperty("line.separator");
StringBuilder buf = new StringBuilder(sep);
buf.append("PRIMARY STATEMENTS").append(sep);
Iterator<String> stmtIter = statements.iterator();
while (stmtIter.hasNext())
{
buf.append(StringHelper.indent(stmtIter.next(), 3)).append(sep);
}
if (dirtyStatements != null)
{
buf.append("DIRTY STATEMENTS").append(sep);
stmtIter = dirtyStatements.iterator();
while (stmtIter.hasNext())
{
buf.append(StringHelper.indent(stmtIter.next(), 3)).append(sep);
}
}
buf.append("GETTERS").append(sep);
Iterator<Method> getterIter = getters.iterator();
while (getterIter.hasNext())
{
buf.append(" ").append(getterIter.next()).append(sep);
}
return buf.toString();
}
/**
* Get an iterator on the list of FQL statements in this bundle.
*
* @return Iterator of FQL statements.
*/
Iterator<String> statements()
{
return statements.iterator();
}
/**
* Get the number of FQL statements stored in this bundle.
*
* @return Statement list size.
*/
int statementsSize()
{
return statements.size();
}
/**
* Retrieve the FQL statement at a particular index in the list.
*
* @param index
* Index of the required statement.
* @param dirty
* <code>true</code> to retrieve statement for dirty database
* dialect; <code>false</code> to get primary statement.
*
* @return FQL statement at the specified index.
*
* @throws ArrayIndexOutOfBoundsException
* if <code>index</code> is invalid.
*/
String getStatement(int index, boolean dirty)
{
if (dirty && dirtyStatements == null)
{
throw new IllegalStateException(
"No dirty FQL statements available");
}
List<String> list = (dirty ? dirtyStatements : statements);
return list.get(index);
}
/**
* Replace the FQL statement at the specified index with a new statement.
*
* @param index
* Index at which to replace statement.
* @param statement
* New FQL statement.
* @param dirty
* <code>true</code> to set statement for dirty database dialect;
* <code>false</code> to set primary statement.
*
* @throws ArrayIndexOutOfBoundsException
* if <code>index</code> is invalid.
*/
void setStatement(int index, String statement, boolean dirty)
{
if (dirty && dirtyStatements == null)
{
throw new IllegalStateException("No dirty FQL statements available");
}
List<String> list = (dirty ? dirtyStatements : statements);
list.set(index, statement);
}
/**
* Add an FQL statement to the end of the list of primary or dirty
* statements stored in this bundle.
*
* @param statement
* FQL statement to add.
* @param dirty
* <code>true</code> to add statement for dirty database dialect;
* <code>false</code> to add primary statement.
*/
void addStatement(String statement, boolean dirty)
{
if (dirty)
{
if (dirtyStatements == null)
{
dirtyStatements = new ArrayList<>();
}
dirtyStatements.add(statement);
}
else
{
statements.add(statement);
}
}
/**
* Return the number of DMO getter methods stored in this bundle.
*
* @return A nonnegative number.
*/
int gettersSize()
{
return getters.size();
}
/**
* Get an iterator on the DMO getter methods, if any, stored in this
* bundle.
*
* @return Iterator of getter method objects.
*/
Iterator<Method> getters()
{
return getters.iterator();
}
/**
* Add a DMO getter method to the end of the list of methods stored in
* this bundle.
*
* @param getter
* Accessor method.
*/
void addGetter(Method getter)
{
getters.add(getter);
}
/**
* Get the <code>ParameterIndices</code> object associated with this
* bundle. This object maintains an array of zero-based indices into the
* array of query substitution arguments for the base (i.e., unaugmented)
* where clause, as it exists <i>after</i> preprocessing is complete. This
* additional level of indirection is necessary because the query
* substitution placeholders may have been reordered within the FQL where
* clause expression during the FQL preprocessing rewrite step, and some
* parameters may have been inlined into the where clause.
*
* @return An object which provides a mapping of indices into the query
* substitution parameter list.
*/
ParameterIndices getParameterIndices()
{
return paramIndices;
}
/**
* Set the <code>ParameterIndices</code> object associated with this
* bundle. This object maintains an array of zero-based indices into the
* array of query substitution arguments for the base (i.e., unaugmented)
* where clause, as it exists <i>after</i> preprocessing is complete. This
* additional level of indirection is necessary because the query
* substitution placeholders may have been reordered within the FQL where
* clause expression during the FQL preprocessing rewrite step, and some
* parameters may have been inlined into the where clause.
*
* @param paramIndices
* An object which provides a mapping of indices into the query
* substitution parameter list.
*/
void setParameterIndices(ParameterIndices paramIndices)
{
this.paramIndices = paramIndices;
}
/**
* Freeze this bundle so that its internal data structures can no longer be
* modified.
*/
void freeze()
{
((ArrayList<String>) statements).trimToSize();
statements = Collections.unmodifiableList(statements);
if (dirtyStatements == null)
{
dirtyStatements = statements;
}
else
{
((ArrayList<String>) dirtyStatements).trimToSize();
dirtyStatements = Collections.unmodifiableList(dirtyStatements);
}
((ArrayList<Method>) getters).trimToSize();
getters = Collections.unmodifiableList(getters);
}
}