# 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 &gt; ?)
     *
     * 2) from Customer as customer
     *    where (customer.number = ? and customer.creditLimit = ?)
     *      and (customer.name &gt; ?)
     * </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);
       }
    }