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;
}
}
}