# ResultsAdapter.java

    /*
    ** Module   : ResultsAdapter.java
    ** Abstract : Adapter object which abstracts underlying Results implementation
    **
    ** Copyright (c) 2004-2024, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- --JPRM-- ----------------------------Description-----------------------------
    ** 001 ECF 20060803   @28376 Created initial version. Adapter object which abstracts the
    **                           underlying Results implementation for a Results user, allowing
    **                           Results instances to be connected and disconnected without the
    **                           consumer being aware of the change.
    ** 002 ECF 20060808   @28572 Integrated ResultsProvider mechanism. This device allows client code
    **                           to expire the current delegate. Thereafter the adapter will request
    **                           a new delegate from the registered results provider at the soonest
    **                           opportunity.
    ** 003 ECF 20070225   @32230 Plug results resource leak. Clean up results resources when a new
    **                           results delegate is supplied. This was causing both prepared
    **                           statements and result sets to remain unclosed in the backing JDBC
    **                           connection.
    ** 004 ECF 20070711   @34453 Added sessionClosing() method. Delegates the handling of the session
    **                           close event to the currently connected Results object, if any.
    ** 005 ECF 20080122   @36887 Added getResults(). Retrieves delegate worker for this adapter.
    ** 006 ECF 20080709   @39088 Fixed setResults(). If new results object is null, set delegateNeeded
    **                           to true.
    ** 007 ECF 20190427          Fixed results caching.
    ** 008 ECF 20200906          New ORM implementation.
    ** 009 AL2 20230315          Implemented isAutoFetch.
    ** 010 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;

    /**
     * An implementation of the {@code Results} interface which delegates all work to another {@code
     * Results} implementation.  A worker can be connected at any time using {@link #setResults}, or
     * disconnected by passing a {@code null} parameter to this method.
     * <p>
     * Client code may expire the current delegate worker by invoking the {@link
     * #prepareDelegateChange()} method. This will cause the adapter to replace the delegate at the
     * next safe opportunity. This is accomplished by requesting a new {@code Results} object from a
     * {@link ResultsProvider} which is registered at construction time.
     */
    final class ResultsAdapter
    implements Results
    {
       /** Object which provides results to this adapter upon request */
       private final ResultsProvider provider;

       /** Delegate worker object */
       private Results results = null;

       /** State flag indicating a new delegate worker must be installed */
       private boolean delegateNeeded = true;

       /**
        * Default constructor.
        *
        * @param   provider
        *          Object which provides results to this adapter upon request.
        */
       ResultsAdapter(ResultsProvider provider)
       {
          this.provider = provider;
       }

       /**
        * Move cursor to the first result row.
        *
        * @return  {@code true} if there are any results.
        *
        * @throws  NullPointerException
        *          if the adapter is not {@link #isConnected() connected}.
        */
       public boolean first()
       {
          checkDelegate();

          return results.first();
       }

       /**
        * Move cursor to the last result row.
        *
        * @return  {@code true} if there are any results.
        *
        * @throws  NullPointerException
        *          if the adapter is not {@link #isConnected() connected}.
        */
       public boolean last()
       {
          checkDelegate();

          return results.last();
       }

       /**
        * Move cursor to the next result row.
        *
        * @return  {@code true} if there is a result under the cursor after the move.
        *
        * @throws  NullPointerException
        *          if the adapter is not {@link #isConnected() connected}.
        */
       public boolean next()
       {
          checkDelegate();

          return results.next();
       }

       /**
        * Move cursor to the previous result row.
        *
        * @return  {@code true} if there is a result under the cursor after the move.
        *
        * @throws  NullPointerException
        *          if the adapter is not {@link #isConnected() connected}.
        */
       public boolean previous()
       {
          checkDelegate();

          return results.previous();
       }

       /**
        * Is the cursor on the first row in the result set?
        *
        * @return  {@code true} if the cursor is on the first row.
        *
        * @throws  NullPointerException
        *          if the adapter is not {@link #isConnected() connected}.
        */
       public boolean isFirst()
       {
          return results.isFirst();
       }

       /**
        * Is the cursor on the last row in the result set?
        *
        * @return  {@code true} if the cursor is on the first row.
        *
        * @throws  NullPointerException
        *          if the adapter is not {@link #isConnected() connected}.
        */
       public boolean isLast()
       {
          return results.isLast();
       }

       /**
        * Is the result delegate responsible for fetching? This is usually true when using
        * query delegates which handle the fetching process already.
        *
        * @return   {@code true} for complex results which also do the fetching.
        *
        */
       @Override
       public boolean isAutoFetch()
       {
          return results.isAutoFetch();
       }

       /**
        * 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)
       {
          return results.get(forceOnlyId);
       }

       /**
        * 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}.
        *
        * @throws  NullPointerException
        *          if the adapter is not {@link #isConnected() connected}.
        */
       public Object get(int column)
       {
          return results.get(column);
       }

       /**
        * 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}.
        *
        * @throws  NullPointerException
        *          if the adapter is not {@link #isConnected() connected}.
        */
       public Long getID(int column)
       {
          return results.getID(column);
       }

       /**
        * 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.
        *
        * @throws  NullPointerException
        *          if the adapter is not {@link #isConnected() connected}.
        */
       public int getRowNumber()
       {
          return results.getRowNumber();
       }

       /**
        * 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}.
        *
        * @throws  NullPointerException
        *          if the adapter is not {@link #isConnected() connected}.
        */
       public boolean setRowNumber(int row)
       {
          checkDelegate();

          return results.setRowNumber(row);
       }

       /**
        * 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}.
        *
        * @throws  NullPointerException
        *          if the adapter is not {@link #isConnected() connected}.
        */
       public boolean scroll(int rows)
       {
          checkDelegate();

          return results.scroll(rows);
       }

       /**
        * Reset the cursor to its natural starting position, before the first result row.
        *
        * @throws  NullPointerException
        *          if the adapter is not {@link #isConnected() connected}.
        */
       public void reset()
       {
          checkDelegate();

          results.reset();
       }

       /**
        * Give the underlying worker a chance to respond to a session closing event.
        *
        * @see  Results#sessionClosing()
        */
       public void sessionClosing()
       {
          if (isConnected())
          {
             results.sessionClosing();
          }
       }

       /**
        * Delegate cleanup to the underlying worker.
        */
       public void cleanup()
       {
          if (isConnected())
          {
             results.cleanup();
          }
       }

       /**
        * 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. This implementation
        *          passes on the invocation to the delegate results object, if any. If none, it
        *          returns {@code false}.
        */
       public boolean isResultSetCached()
       {
          return isConnected() ? results.isResultSetCached() : false;
       }

       /**
        * Prepare for a disconnect and for a request for new delegate results from the results
        * provider. The current delegate will be disconnected, and a request for new results made, on
        * the next call to any method which would change the current position within the result set.
        */
       void prepareDelegateChange()
       {
          delegateNeeded = true;
       }

       /**
        * Get the underlying {@code Results} worker object for this adapter.
        *
        * @return  Underlying delegate worker.
        */
       Results getResults()
       {
          return results;
       }

       /**
        * Set the underlying {@code Results} worker object for this adapter.
        *
        * @param   results
        *          Underlying delegate worker.
        */
       void setResults(Results results)
       {
          if (this.results != null)
          {
             this.results.cleanup();
          }

          this.results = results;
          delegateNeeded = (results == null);
       }

       /**
        * Report whether this adapter currently is connected to a delegate worker.
        *
        * @return  {@code true} if there is a delegate {@code Results} object connected to this
        * adapter, else {@code false}.
        */
       boolean isConnected()
       {
          return (results != null);
       }

       /**
        * Check whether a new delegate worker is needed and if so, request the {@link ResultsProvider}
        * registered with this object to create a new one and to attach it to this adapter.
        */
       private void checkDelegate()
       {
          if (delegateNeeded)
          {
             provider.provideNewResults(this);
          }
       }
    }