# FQLHelperCache.java

    /*
    ** Module   : FQLHelperCache.java
    ** Abstract : Shared cache for FQLHelper object instances
    **
    ** Copyright (c) 2004-2025, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- --JPRM-- -----------------------------------Description-----------------------------------
    ** 001 ECF 20051007   @23249 Created initial version. Caches FQLHelper objects available in the
    **                           current context for faster access to queries.
    ** 002 ECF 20060120   @24043 Integrated with P2J server. Replaced ThreadLocal with ContextLocal.
    ** 003 ECF 20060306   @25022 Added support for foreign relation joins. The foreign DMO interface
    **                           was added as an additional, optional, secondary key component.
    ** 004 ECF 20070228   @32245 Added idOnly designation to key. Designates a projection query
    **                           returning the primary key ID only.
    ** 005 ECF 20070827   @35001 Minor optimization. Replace StringBuffer with StringBuilder.
    ** 006 ECF 20080310   @37476 Added Hibernate types to key.
    ** 007 ECF 20090204   @41256 Improved performance. Replaced string-based cache key with a faster
    **                           implementation.
    ** 008 ECF 20090205   @41260 Fixed NPE in Key's hashCode() and equals() methods.
    ** 009 ECF 20150301          Replaced context-local caches with a shared, fixed size cache for a
    **                           massive memory savings.
    ** 010 ECF 20160225          Switched from DMO implementation class to DMO interface as a key
    **                           element.
    ** 011 ECF 20190327          Replaced LFUAgingCache with LRUCache.
    ** 012 ECF 20200906          New ORM implementation.
    ** 013 CA  20200930          Cache the hash code value.
    **     OM  20221103          Renamed class to FQLHelperCache.
    ** 014 DDF 20230608          Made the size of the LRUCache configurable.
    **     DDF 20230613          Removed the static block that reads the configuration size of the cache.
    **                           CacheManager will handle the finding of the configuration size and the
    **                           cache creation.
    **     DDF 20230627          Made the cache final.
    **     DDF 20230706          Replaced createLRUCache call with another one that will use a null
    **                           discriminator by default.
    ** 015 LS  20250512          Added the included/excluded fields to the Key.
    */

    /*
    ** 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 com.goldencode.cache.*;
    import com.goldencode.p2j.persist.orm.Property;

    import java.util.Arrays;

    /**
     * A cache of {@link FQLHelper} objects mapped to a complex key which encompasses the DMO
     * implementation class, base where clause, Hibernate substitution types, restriction sorting,
     * foreign key information, and whether the query is a project query.
     * <p>
     * An <code>FQLHelper</code> instance is retrieved using the {@link #get} method;  it is stored
     * in the cache using the {@link #put} method.
     */
    final class FQLHelperCache
    {
       /** Expiry cache of FQL helper objects */
       private final Cache<Key, FQLHelper> cache;

       /**
        * Default constructor.
        */
       FQLHelperCache()
       {
          cache = CacheManager.createLRUCache(FQLHelperCache.class, 8192);
       }

       /**
        * Attempt to retrieve an {@link FQLHelper} from the cache.
        *
        * @param   dmoIface
        *          DMO interface associated with the helper. May not be <code>null</code>.
        * @param   types
        *          Hibernate types of query substitution parameters. May be an empty array, but may
        *          not be <code>null</code>.
        * @param   idOnly
        *          Indicates whether query is a projection query returning the primary key ID only.
        * @param   where
        *          Where clause of a query. May be <code>null</code>.
        * @param   sort
        *          Order by clause of a query. May be <code>null</code>.
        * @param   joinIface
        *          Interface DMO associated within query by foreign key.  May be <code>null</code>.
        * @param   included
        *          The included properties.
        * @param   excluded
        *          The excluded properties.
        *
        * @return  A cached <code>FQLHelper</code> instance or <code>null</code> if an existing
        *          instance was not found in the cache.
        */
       FQLHelper get(Class<? extends DataModelObject> dmoIface,
                     FqlType[] types,
                     boolean idOnly,
                     String where,
                     String sort,
                     Class<? extends DataModelObject> joinIface,
                     Property[] included,
                     Property[] excluded)
       {
          FQLHelper helper = null;
          Key key = new Key(dmoIface, where, types, idOnly, sort, joinIface, included, excluded);

          synchronized (cache)
          {
             helper = cache.get(key);
          }

          return helper;
       }

       /**
        * Store an {@link FQLHelper} in the cache.
        *
        * @param   dmoIface
        *          DMO interface associated with the helper. May not be <code>null</code>.
        * @param   types
        *          Hibernate types of query substitution parameters. May be an empty array, but may
        *          not be <code>null</code>.
        * @param   idOnly
        *          Indicates whether query is a projection query returning the primary key ID only.
        * @param   where
        *          Where clause of a query. May be <code>null</code>.
        * @param   sort
        *          Order by clause of a query. May be <code>null</code>.
        * @param   joinIface
        *          Interface DMO associated within query by foreign key.  May be <code>null</code>.
        * @param   helper
        *          <code>FQLHelper</code> instance to be cached.
        * @param   included
        *          The included properties.
        * @param   excluded
        *          The excluded properties.
        */
       void put(Class<? extends DataModelObject> dmoIface,
                FqlType[] types,
                boolean idOnly,
                String where,
                String sort,
                Class<? extends DataModelObject> joinIface,
                FQLHelper helper,
                Property[] included,
                Property[] excluded)
       {
          Key key = new Key(dmoIface, where, types, idOnly, sort, joinIface, included, excluded);

          synchronized (cache)
          {
             cache.put(key, helper);
          }
       }

       /**
        * <code>FQLHelper</code> hashable lookup key.
        */
       private static class Key
       {
          /** DMO interface */
          private final Class<? extends DataModelObject> dmoIface;

          /** FQL where clause, if any */
          private final String where;

          /** Hibernate types of query substitution parameters, if any */
          private final FqlType[] types;

          /** Is query a projection query returning primary key ID only? */
          private final boolean idOnly;

          /** Order by clause, if any */
          private final String sort;

          /** Interface of DMO with which query's records are joined, if any */
          private final Class<?> joinIface;

          /** Pre-computed hash code. */
          private final int hashCode;

          /** The included properties (FIELDS option). */
          private final Property[] included;

          /** The excluded properties (EXCEPT option). */
          private final Property[] excluded;

          /**
           * Constructor for a lookup key for the given data.
           *
           * @param   dmoIface
           *          DMO interface associated with the helper. May not be <code>null</code>.
           * @param   types
           *          Hibernate types of query substitution parameters. May be an empty array, but may
           *          not be <code>null</code>.
           * @param   idOnly
           *          Indicates whether query is a projection query returning the primary key ID only.
           * @param   where
           *          Where clause of a query. May be <code>null</code>.
           * @param   sort
           *          Order by clause of a query. May be <code>null</code>.
           * @param   joinIface
           *          Interface DMO associated within query by foreign key.  May be <code>null</code>.
           * @param   included
           *          The included properties.
           * @param   excluded
           *          The excluded properties.
           */
          Key(Class<? extends DataModelObject> dmoIface,
              String where,
              FqlType[] types,
              boolean idOnly,
              String sort,
              Class<?> joinIface,
              Property[] included,
              Property[] excluded)
          {
             this.dmoIface = dmoIface;
             this.where = where;
             this.types = types;
             this.idOnly = idOnly;
             this.sort = sort;
             this.joinIface = joinIface;
             this.included = included != null ? included.clone(): null;
             this.excluded = excluded != null ? excluded.clone(): null;

             int result = 17;

             result = 37 * result + dmoIface.hashCode();
             result = 37 * result + (where == null ? 0 : where.hashCode());
             result = 37 * result + (idOnly ? 1 : 0);
             result = 37 * result + (sort == null ? 0 : sort.hashCode());
             result = 37 * result + (joinIface == null ? 0 : joinIface.hashCode());
             for (FqlType type : types)
             {
                result = 37 * result + type.hashCode();
             }
             result = 37 * result + Arrays.hashCode(included);
             result = 37 * result + Arrays.hashCode(excluded);

             this.hashCode = result;
          }

          /**
           * A hash code implementation consistent with our overridden {@link #equals(Object)} method.
           *
           * @return  Hash code.
           */
          public int hashCode()
          {
             return hashCode;
          }

          /**
           * Test for equivalence with another object.
           *
           * @param   o
           *          Another <code>ColumnKey</code> instance.
           *
           * @return  <code>true</code> if <code>o</code> has same internal
           *          state as this object;  else <code>false</code>.
           */
          public boolean equals(Object o)
          {
             if (!(o instanceof Key))
             {
                return false;
             }

             Key that = (Key) o;

             if ((this.joinIface == null || that.joinIface == null) &&
                 this.joinIface != that.joinIface)
             {
                return false;
             }

             if ((this.where == null || that.where == null) &&
                 this.where != that.where)
             {
                return false;
             }

             if ((this.sort == null || that.sort == null) &&
                 this.sort != that.sort)
             {
                return false;
             }

             boolean same = (this.dmoIface.equals(that.dmoIface)                               &&
                             this.idOnly == that.idOnly                                        &&
                             (this.joinIface == null || this.joinIface.equals(that.joinIface)) &&
                             (this.sort == null || this.sort.equals(that.sort))                &&
                             (this.where == null || this.where.equals(that.where)));

             int len = this.types.length;
             if (!same || len != that.types.length)
             {
                return false;
             }
             return Arrays.equals(included, that.included) && Arrays.equals(excluded, that.excluded);

    //         for (int i = 0; i < len; i++)
    //         {
    //            if (!this.types[i].equals(that.types[i]))
    //            {
    //               return false;
    //            }
    //         }
          }

          /**
           * Debug method to provide string representation of this object's state.
           *
           * @return  String representation of this object's state.
           */
          public String toString()
          {
             StringBuilder buf = new StringBuilder();
             buf.append(dmoIface.getName());
             buf.append("@");
             buf.append(idOnly ? DatabaseManager.PRIMARY_KEY : "dmo");
             buf.append("@");
             buf.append(where);
             buf.append("@");
             buf.append(sort);
             buf.append("@");
             buf.append(joinIface != null ? joinIface.getName() : "null");
             for (FqlType type : types)
             {
                buf.append("#");
                buf.append(type);
             }
             if (included != null)
             {
                for (Property p : included)
                {
                   if (p != null)
                   {
                      buf.append("#");
                      buf.append(p);
                   }
                }
             }
             if (excluded != null)
             {
                for (Property p : excluded)
                {
                   if (p != null)
                   {
                      buf.append("#");
                      buf.append(p);
                   }
                }
             }
             return buf.toString();
          }
       }
    }