# P2JIndexComponent.java

    /*
    ** Module   : P2JIndexComponent.java
    ** Abstract : Index helper class; represents a Progress or SQL index component
    **
    ** Copyright (c) 2004-2025, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- --JPRM-- -----------------------------------Description-----------------------------------
    ** 001 ECF 20051010   @23179 Created initial version. Represents a single component of a database
    **                           index.
    ** 002 ECF 20070328   @32646 Partial fix for text components of an index. The getNameCaseAware()
    **                           method was not embedding the rtrim() function inside the upper()
    **                           function.
    ** 003 ECF 20070328   @32647 Need a more complete solution for the getNameCaseAware() method. The
    **                           previous fix does not properly wrap a component with the rtrim()
    **                           function for case-sensitive text components. We need more type
    **                           information for an index component in order to do that.
    ** 004 ECF 20070629   @35300 Minor optimization. Replaced StringBuffer with StringBuilder.
    ** 005 ECF 20080311   @37479 Added flag to indicate whether component is a character data type.
    **                           Also optimized flags into a single bitfield.
    ** 006 ECF 20140318          Added copy constructor.
    ** 007 CA  20190724          The field name must be case-insensitive.
    **     OM  20190829          Stored the original name and provided when case-sensitive name
    **                           is required.
    ** 008 ECF 20200906          Javadoc fixes.
    ** 009 OM  20200924          Objects of this class carry multiple information to avoid map lookups for them.
    **                           Made object immutable and local optimized.
    ** 019 IAS 20210219          Word tables support for custom extents
    ** 020 GBB 20230512          Logging methods replaced by CentralLogger/ConversionStatus.
    ** 021 DDF 20240205          Added the MANDATORY flag.
    */

    /*
    ** 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.p2j.util.logging.*;

    import java.util.*;

    /**
     * Represents a single index component; stores information about the component's name, sort direction, whether
     * it contains text data, its case sensitivity, and abbreviated status. The purpose of the name attribute is
     * left intentionally vague, such that it could represent a Progress field name, or an SQL database table
     * column name, or a Java DMO property name.
     */
    public final class P2JIndexComponent
    {
       /** Logger */
       private static final CentralLogger LOG = CentralLogger.get(P2JIndexComponent.class);

       /** Flag indicating descending (non-default) sort direction */
       private static final int DESCENDING = 0x01;

       /** Flag indicating component represents character data */
       private static final int CHARACTER = 0x02;

       /** Flag indicating component ignores case (character data only) */
       private static final int IGNORE_CASE = 0x04;

       /** Flag indicating abbreviated status (Progress semantic) */
       private static final int ABBREVIATED = 0x08;

       /** Flag indicating if the component is non-null */
       private static final int MANDATORY = 0x10;

       /** Name of indexed property (if available). */
       private final String propertyName;

       /** Name of indexed database column (if available). */
       private final String columnName;

       /** The legacy name of this component was built from (case sensitive), if available. */
       private final String legacyName;

       /** The legacy normalized name of this component was built from, if available. */
       private final String normalizedName;

       /** Extent of the component (can be > 0 only for a word index component) */
       private final int extent;
       /**
        * Flag for namespaces supported by this object. If this identifier is on then the 4GL legacy name is
        * present (and the normalized name, evidently).
        */
       public static final int LEGACY = 1;

       /**
        * Flag for namespaces supported by this object. If this identifier is on then the ORM property name is
        * present (usually the column is also present, but not mandatory).
        */
       public static final int PROPERTY = 2;

       /**
        * Flag for namespaces supported by this object. If this identifier is on then the SQL column name is
        * present.
        */
       public static final int COLUMN = 4;

       /**
        * Flag for namespaces supported by this object. If this identifier is on then the normalized 4GL legacy
        * name is present (and the case sensitive name, evidently).
        */
       public static final int NORMALIZED = 8;

       /** Property flags */
       private final int flags;

       /** Precomputed hash value. */
       private final int hash;

       /**
        * Constructor.
        *
        * @param   legacy
        *          The legacy name (optional).
        * @param   property
        *          Property name (optional).
        * @param   column
        *          Column name (optional).
        * @param   extent
        *          Extent of the component (can be > 0 only for a word index component).
        * @param   descending
        *          {@code true} if component has non-default, descending sort direction;  {@code false} if
        *          ascending.
        * @param   isChar
        *          {@code true} if this component represents a character field.
        * @param   ignoreCase
        *          {@code true} if this component represents a character field AND case is ignored when sorting
        *          index, else {@code false}.
        * @param   abbreviated
        *          {@code true} if index component supports the Progress semantic of using abbreviated data for a
        *          sort or selection match. Valid only if this is the last component of the index (not enforced
        *          here).
        * @param   isMandatory
        *          {@code true} if the index component is non-null, {@code false} otherwise.
        */
       public P2JIndexComponent(String legacy,
                                String property,
                                String column,
                                int extent,
                                boolean descending,
                                boolean isChar,
                                boolean ignoreCase,
                                boolean abbreviated,
                                boolean isMandatory)
       {
          // validation: all names cannot be simultaneously null:
          if (property == null && column == null && legacy == null)
          {
             throw new IllegalArgumentException("All index component names cannot be simultaneously null");
          }

          int flags = 0;
          if (descending)
          {
             flags |= DESCENDING;
          }
          if (isChar)
          {
             flags |= CHARACTER;
          }
          if (ignoreCase)
          {
             if (!isChar)
             {
                throw new IllegalArgumentException("Non-character index component cannot be case-insensitive");
             }
             flags |= IGNORE_CASE;
          }
          if (abbreviated)
          {
             flags |= ABBREVIATED;
          }
          if (isMandatory)
          {
             flags |= MANDATORY;
          }

          this.propertyName = property;
          this.columnName = column;
          this.legacyName = legacy;
          this.extent = extent;
          this.normalizedName = legacy == null ? null : legacy.toLowerCase();
          this.flags = flags;

          int hash0 = propertyName != null ? propertyName.hashCode() : 0;
          hash0 = 31 * hash0 + (columnName != null ? columnName.hashCode() : 0);
          hash0 = 31 * hash0 + (normalizedName != null ? normalizedName.hashCode() : 0);
          this.hash = 31 * hash0 + flags;
       }

       /**
        * Copy constructor.
        *
        * @param   component
        *          Instance to copy.
        */
       public P2JIndexComponent(P2JIndexComponent component)
       {
          this.propertyName = component.propertyName;
          this.columnName = component.columnName;
          this.legacyName = component.legacyName;
          this.normalizedName = component.normalizedName;
          this.flags = component.flags;
          this.hash = component.hash;
          this.extent = component.extent;
       }

       /**
        * Constructor.
        *
        * @param   legacy
        *          The legacy name (optional).
        * @param   property
        *          Property name (optional).
        * @param   column
        *          Column name (optional).
        * @param   descending
        *          {@code true} if component has non-default, descending sort direction;  {@code false} if
        *          ascending.
        * @param   isChar
        *          {@code true} if this component represents a character field.
        * @param   ignoreCase
        *          {@code true} if this component represents a character field AND case is ignored when sorting
        *          index, else {@code false}.
        * @param   abbreviated
        *          {@code true} if index component supports the Progress semantic of using abbreviated data for a
        *          sort or selection match. Valid only if this is the last component of the index (not enforced
        *          here).
        * @param   isMandatory
        *          {@code true} if the index component is non-null, {@code false} otherwise.
        */
       public P2JIndexComponent(String legacy,
                                String property,
                                String column,
                                boolean descending,
                                boolean isChar,
                                boolean ignoreCase,
                                boolean abbreviated,
                                boolean isMandatory)
       {
          this(legacy, property, column, 0, descending, isChar, ignoreCase, abbreviated, isMandatory);
       }
       /**
        * Returns one of the known flavours of this component.
        *
        * @param   namespace
        *          The flag for one of the namespace to be returned.
        *
        * @return  The name of the component in the requested name space.
        *
        * @throws  IllegalArgumentException
        *          if the parameter is not a simple namespace flag (no combinations allowed).
        * @throws  RuntimeException
        *          (temporary) If the requested name is not set. This normally point out a programming error
        *          as the caller should know the namespace this object was constructed with and the name spaces
        *          for them may be different (ex: Legacy: {@code a-Field}, Property: {@code aField}, SQL Column:
        *          {@code a_field}, Normalized legacy: {@code a-field}).
        */
       public String getName(int namespace)
       {
          switch (namespace)
          {
             case LEGACY: return getLegacyName();
             case PROPERTY: return getPropertyName();
             case COLUMN: return getColumnName();
             case NORMALIZED: return getNormalizedName();
             default: throw new IllegalArgumentException("Invalid P2JIndexComponent namespace");
          }
       }

       /**
        * Get index legacy's name (in lowercase). To get the case-sensitive name use {@link #getLegacyName}.
        *
        * @return  Index field or column name (in lower case).
        */
       public String getNormalizedName()
       {
          checkNotNull(normalizedName, "normalizedName");
          return normalizedName;
       }

       /**
        * Get index component legacy's original name (case-sensitive).
        *
        * @return  Index component legacy's original name (case-sensitive).
        */
       public String getLegacyName()
       {
          checkNotNull(legacyName, "legacyName");
          return legacyName;
       }

       /**
        * Get index component's column name.
        *
        * @return  Index column name.
        */
       public String getColumnName()
       {
          checkNotNull(columnName, "columnName");
          return columnName;
       }

       /**
        * Get index property's column name.
        *
        * @return  Index property name.
        */
       public String getPropertyName()
       {
          checkNotNull(propertyName, "propertyName");
          return propertyName;
       }

       /**
        * Get extent of the field.
        *
        * @return  Extent of the field.
        */
       public int getExtent()
       {
          return extent;
       }

       /**
        * Indicate if index component uses descending (non-default) sort direction.
        *
        * @return  {@code true} if sort direction is descending, else {@code false} if ascending.
        */
       public boolean isDescending()
       {
          return ((flags & DESCENDING) != 0);
       }

       /**
        * Indicate if index component represents a character field.
        *
        * @return  {@code true} if case is ignored;  else {@code false}.
        */
       public boolean isCharType()
       {
          return ((flags & CHARACTER) != 0);
       }

       /**
        * Indicate if index component represents a character field and ignores case in data comparisons.
        *
        * @return  {@code true} if case is ignored;  else {@code false}.
        */
       public boolean isIgnoreCase()
       {
          return ((flags & IGNORE_CASE) != 0);
       }

       /**
        * Indicate if index component supports Progress semantic of using
        * abbreviated data for a sort or selection match.
        *
        * @return  {@code true} if abbreviated match is supported, else {@code false}.
        */
       public boolean isAbbreviated()
       {
          return ((flags & ABBREVIATED) != 0);
       }

       /**
        * Indicate if index component is mandatory.
        *
        * @return  {@code true} if mandatory, else {@code false}.
        */
       public boolean isMandatory()
       {
          return ((flags & MANDATORY) != 0);
       }

       /**
        * Compare this object to another {@code P2JIndexComponent}. It is assumed both objects contains data in
        * same space names.
        *
        * @param   other
        *          The {@code P2JIndexComponent} object to compare to.
        * @param   direction
        *          If the direction should also be tested. If {@code false} the direction is omitted.
        *
        * @return  {@code true} only if the two components represents the same field/property/column.
        */
       public boolean sameAs(P2JIndexComponent other, boolean direction)
       {
          if (normalizedName != null && !normalizedName.equals(other.normalizedName))
          {
             return false;
          }

          if (columnName != null && !columnName.equals(other.columnName))
          {
             return false;
          }

          if (propertyName != null && !propertyName.equals(other.propertyName))
          {
             return false;
          }

          // compare the direction is requested too
          return !direction || isDescending() == other.isDescending();
       }

       /**
        * Test for equivalence;{@code o} is considered equivalent if it a {@code Component} instance with the same
        * name, sort direction, and abbreviation support as this component instance.
        *
        * @param   o
        *          An object instance, presumably a {@code Component} object, to test for equivalence with this
        *          object.
        *
        * @return  {@code true} if {@code o} is equivalent to this instance, else {@code false}.
        */
       @Override
       public boolean equals(Object o)
       {
          if (this == o)
          {
             return true;
          }
          if (o == null || getClass() != o.getClass())
          {
             return false;
          }

          P2JIndexComponent that = (P2JIndexComponent) o;

          if (flags != that.flags)
          {
             return false;
          }
          if (!Objects.equals(propertyName, that.propertyName))
          {
             return false;
          }
          if (!Objects.equals(columnName, that.columnName))
          {
             return false;
          }
          return Objects.equals(normalizedName, that.normalizedName);
       }

       /**
        * Hash code implementation which is consistent with this class'  implementation of {@link #equals}.
        *
        * @return  This object's precomputed hash code.
        */
       @Override
       public int hashCode()
       {
          return hash;
       }

       /**
        * Get a string representation of the internal state of this object, primarily for debugging purposes.
        *
        * @return  String representing this object's state.
        */
       @Override
       public String toString()
       {
          return "{" + legacyName + "/" + propertyName + "/" + columnName + ":" + flags + "}";
       }

       /**
        * Checks whether the requested name is {@code null}. An exception is thrown if the case.
        *
        * @param   name
        *          The name to be checked.
        * @param   space
        *          The namespace. Used for constructing the exception message.
        *
        * @throws  RuntimeException
        *          when the specified name is {@code null}. The message of the exception will contain a
        *          description of the internal structure to facilitate the dataflow and configured name-spaces.
        */
       private void checkNotNull(String name, String space)
       {
          if (name == null)
          {
             RuntimeException runtimeException = new RuntimeException(space + " == null for " + this);
             LOG.warning("", runtimeException);
             throw runtimeException;
          }
       }
    }