P2JIndex.java
/*
** Module : P2JIndex.java
** Abstract : Index helper class; represents a Progress or SQL index
**
** Copyright (c) 2005-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- -----------------------------------Description-----------------------------------
** 001 ECF 20051010 @23180 Created initial version. Describes a database index and contains several helper
** methods to detect index redundancy and sets of minimal unique index component
** sets.
** 002 ECF 20060129 @24284 Changes needed for temp table support. Added convenience constructor and made
** table name a settable field after construction.
** 003 ECF 20070424 @33213 Fixed equals and hashCode implementations. Neither took index component order
** into account.
** 004 ECF 20080311 @37478 Added isChar parameter to addComponent(). Also integrated generics.
** 005 ECF 20090428 @42126 Exposed setUnique(boolean) as package private. Needed by DirtyTempTableHelper.
** 006 VMN 20130830 Added method getOrderByClause().
** 007 VMN 20131126 Fixed getOrderByClause(), removed to-do marker.
** 008 OM 20130821 Added descending support for index components.
** 009 SVL 20140106 Minor change in toString.
** 010 ECF 20140318 Added copy constructor, compact method.
** 011 ECF 20140914 Fixed minor problem with hashCode implementation.
** 012 ECF 20150201 Added components(boolean) method.
** 013 ECF 20200906 New ORM implementation.
** 014 OM 20200925 Separated namespaces for index components.
** 015 IAS 20201224 Added word tables support
** IAS 20210219 Word tables support for custom extents
** IAS 20210408 Added copy c'tor with table name replacement.
** ECF 20220617 When eliminating redundant indices, consider component sort direction.
** OM 20220609 Added support for legacy name.
** SVL 20230110 P2JIndex.components() provides direct access to the components array in order to
** improve performance.
** 016 CA 20230221 Javadoc fixes.
** 017 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
** 018 DDF 20240205 Added mandatory property to index components.
*/
/*
** 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.util.*;
import com.goldencode.p2j.persist.dialect.*;
import com.goldencode.p2j.util.logging.*;
/**
* A descriptor of an index, which may be either a Progress index or an
* SQL database index. The latter's features for our purposes here represent
* a subset of the former's features, so the same class is useful for both.
* <p>
* An index is described at two levels:
* <ol>
* <li>attributes of the index itself:
* <ul>
* <li>table name
* <li>index name
* <li>unique flag
* <li>primary flag
* <li>word flag
* </ul>
* <li>components of the index (1 or more):
* <ul>
* <li>field or column name
* <li>sort direction
* <li>abbreviation support
* <li>whether data type is character
* <li>whether case should be ignored for a character component
* </ul>
* </ol>
* <p>
* This class contains several static convenience methods:
* <ul>
* <li>{@link #normalizeUniqueIndexes}: Given a list of indexes for the
* same table, this method identifies the minimal set of unique indexes
* which define the least common denominator, unique indexes for a
* table. It also normalizes any index which contains all of the
* components of any of these by marking it as unique.
* <li>{@link #nonRedundantIndexes}: Given a list of indexes for the same
* table, this method identifies the sub-list of indexes which are not
* redundant with one another. For our purposes here, an index is
* considered redundant with another, if both indexes have the same
* number of components, where each component appears in the same order
* and has the same name as its counterpart.
* </ul>
*/
public final class P2JIndex
{
/** Logger */
private static final CentralLogger LOG = CentralLogger.get(P2JIndex.class);
/** Name of index */
private final String name;
/** Is this the primary index for its table? */
private boolean primary;
/** Is this a word index? */
private final boolean word;
/** List of index components */
private final ArrayList<P2JIndexComponent> components = new ArrayList<>();
/** Name of table with which index is associated */
private String table;
/** Does this index define a unique constraint for its table? */
private boolean unique;
/** Word table name (for word index)*/
private String wordTableName;
/** The legacy name of the index. */
private String legacyName;
/**
* Convenience constructor.
*
* @param table
* Name of the table with which the index is associated.
* @param name
* Index name.
* @param unique
* <code>true</code> if this index defines a unique constraint
* for its table, else <code>false</code>.
*/
public P2JIndex(String table, String name, boolean unique)
{
this(table, name, unique, false, false, null);
}
/**
* Convenience constructor.
*
* @param table
* Name of the table with which the index is associated.
* @param name
* Index name.
* @param unique
* <code>true</code> if this index defines a unique constraint
* for its table, else <code>false</code>.
* @param primary
* <code>true</code> if this index is the primary index for its
* table, else <code>false</code>.
* @param word
* <code>true</code> if this index is a word index, else
* <code>false</code>.
*/
public P2JIndex(String table, String name, boolean unique, boolean primary, boolean word)
{
this(table, name, unique, primary, word, null);
}
/**
* Convenience constructor.
*
* @param table
* Name of the table with which the index is associated.
* @param name
* Index name.
* @param unique
* <code>true</code> if this index defines a unique constraint
* for its table, else <code>false</code>.
* @param primary
* <code>true</code> if this index is the primary index for its
* table, else <code>false</code>.
* @param word
* <code>true</code> if this index is a word index, else
* <code>false</code>.
* @param wordTableName
* Word table name (only used for word index).
*/
public P2JIndex(String table,
String name,
boolean unique,
boolean primary,
boolean word,
String wordTableName)
{
this.table = table;
this.name = name;
this.unique = unique;
this.primary = primary;
this.word = word;
this.wordTableName = wordTableName;
// assume the name is the legacy name. If not, it will be overwritten using the setter
legacyName = name;
}
/**
* Copy constructor.
*
* @param index
* Instance to copy.
*/
public P2JIndex(P2JIndex index)
{
this(index.table, index.name, index.unique, index.primary, index.word, null);
ArrayList<P2JIndexComponent> components = index.components;
for (int i = 0; i < components.size(); i++)
{
P2JIndexComponent comp = components.get(i);
this.components.add(new P2JIndexComponent(comp));
}
compact();
}
/**
* Copy constructor with table name replacement.
*
* @param index
* Instance to copy.
* @param tableName
* table name replacement.
*/
public P2JIndex(P2JIndex index, String tableName)
{
this(tableName, index.name, index.unique, index.primary, index.word, null);
ArrayList<P2JIndexComponent> components = index.components;
for (int i = 0; i < components.size(); i++)
{
P2JIndexComponent comp = components.get(i);
this.components.add(new P2JIndexComponent(comp));
}
compact();
}
/**
* Given a list of indexes, identify the minimal set of unique indexes
* which define the least common denominator, unique constraints for a
* table. This will be the set of unique indexes which are least specific
* in terms of components. For instance, given three unique indexes with
* the following components:
* <ul>
* <li>Index 1: A
* <li>Index 2: A, B
* <li>Index 3: A, B, C
* </ul>
* Index 1 is the least common denominator, unique index of the group.
* This is because the table can contain no tuples where the value of
* columns B or C affect the uniqueness of the tuple. That is, for every
* unique value of A, there can be only one combination of B and C, since
* no other tuple can exist with the same value of A but different values
* of B or C.
* <p>
* Conversely, if Index 2 and Index 3 are not explicitly defined as being
* unique in a Progress schema, they are nevertheless <i>implicitly</i>
* unique because they include column A, which requires each tuple to
* contain a unique value for column A. Thus, as a useful side effect,
* this method normalizes such indexes by marking them unique, regardless
* of whether the unique flag was applied in the originating Progress
* schema.
*
* @param indexes
* A list of indexes for a common table.
* @param namespace
* Flag for data namespace to be processed. See int constants defined in {@code P2JIndexComponent}
* for valid values. If there is no match, an exception is thrown.
*
* @return The list of indexes which defines the minimal set of least common denominator, unique indexes
* for that index group. Order of the original list is not guaranteed to be preserved in the
* resulting subset list.
*
* @throws IllegalArgumentException
* if {@code indexes} contains index descriptors for different tables.
*/
public static List<P2JIndex> normalizeUniqueIndexes(List<P2JIndex> indexes, int namespace)
{
Map<Set<String>, P2JIndex> results = new HashMap<>();
int size = indexes.size();
// Iterate backwards through the full list to get the next index to
// test as a least common denominator candidate.
ListIterator<P2JIndex> iIter = indexes.listIterator(size);
while (iIter.hasPrevious())
{
P2JIndex iIndex = iIter.previous();
boolean minimal = iIndex.isUnique();
// Iterate backwards through the full list to test each other index
// in the list against the LCD candidate.
ListIterator<P2JIndex> jIter = indexes.listIterator(size);
while (jIter.hasPrevious())
{
P2JIndex jIndex = jIter.previous();
// Skip the same descriptor instance.
if (iIndex == jIndex)
{
continue;
}
// Assert that both test subjects are from the same table.
if (!iIndex.getTable().equals(jIndex.getTable()))
{
throw new IllegalArgumentException(
"Cannot normalize indexes from different tables; found '" + iIndex + "' and '" + jIndex);
}
// Is the LCD candidate a possible superset of another index?
if (iIndex.containsAll(jIndex, namespace))
{
// the normalization step: if the LCD candidate contains all components of a unique index, it
// is itself unique, by definition.
if (jIndex.isUnique())
{
iIndex.setUnique(true);
minimal = true;
}
// Candidate cannot be confirmed if:
// A) its components form a superset of another index' components; OR
// B) neither it nor the counterpart index is unique.
int iSize = iIndex.size();
int jSize = jIndex.size();
if ((iSize > jSize && jIndex.isUnique()) || (!minimal && !jIndex.isUnique()))
{
minimal = false;
}
// no need for further consideration if we've already ruled out LCDness
if (!minimal)
{
break;
}
}
}
// If we got through both loop levels and the candidate was
// confirmed, store it in a map, keyed by the candidate's name set.
if (minimal)
{
results.put(iIndex.getNameSet(null, namespace), iIndex);
}
}
return new ArrayList<>(results.values());
}
/**
* Given a list of index descriptors, assemble a list of those which are
* not redundant with one another. An index is considered redundant if it
* contains all components of another, and in the same order. For every
* group of redundant indexes, one (the first encountered in a first to
* last scan) is added to the resulting, non-redundant list.
*
* @param indexes
* List of indexes which contain possible redundancies.
*
* @return Sublist of <code>indexes</code> which contains no redundancies.
*/
public static List<P2JIndex> nonRedundantIndexes(List<P2JIndex> indexes)
{
List<P2JIndex> list = new ArrayList<>();
int size = indexes.size();
for (int i = 0; i < size; i++)
{
P2JIndex iIndex = indexes.get(i);
boolean redundant = false;
for (int j = 0; j < size; j++)
{
if (j == i)
{
continue;
}
P2JIndex jIndex = indexes.get(j);
int iSize = iIndex.size();
int jSize = jIndex.size();
if (iSize == jSize && iIndex.leadsWith(jIndex) && i > j)
{
redundant = true;
break;
}
}
if (!redundant)
{
list.add(iIndex);
}
}
return list;
}
/**
* Get table name associated with this index.
*
* @return Table name.
*/
public String getTable()
{
return table;
}
/**
* Get name of this index.
*
* @return Index name.
*/
public String getName()
{
return name;
}
/**
* Indicate whether this index is unique.
*
* @return <code>true</code> if unique, else <code>false</code>.
*/
public boolean isUnique()
{
return unique;
}
/**
* Indicate whether this index is the primary index for its table (only meaningful for Progress indexes).
*
* @return {@code true} if primary, else {@code false}.
*/
public boolean isPrimary()
{
return primary;
}
/**
* Indicate whether this index is the primary index for its table (only meaningful for Progress indexes).
*
* @param primary
* {@code true} for marking the index as primary, else {@code false}.
*/
public void setPrimary(boolean primary)
{
this.primary = primary;
}
/**
* Indicate whether this index is a word index (only meaningful for
* Progress indexes).
*
* @return <code>true</code> if a word index, else <code>false</code>.
*/
public boolean isWord()
{
return word;
}
/**
* Add an index component to the index definition. This overloaded variant will create a
* <strong>generic</strong> named component always ascending, non-character and non-abbreviated.
* <p>
* Because this method creates a generic component, all its namespaces are assigned to same value. Usage of
* this method is <strong>not</strong> encouraged because of the possible leaks of the data from one
* namespace to another.
*
* @param name
* The legacy name of the index field which defines this index component.
*
* @return Newly added {@code P2JIndexComponent} object.
*/
public P2JIndexComponent addComponent(String name)
{
return addComponent(name, null, null, false, false, false, false, false);
}
/**
* Add an index component to the index definition. This overloaded variant will create a
* <strong>generic</strong> named component always ascending, non-abbreviated.
* <p>
* Because this method creates a generic component, all its namespaces are assigned to same value. Usage of
* this method is <strong>not</strong> encouraged because of the possible leaks of the data from one
* namespace to another.
*
* @param name
* The name of the index field which defines this index component.
* @param ignoreCase
* {@code true} if this component represents a character field AND case is ignored when sorting
* index, else {@code false}.
*
* @return Newly added {@code P2JIndexComponent} object.
*/
public P2JIndexComponent addComponent(String name, boolean ignoreCase)
{
return addComponent(name, name, name, false, true, ignoreCase, false, false);
}
/**
* Add an index component to the index definition. An index component consists of a field name, sort
* direction, case sensitivity flag, abbreviation flag, and mandatory flag.
*
* @param legacy
* The legacy name (optional).
* @param property
* Property name (optional).
* @param column
* Column name (optional).
* @param descending
* {@code true} if this component sorts naturally in descending order; {@code false} to sort in
* ascending order.
* @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 this component is abbreviated, else {@code false}.
*
* @return Newly added {@code P2JIndexComponent} object.
*/
public P2JIndexComponent addComponent(String legacy,
String property,
String column,
boolean descending,
boolean isChar,
boolean ignoreCase,
boolean abbreviated)
{
return addComponent(legacy, property, column, descending, isChar, ignoreCase, abbreviated, false);
}
/**
* Add an index component to the index definition. An index component consists of a field name, sort
* direction, case sensitivity flag, abbreviation flag, and mandatory flag.
*
* @param legacy
* The legacy name (optional).
* @param property
* Property name (optional).
* @param column
* Column name (optional).
* @param descending
* {@code true} if this component sorts naturally in descending order; {@code false} to sort in
* ascending order.
* @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 this component is abbreviated, else {@code false}.
* @param isMandatory
* {@code true} if this component is mandatory (non-null), else {@code false}.
*
* @return Newly added {@code P2JIndexComponent} object.
*/
public P2JIndexComponent addComponent(String legacy,
String property,
String column,
boolean descending,
boolean isChar,
boolean ignoreCase,
boolean abbreviated,
boolean isMandatory)
{
return addComponent(legacy,
0,
property,
column,
descending,
isChar,
ignoreCase,
abbreviated,
isMandatory);
}
/**
* Add an index component to the index definition. An index component consists of a field name, sort
* direction, case sensitivity flag, abbreviation flag, and mandatory flag.
*
* @param legacy
* The legacy name (optional).
* @param extent
* Extent of the component (can be > 0 only for a word index component).
* @param property
* Property name (optional).
* @param column
* Column name (optional).
* @param descending
* {@code true} if this component sorts naturally in descending order; {@code false} to sort in
* ascending order.
* @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 this component is abbreviated, else {@code false}.
* @param isMandatory
* {@code true} if this component is mandatory (non-null), else {@code false}.
*
* @return Newly added {@code P2JIndexComponent} object.
*/
public P2JIndexComponent addComponent(String legacy,
int extent,
String property,
String column,
boolean descending,
boolean isChar,
boolean ignoreCase,
boolean abbreviated,
boolean isMandatory)
{
P2JIndexComponent comp = new P2JIndexComponent(
legacy, property, column, extent, descending, isChar, ignoreCase, abbreviated, isMandatory);
components.add(comp);
return comp;
}
/**
* Get the number of components in this index.
*
* @return Index size.
*/
public int size()
{
return components.size();
}
/**
* Reclaim a bit of memory once all components have been added and this object will no longer
* be changed.
*/
public void compact()
{
((ArrayList<P2JIndexComponent>) components).trimToSize();
}
/**
* Get the index component at the specified position.
*
* @param position
* 0-based position of the desired index component.
*
* @return Index component at <code>position</code>.
*
* @throws ArrayIndexOutOfBoundsException
* if <code>position</code> is invalid.
*/
public P2JIndexComponent getComponent(int position)
{
return components.get(position);
}
/**
* Get all {@link P2JIndexComponent}s in this index.
*
* @return Array of components.
*/
public ArrayList<P2JIndexComponent> components()
{
return components;
}
/**
* Get an iterator on all {@link P2JIndexComponent}s in this index.
*
* @return Component iterator.
*/
public Iterator<P2JIndexComponent> componentsIter()
{
return components.iterator();
}
/**
* Get an iterator on the {@link P2JIndexComponent}s in this index, optionally omitting a
* trailing primary key component, if present and if the index is non-unique.
*
* @param omitTrailingPK
* <code>true</code> to omit the trailing primary key component, if present and the
* index is non-unique; else <code>false</code>.
*
* @return Component iterator.
*/
public Iterator<P2JIndexComponent> components(final boolean omitTrailingPK)
{
if (unique || !omitTrailingPK)
{
// return regular iterator if index is explicitly unique or we've been told not to omit
// a trailing primary key component
return components.iterator();
}
// return a custom iterator which ignores a trailing primary key component
return new Iterator<P2JIndexComponent>()
{
private final Iterator<P2JIndexComponent> iter = components.iterator();
private P2JIndexComponent next = null;
public boolean hasNext()
{
if (iter.hasNext() && next == null)
{
next = iter.next();
// ignore the last component if it's the primary key
if (!iter.hasNext() && DatabaseManager.PRIMARY_KEY.equals(next.getPropertyName()))
{
next = null;
}
}
return (next != null);
}
public P2JIndexComponent next()
{
if (!hasNext())
{
throw new NoSuchElementException();
}
P2JIndexComponent comp = next;
next = null;
return comp;
}
public void remove()
{
// cannot remove components with this iterator
throw new UnsupportedOperationException();
}
};
}
/**
* Return up to <code>size</code> leading components of this index. If
* <code>size</code> is larger than the number of components in the index,
* all components are returned. The order of components is preserved in
* the returned list.
*
* @param size
* Number of leading components to return.
*
* @return List view of the first <code>size</code> components of the
* index.
*/
public List<P2JIndexComponent> getLeadingComponents(int size)
{
size = Math.min(size, components.size());
return components.subList(0, size);
}
/**
* Get order by clause for given index of database.
*
* @param dmoAlias
* Alias qualifier which represents DMO in sort phrase.
* @param namespace
* The namespace of the components to be used when constructing the returned {@code String}.
* For possible values see the {@link P2JIndexComponent} integer namespace constants.
*
* @return order by clause.
*/
public String getOrderByClause(String dmoAlias, int namespace)
{
StringBuilder result = new StringBuilder();
for (int i = 0; i < components.size(); i++)
{
P2JIndexComponent nextComp = components.get(i);
if (result.length() > 0)
{
result.append(", ");
}
String field = nextComp.getName(namespace);
result.append(dmoAlias);
result.append(".");
result.append(field);
result.append(" ");
result.append(nextComp.isDescending() ? "desc" : "asc");
}
return result.toString();
}
/**
* Test whether this index leads with components of the same name as the specified index. For the purpose
* of this test, only the names of the components are considered; the sort direction and word flags are
* ignored. The order of the index components is significant.
*
* @param index
* Index with which to compare this index' components.
*
* @return {@code true} if and only if this index contains <b>all</b> of the components of {@code index}
* as its leading components, and these components appear in the same order in both indexes;
* {@code false} otherwise.
*/
public boolean leadsWith(P2JIndex index)
{
int size = index.size();
if (size > components.size())
{
return false;
}
for (int i = 0; i < size; i++)
{
P2JIndexComponent next = index.components.get(i);
P2JIndexComponent local = components.get(i);
if (!next.sameAs(local, true))
{
return false;
}
}
return true;
}
/**
* Return the set of index component names for this index. Iterations over this set will return the names
* in the order in which they were added to the index.
*
* @param dialect
* Optional P2J database dialect.
* <ul>
* <li>If provided, index components of text type will be processed as necessary to produce an
* expression which will right trim whitespace, properly handle case insensitivity and
* sorting order (descending) as necessary. This may mean wrapping the property name in
* various functions, substituting the name with a computed column name, or whatever is
* appropriate for the particular dialect.
* <li>Otherwise only the plain list of components names is returned.
* </ul>
* @param namespace
* Flag for data namespace to be processed. See int constants defined in {@code P2JIndexComponent}
* for valid values. If there is no match, an exception is thrown.
*
* @return Set of index component names.
*/
public LinkedHashSet<String> getNameSet(Dialect dialect, int namespace)
{
LinkedHashSet<String> set = new LinkedHashSet<>();
for (int i = 0; i < components.size(); i++)
{
P2JIndexComponent next = components.get(i);
String name = next.getName(namespace);
if (dialect != null)
{
if (next.isCharType())
{
// for char type wrap it into functions according to dialect
boolean ignoreCase = next.isIgnoreCase();
name = dialect.getProcessedCharacterColumnName(name, ignoreCase);
}
// check sorting order, this applies for all BDTs, not only char
if (next.isDescending())
{
name += " desc";
}
}
set.add(name);
}
return set;
}
/**
* Obtain the legacy name of the index.
*
* @return the legacy name of the index. If not expressly set, this information might not be accurate.
*/
public String getLegacyName()
{
return legacyName;
}
/**
* Sets the legacy name of the index, when available.
*
* @param legacyName
* The legacy name of the index.
*/
public void setLegacyName(String legacyName)
{
this.legacyName = legacyName;
}
/**
* Implement a very loose test for equivalence, whereby <code>o</code> is
* considered equivalent to this object as long as both instances contain
* exactly the same set of component names, in the same order. Table name,
* index name, and component sort direction and abbreviation support are
* ignored for this test.
*
* @param o
* An object instance, presumably a <code>P2JIndex</code> object,
* to test for equivalence with this object.
*
* @return <code>true</code> if <code>o</code> tests as equivalent to
* this object, using the criteria specified above, else
* <code>false</code>.
*/
public boolean equals(Object o)
{
if (!(o instanceof P2JIndex))
{
return false;
}
P2JIndex idx = (P2JIndex) o;
// Can't use LinkedHashSet.equals() to compare name sets returned by getNameSet(), because that equals()
// implementation does not take element order into account. Do the comparison manually.
ArrayList<P2JIndexComponent> c1 = this.components;
ArrayList<P2JIndexComponent> c2 = idx.components;
if (c1.size() != c2.size())
{
return false;
}
for (int i = 0; i < c1.size(); i++)
{
P2JIndexComponent comp1 = c1.get(i);
P2JIndexComponent comp2 = c2.get(i);
if (!comp1.equals(comp2))
{
return false;
}
}
return true;
}
/**
* Overridden implementation to ensure consistency with our specialized
* {@link #equals} method implementation. Note that only the index
* component names are taken into account; all other index and index
* component data is ignored for purposes of this calculation.
*
* @return Hash code of this index.
*/
public int hashCode()
{
int result = 17;
for (int i = 0; i < components.size(); i++)
{
P2JIndexComponent next = components.get(i);
result = 37 * result + next.hashCode();
}
return result;
}
/**
* 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()
{
StringBuilder buf = new StringBuilder();
if (table != null)
{
buf.append(table);
buf.append('.');
}
buf.append(name);
buf.append(" [");
for (int i = 0; i < components.size(); i++)
{
P2JIndexComponent component = components.get(i);
if (i > 0)
{
buf.append(", ");
}
buf.append(component);
}
buf.append(']');
if (isUnique())
{
buf.append('*');
}
if (isPrimary())
{
buf.append('!');
}
if (isWord())
{
buf.append('%');
}
return buf.toString();
}
/**
* Get word table names by dialect
*
* @return Word table name.
*/
public String getWordTableName()
{
return wordTableName;
}
/**
* Set word table names
*
* @param wordTableName
* Word table names by dialect.
*/
public void setWordTableName(String wordTableName)
{
this.wordTableName = wordTableName;
}
/**
* Set the table with which this index is associated.
*
* @param table
* Name of table.
*/
void setTable(String table)
{
this.table = table;
}
/**
* Set the unique flag within this index.
*
* @param unique
* <code>true</code> to indicate that this index is unique;
* <code>false</code> to indicate it is not.
*/
void setUnique(boolean unique)
{
this.unique = unique;
}
/**
* Indicate whether this index contains all of the components of the specified index. Only index component
* names are considered, not any other properties of each component.
*
* @param index
* Index whose components are checked against those in this index.
* @param namespace
* Flag for data namespace to be processed. See int constants defined in {@code P2JIndexComponent}
* for valid values. If there is no match, an exception is thrown.
*
* @return {@code true} if this object contains all components of {@code index}; else {@code false}.
*/
private boolean containsAll(P2JIndex index, int namespace)
{
Set<String> localSet = getNameSet(null, namespace);
Set<String> otherSet = index.getNameSet(null, namespace);
return localSet.containsAll(otherSet);
}
/**
* Test harness. Creates multiple instance of this class and tests that
* minimal unique indexes can be determined, and that redundant indexes
* can be identified and culled from a list.
*
* @param args
* Not used.
*/
public static void main(String[] args)
{
try
{
P2JIndex index;
List<P2JIndex> list = new ArrayList<>();
index = new P2JIndex("Table 1", "ABCD", true, false, false);
index.addComponent("A");
index.addComponent("B");
index.addComponent("C");
index.addComponent("D");
list.add(index);
index = new P2JIndex("Table 1", "ACB", true, false, false);
index.addComponent("A");
index.addComponent("C");
index.addComponent("B");
list.add(index);
index = new P2JIndex("Table 1", "BCA", true, false, false);
index.addComponent("B");
index.addComponent("C");
index.addComponent("A");
list.add(index);
index = new P2JIndex("Table 1", "DE", true, false, false);
index.addComponent("D");
index.addComponent("E");
list.add(index);
index = new P2JIndex("Table 1", "BAC", true, false, false);
index.addComponent("B");
index.addComponent("A");
index.addComponent("C");
list.add(index);
index = new P2JIndex("Table 1", "BC", true, false, false);
index.addComponent("B");
index.addComponent("C");
list.add(index);
index = new P2JIndex("Table 1", "AB", true, false, false);
index.addComponent("A");
index.addComponent("B");
list.add(index);
index = new P2JIndex("Table 1", "F1", false, false, false);
index.addComponent("F");
list.add(index);
index = new P2JIndex("Table 1", "F2", true, true, false);
index.addComponent("F");
list.add(index);
index = new P2JIndex("Table 1", "ED1", false, false, false);
index.addComponent("E");
index.addComponent("D");
list.add(index);
index = new P2JIndex("Table 1", "ED2", false, false, false);
index.addComponent("E");
index.addComponent("D");
list.add(index);
index = new P2JIndex("Table 1", "ED3", true, false, false);
index.addComponent("E");
index.addComponent("D");
list.add(index);
index = new P2JIndex("Table 1", "EG", false, false, false);
index.addComponent("E");
index.addComponent("G", true);
list.add(index);
index = new P2JIndex("Table 1", "CAB", true, false, false);
index.addComponent("C");
index.addComponent("A");
index.addComponent("B");
list.add(index);
System.out.println("PRE-NORMALIZED (" + list.size() + ")");
System.out.println("-------------------------------");
System.out.println(list);
System.out.println();
List<P2JIndex> minimalUnique = P2JIndex.normalizeUniqueIndexes(list, P2JIndexComponent.LEGACY);
List<P2JIndex> nonRedundant = P2JIndex.nonRedundantIndexes(list);
System.out.println("NON-REDUNDANT (" + nonRedundant.size() + ")");
System.out.println("-------------------------------");
System.out.println(nonRedundant);
System.out.println();
System.out.println("MINIMAL UNIQUE (" + minimalUnique.size() + ")");
System.out.println("-------------------------------");
System.out.println(minimalUnique);
System.out.println();
Set<P2JIndex> remaining = new HashSet<>(nonRedundant);
remaining.removeAll(minimalUnique);
System.out.println("REMAINING (" + remaining.size() + ")");
System.out.println("-------------------------------");
System.out.println(remaining);
System.out.println();
System.out.println("NORMALIZED (" + list.size() + ")");
System.out.println("-------------------------------");
System.out.println(list);
System.out.println();
}
catch (Exception exc)
{
LOG.severe("", exc);
}
}
}