DMOSignatureHelper.java
/*
** Module : DMOSignatureHelper.java
** Abstract : A collection of methods used for handling DMO signatures.
**
** Copyright (c) 2020-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
** 001 AIL 20201125 First revision.
** 20201126 Added unique constraint encoding and checking.
** 20201210 Added explicit signature for DMO operations in which the property names matter.
** 20201214 Fixed unique constraint checking.
** 20201216 Added support for loose-copy-mode.
** CA 20210508 Added a signature for the DMO schema, used by shared buffer/temp-table match.
** AIL 20210510 Used type encoding for schema signaure; made typeMapping thread-safe.
** IAS 20210515 Fixed potential NPE in the buildSchemaSignature() method (could happen
** for the denormalized extent fields)
** OM 20220727 FieldId and PropertyId are different for denormalized extent fields.
** CA 20220606 Fixed a bug in uniqueConstraintsMatch, wrong index was used for dstConstraints.
** HC 20230118 Eliminated some of the uses of String.toUpperCase and/or String.toLowerCase
** for performance.
** 002 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
** 003 SR 20230704 Added sqlSignatured used for copy-temp-table.
** 004 DDF 20250131 Removed no-undo status from the schema signature.
** DDF 20250210 Added back the no-undo status and added simpleSchemaSignature to DmoSignature
** constructor.
*/
/*
** 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.orm;
import java.util.*;
import java.util.logging.*;
import java.util.regex.Pattern;
import com.goldencode.p2j.persist.annotation.*;
import com.goldencode.p2j.util.*;
import com.goldencode.p2j.util.logging.*;
/**
* Compute a string based signature for a DMO, which contains information regarding the
* type of each field, extents and eventually some restrictions like mandatory or unique.
* In order to have the prefix-code property for each generated string, the following
* standards should be respected:
* - The properties are represented in the order they are provided.
* - A data type representation should be a single upper case character.
* - In case the field has an extent, the length of the extent will follow the data type
* representation as a number (no matter the number of digits)
* - At the end of the representation of one property, there will be a sequence of lower-case
* characters showing which restrictions are imposed on this property (mandatory). The
* order of these is important.
* This means that one property will have the following format:
* UpperCaseType ExtentDigit* LowerCaseConstraint*
* At the end of the property representation, we will have unique constraint representations,
* which is sequence of delimited enumerations of properties id.
*/
public class DMOSignatureHelper
{
/**
* A map statically initialized with the classic JVM data type single-char representation.
* This can also contain other mappings created at the runtime.
*/
private static final Map<Class<? extends BaseDataType>, Character> typeMapping;
/** The character used for representing a mandatory property */
private static final char CHAR_MANDANTORY = 'm';
/**
* The character used as delimiter between unique constraints which are encoded
* as a sequence of property ids
*/
private static final char CHAR_UNIQUE_CONSTRAINT_DELIMITER = '|';
/** The character used as delimiter between the property ids in the same group (ex: unique constraint) */
private static final char CHAR_PROPERTY_ID_DELIMITER = ',';
/** The character used before appending a property name to the signature. */
private static final char PROPERTY_NAME_DELIMITER_START = '(';
/** The character used after appending a property name to the signature. */
private static final char PROPERTY_NAME_DELIMITER_FINISH = ')';
/** A string based delimiter (regex friendly) used for splitting a signature into separate constraints */
private static final String FORMATTED_CONSTRAINT_DELIMITER;
/**
* A string based delimiter (regex friendly) used for splitting a propriety id group
* into separate property ids
*/
private static final String FORMATTED_PROPERTY_DELIMITER;
/** Logger */
private static final CentralLogger LOG = CentralLogger.get(DMOSignatureHelper.class.getName());
/** A helper to select a suitable unused character for a specified data type. */
private static final TypeIdentifierProvider findSuitableCandidate;
/** A helper to select any unused character for a specified data type. */
private static final TypeIdentifierProvider findAnyCandidate;
/** Static initialization of the type mapping */
static
{
typeMapping = new IdentityHashMap<>();
typeMapping.put(character.class, 'C');
typeMapping.put(decimal.class, 'F');
typeMapping.put(integer.class, 'I');
typeMapping.put(int64.class, 'J');
typeMapping.put(logical.class, 'Z');
FORMATTED_CONSTRAINT_DELIMITER = Pattern.quote(String.valueOf(CHAR_UNIQUE_CONSTRAINT_DELIMITER));
FORMATTED_PROPERTY_DELIMITER = Pattern.quote(String.valueOf(CHAR_PROPERTY_ID_DELIMITER));
findSuitableCandidate = (type) ->
{
synchronized (typeMapping)
{
Set<Character> values = new HashSet<Character>(typeMapping.values());
for (char raw : type.getName().toCharArray())
{
char candidate = Character.toUpperCase(raw);
if (isType(candidate) && !values.contains(candidate))
{
typeMapping.put(type, candidate);
return candidate;
}
}
return null;
}
};
findAnyCandidate = (type) ->
{
synchronized (typeMapping)
{
Set<Character> values = new HashSet<Character>(typeMapping.values());
for (char candidate = 'A'; candidate <= 'Z'; candidate++)
{
if (!values.contains(candidate))
{
typeMapping.put(type, candidate);
return candidate;
}
}
return null;
}
};
}
/**
* This builds a signature which represents a DMO. The DMOs which are compatible under certain
* circumstances (structural, by property name etc.) should compatible string signatures.
*
* @param meta
* The DMO meta instance. WARNING: this is called from the {@link DmoMeta#DmoMeta c'tor}.
*
* @return A DMO signature representing the a DMO having the specified properties and unique constraints.
*/
static DmoSignature buildSignature(DmoMeta meta)
{
// build a collection of lists containing properties part of the same unique constraint
// this will be used when encoding unique constraints in the signature
List<List<Property>> uniqueConstraints = meta.getUniqueConstraintsAsProps();
IteratorProvider<Property> itProvider = () -> meta.getFields(false);
Map<Integer, Integer> explicitIdToPos = new HashMap<>();
String quickSignature = buildQuickSignature(itProvider, uniqueConstraints, meta.tempTable);
String explicitSignature = buildExplicitSignature(itProvider,
uniqueConstraints,
meta.tempTable,
explicitIdToPos);
// The simpleSchemaSignature relies on the schemaSignature, the number of characters
// used to add the no-undo flag and the separator should be removed from the start of
// the schemaSignature to get the simple version (no options, e.g. no-undo).
String schemaSignature = buildSchemaSignature(meta);
String simpleSchemaSignature = schemaSignature.substring(2);
String sqlSignature = buildSqlSignature(itProvider,
uniqueConstraints,
meta.tempTable,
explicitIdToPos);
return new DmoSignature(schemaSignature,
simpleSchemaSignature,
quickSignature,
explicitSignature,
explicitIdToPos,
sqlSignature);
}
/**
* Check if two signatures are compatible from properties name order point of view. This works only
* if both the source and destination have explicit signatures - as the property names are relevant here.
*
* @param srcSignature
* The source signature which should be checked.
* @param dstSignature
* The destination signature which should be checked.
*
* @return {@code true} if the DMO signatures shuffled the property names in the explicit signatures
* in the same order. This means that, if the explicit signature match and this returns
* {@code true}, it means that they have the same order of the properties name/types/extents/
* constraints etc. Otherwise, they only have the same property names but in different order,
* case in which this returns {@code false}.
*/
public static boolean exactPropertyOrder(DmoSignature srcSignature, DmoSignature dstSignature)
{
if (srcSignature == null || dstSignature == null)
{
return false;
}
String srcOrder = srcSignature.getIdToPosAsString();
String dstOrder = dstSignature.getIdToPosAsString();
if (srcOrder == null || dstOrder == null)
{
return false;
}
return srcOrder.equals(dstOrder);
}
/**
* Check if the source and destination are compatible for a fast buffer-copy.
*
* @param src
* The signature of the source DMO.
* @param dst
* The signature of the destination DMO.
*
* @return {@code true} if the explicit signatures are equal, so they have the same property names
* (not always in the same order).
*/
public static boolean validBufferCopy(DmoSignature src, DmoSignature dst)
{
if (src == null || dst == null)
{
return false;
}
String srcSignature = src.getExplicitSignature();
String dstSignature = dst.getExplicitSignature();
if (srcSignature == null || dstSignature == null)
{
return false;
}
return srcSignature.equals(dstSignature);
}
/**
* Check if the destination signature is less restrictive than the source signature. This checks
* each field for type and extent compatibility; afterwards it checks if all destination constraints
* can be validated by source's constraints. If the signatures are compatible in this way,
* we can allow a fast copy. Note that looseCopy mode will relax the constraints regarding property
* order; all we care about are the unique constraints.
*
* @param src
* The signature of the source DMO.
* @param dst
* The signature of the destination DMO.
* @param looseCopy
* Flag which indicates if the DMO signatures are compatible in loose-copy-mode.
*
* @return {@code true} if the signatures are compatible.
*/
public static boolean validTempTableBulkCopy(DmoSignature src,
DmoSignature dst,
boolean looseCopy)
{
if (src == null || dst == null)
{
return false;
}
String srcSignature = src.getQuickSignature();
String dstSignature = dst.getQuickSignature();
if (srcSignature == null || dstSignature == null)
{
return false;
}
if (srcSignature.equals(dstSignature))
{
return true;
}
// fall back to an in-detail analysis of the signatures
String[] srcChunks = srcSignature.split(FORMATTED_CONSTRAINT_DELIMITER, 2);
String[] dstChunks = dstSignature.split(FORMATTED_CONSTRAINT_DELIMITER, 2);
// verify the properties first: split after the unique constraint delimiter and get the first chunk
// in loose-copy-mode, this step is skipped
if (!looseCopy && !propertiesMatch(srcChunks[0], dstChunks[0]))
{
return false;
}
// verify unique constraints after: take the second chuck and match the unique constraints
// both have unique constraints so resolve through special method
if (srcChunks.length > 1 && dstChunks.length > 1 && !uniqueConstraintsMatch(srcChunks[1], dstChunks[1]))
{
return false;
}
// the destination has unique constraints, but the source doesn't, so this is not a safe copy
if (srcChunks.length <= 1 && dstChunks.length > 1)
{
return false;
}
return true;
}
/**
* Build a signature to match the DMO schema with another one.
* <p>
* The rules use the field's data-type in the order they are defined, without extent or mandatory.
* <p>
* For indexes, the signature will contain each index, in order of definition, and each will have:
* the index type (unique, primary, word), followed by the components (which are the field index in the
* definition), sort order, and field's case-sensitivity, in case of character.
*
* @param meta
* The DMO meta.
*
* @return The schema signature.
*/
private static String buildSchemaSignature(DmoMeta meta)
{
char sep = '|';
char sep2 = '#';
Map<String, Integer> l2i = new HashMap<>();
StringBuilder sig = new StringBuilder();
sig.append(meta.noUndo ? 'N' : 'U').append(sep); // no-undo status
sig.append(meta.legacyTable.toLowerCase()).append(sep); // always lowercase
sig.append(meta.propsByName.size()).append(sep2);
int idx = 0;
for (Property field : meta.propsByName.values())
{
if (field instanceof ReservedProperty)
{
continue;
}
// field extent is not used, just data type
Character ch = getTypeEncoding((Class<? extends BaseDataType>) field._fwdType);
sig.append(ch != null ? ch : field._fwdType.getSimpleName()).append(sep); // data-type
l2i.put(field.legacyLower, idx); // always lowercase
idx = idx + 1;
}
sig.append(sep2);
for (Index index : meta.indexes)
{
sig.append(index.legacy().toLowerCase()).append(sep); // always lowercase
// index-tyoe: WORD, PRIMARY or UNIQUE
sig.append(index.word() ? "W" : "")
.append(index.primary() ? "P" : "")
.append(index.unique() ? "U" : "")
.append(sep);
for (IndexComponent ixc : index.components())
{
String fname = ixc.legacy().toLowerCase(); // always lowercase
sig.append(l2i.get(fname)).append(sep); // field index.
Property field = meta.propsByName.get(ixc.name());
if (field == null)
{
field = meta.propsByName.values().stream().
filter(p-> fname.equals(p.legacy)).findFirst().orElse(null);
}
if (field != null && field.caseSensitive)
{
sig.append("CS").append(sep); // field case-sensitivity is used only if there is an index on it
}
sig.append(ixc.descending() ? 'D' : 'A').append(sep2); // sort order
}
}
return sig.toString();
}
/**
* This builds a string which structurally represents a DMO. The DMOs which are compatible,
* from strcture's point of view, should have the same signature. This is useful for quickly
* checking compatibility (example: copy-temp-table).
*
* @param it
* An iterator to the whole collection of properties for the processed DMO.
* @param uniqueConstraints
* A list of multiple properties which together should be unique.
* @param tempTable
* A flag to indicate that that the properties are for a temp-table.
*
* @return A string representing the signature for a DMO having the specified properties.
*/
private static String buildQuickSignature(IteratorProvider<Property> it,
List<List<Property>> uniqueConstraints,
boolean tempTable)
{
StringBuilder sb = new StringBuilder();
if (!buildPropertySignature(it, tempTable, false, null, sb, false))
{
return null;
}
if (!buildUniqueConstraintsSignature(uniqueConstraints, null, sb))
{
return null;
}
return sb.toString();
}
private static String buildExplicitSignature(IteratorProvider<Property> it,
List<List<Property>> uniqueConstraints,
boolean tempTable,
Map<Integer, Integer> idToPos)
{
StringBuilder sb = new StringBuilder();
if (!buildPropertySignature(it, tempTable, true, idToPos, sb, false))
{
return null;
}
if (!buildUniqueConstraintsSignature(uniqueConstraints, idToPos, sb))
{
return null;
}
return sb.toString();
}
private static String buildSqlSignature(IteratorProvider<Property> it,
List<List<Property>> uniqueConstraints,
boolean tempTable,
Map<Integer, Integer> idToPos)
{
StringBuilder sb = new StringBuilder();
if (!buildPropertySignature(it, tempTable, true, idToPos, sb, true))
{
return null;
}
if (!buildUniqueConstraintsSignature(uniqueConstraints, idToPos, sb))
{
return null;
}
return sb.toString();
}
/**
* Check if two signatures are compatible in terms of properties data types, extent and field-level
* constraints.
*
* @param src
* The signature of the source DMO.
* @param dst
* The signature of the destination DMO.
*
* @return {@code true} if the signatures are compatible.
*/
private static boolean propertiesMatch(String src, String dst)
{
if (src == null || dst == null)
{
return false;
}
if (src.equals(dst))
{
return true;
}
char[] srcChars = src.toCharArray();
char[] dstChars = dst.toCharArray();
int srcLength = srcChars.length;
int dstLength = dstChars.length;
int i, j;
for (i = 0, j = 0; i < srcLength && j < dstLength;)
{
if (!isType(srcChars[i]) || !isType(dstChars[j]))
{
if (LOG.isLoggable(Level.SEVERE))
{
LOG.log(Level.SEVERE, "DMO signatures are malformed; "
+ "some tokens are not parsed when validating copy");
}
return false;
}
// check type
if (srcChars[i++] != dstChars[j++])
{
return false;
}
// check extent
while (i < srcLength && j < dstLength && isDigit(srcChars[i]) && isDigit(dstChars[j]))
{
if (srcChars[i++] != dstChars[j++])
{
return false;
}
}
if ((i < srcLength && isDigit(srcChars[i])) || (j < dstLength && isDigit(dstChars[j])))
{
return false;
}
// check constraints
while (j < dstLength && isConstraint(dstChars[j]))
{
while (i < srcLength && isConstraint(srcChars[i]) && srcChars[i] != dstChars[j])
{
i++;
}
if (i >= srcLength || !isConstraint(srcChars[i]))
{
return false;
}
// the source has a constraint which matches the current destination constraint
i++;
j++;
}
}
return i == srcLength && j == dstLength;
}
/**
* Check if the second set of unique constraints (encoded in the destination signature) can be implied
* by the first set of unique constraints. This will happen if, for each unique constraint in the
* destination, there is a unique constraint in the source which is stronger (having such constraint
* will automatically imply that the destination unique constraint is satisfied).
*
* @param src
* The signature of the source's unique constraints.
* @param dst
* The signature of the destination's unique constraints.
*
* @return {@code true} if the constraints in the destination are satisfied by the unique constraints
* in the source.
*/
private static boolean uniqueConstraintsMatch(String src, String dst)
{
if (src == null || dst == null)
{
return false;
}
if (src.equals(dst))
{
return true;
}
String[] srcConstraints = src.split(FORMATTED_CONSTRAINT_DELIMITER);
String[] dstConstraints = dst.split(FORMATTED_CONSTRAINT_DELIMITER);
int srcLength = srcConstraints.length;
int dstLength = dstConstraints.length;
for (int j = 0; j < dstLength; j++)
{
boolean foundMatch = false;
for (int i = 0; i < srcLength; i++)
{
if (strongerUniqueConstraint(srcConstraints[i], dstConstraints[j]))
{
foundMatch = true;
break;
}
}
if (!foundMatch)
{
return false;
}
}
return true;
}
/**
* Check if having the first unique constraint will automatically determine that the second unique
* constraint will be satisfied. This happens when all fields in the first unique constraint are
* included in the second one. From signature's point of view, this will be resolved by
* finding all encoded ids in the first unique constraint in the second one. Having same signature,
* will automatically mean that the first unique constraint is stronger.
*
* @param firstConstraint
* The first unique constraint signature which is checked.
* @param secondConstraint
* The second unique constraint signature which is checked.
*
* @return {@code true} if the first unique constraint is included in the second, which means
* that is stronger and can imply the second unique constraint.
*/
private static boolean strongerUniqueConstraint(String firstConstraint, String secondConstraint)
{
if (firstConstraint == null || secondConstraint == null)
{
return false;
}
if (firstConstraint.equals(secondConstraint))
{
return true;
}
String[] ids1 = firstConstraint.split(FORMATTED_PROPERTY_DELIMITER);
String[] ids2 = secondConstraint.split(FORMATTED_PROPERTY_DELIMITER);
int length1 = ids1.length;
int length2 = ids2.length;
// we make use of the fact that the property ids are sorted in a strictly ascending way
int i, j;
for (i = 0, j = 0; i < length1 && j < length2; j++)
{
if (ids1[i].equals(ids2[j])) // we found a constraint match for property i
{
i++;
}
}
// first constraint is stronger, if we could find all its properties in the second constraint
return i == length1;
}
/**
* This is responsible for encoding the properties and append them to the provided string builder.
* The encoding is a non-delimited sequence of property encodings. Each property encoding is
* formed out of: data type encoding, extent encoding and field-level constraints.
*
* @param itProvider
* An iterator to a collection of properties which should be encoded.
* @param tempTable
* A flag indicating that the signature is created for a temp-table specific DMO.
* @param explicit
* A flag indicating that this property signature is done for an explicit signature.
* This means that the property names should be included, and the property ordered.
* @param idToPos
* A valid map which will be filled with the correspondence between property id and
* their order in the generated signature. This is required only if the explicit
* flag is set on true - as only explicit signatures have a different ordering of the props.
* @param sb
* The string builder onto which the signature should be appended.
* @param sqlName
* A flag indicating that the signature should also include the SQL name of the fields.
*
* @return {@code true} if the properties could be encoded in a signature.
*/
private static boolean buildPropertySignature(IteratorProvider<Property> itProvider,
boolean tempTable,
boolean explicit,
Map<Integer, Integer> idToPos,
StringBuilder sb,
boolean sqlName)
{
Map<String, String> nameToSignature = new LinkedHashMap<>();
Iterator<Property> it = itProvider.provide();
while (it.hasNext())
{
StringBuilder sbProp = new StringBuilder();
Property prop = it.next();
Class<? extends BaseDataType> type = (Class<? extends BaseDataType>) prop._fwdType;
Character ch = getTypeEncoding(type);
if (ch == null)
{
return false;
}
sbProp.append(ch);
int extent = prop.extent;
if (extent > 0)
{
sbProp.append(extent);
}
// for explicit signatures, we need to add the name of the property
if (explicit && !processFieldName(prop, tempTable, sbProp, sqlName))
{
return false;
}
if (!processFieldConstraints(prop, tempTable, sbProp))
{
return false;
}
nameToSignature.put(prop.legacy, sbProp.toString());
}
if (!explicit)
{
// this is a quick signature: append the properties in the default order
for (String signature : nameToSignature.values())
{
sb.append(signature);
}
return true;
}
// this is an explicit signature: append the properties sorted by the name
Map<String, Integer> name2Pos = new HashMap<>();
int idx = 1;
for (Map.Entry<String, String> entry : new TreeMap<>(nameToSignature).entrySet())
{
String name = entry.getKey();
String signature = entry.getValue();
sb.append(signature);
name2Pos.put(name, idx++);
}
// create a positioning mapping as the fields are not in the same order in the
// signature as in a record
it = itProvider.provide();
while (it.hasNext())
{
Property prop = it.next();
idToPos.put(prop.id, name2Pos.get(prop.legacy));
}
return true;
}
/**
* This is responsible for encoding the unique constraints and append them to the provided string builder.
* The encoding is a lexicographically sorted sequence of unique constraint signatures, delimited by a
* defined character. Each unique constraint signature is a sorted sequence of property ids, delimited
* by a fixed character.
*
* @param uniqueConstraints
* A list of unique constrains (each being a list of properties).
* @param idToPos
* A mapping from the property ids to their position in the signature. This should be
* {@code null} for quick signatures (as their ids match the position}, which this should be
* not {@code null} null in explicit signatures, where the order doesn't always match.
* @param sb
* The string builder onto which the signature should be appended.
*
* @return {@code true} if the unique constraints could be converted to a signature
*/
private static boolean buildUniqueConstraintsSignature(List<List<Property>> uniqueConstraints,
Map<Integer, Integer> idToPos,
StringBuilder sb)
{
if (uniqueConstraints == null || uniqueConstraints.isEmpty())
{
return true;
}
List<String> constraintSignatures = new ArrayList<>();
for (List<Property> props : uniqueConstraints)
{
if (props == null || props.isEmpty())
{
continue;
}
StringBuilder signature = new StringBuilder();
List<Integer> ids = new ArrayList<>(props.size());
props.forEach((prop) -> ids.add(idToPos == null ? prop.id : idToPos.get(prop.id)));
// the ids should be sorted in order to do exact matches more easily (string matching)
Collections.sort(ids);
for (int i = 0; i < ids.size() - 1; i++)
{
signature.append(ids.get(i)).append(CHAR_PROPERTY_ID_DELIMITER);
}
signature.append(ids.get(ids.size() - 1));
constraintSignatures.add(signature.toString());
}
// the constraints should be sorted in order to do exact matches more easily (string matching)
Collections.sort(constraintSignatures);
for (int i = 0; i < constraintSignatures.size(); i++)
{
sb.append(CHAR_UNIQUE_CONSTRAINT_DELIMITER).append(constraintSignatures.get(i));
}
return true;
}
/**
* Provide a single character encoding for a certain base data type.
*
* @param type
* The base data type for which the encoding is to be retrieved.
* @return A character to be used when encoding a base data type into a signature.
*/
private static Character getTypeEncoding(Class<? extends BaseDataType> type)
{
Character ch;
synchronized (typeMapping)
{
ch = typeMapping.get(type);
}
if (ch == null)
{
// for this kind of situations, find a suitable representation for this type
ch = findSuitableCandidate.provide(type);
}
if (ch == null)
{
// can't find a suitable representation, assign a random character
ch = findAnyCandidate.provide(type);
}
if (ch == null && LOG.isLoggable(Level.WARNING))
{
LOG.log(Level.WARNING, "Couldn't find a character representation for " + type.getName());
}
return ch;
}
/**
* Check if the provided character is suitable for data type encoding in the signature.
*
* @param ch
* The character which is checked.
*
* @return {@code true} if the character is suitable.
*/
private static boolean isType(char ch)
{
return 'A' <= ch && ch <= 'Z';
}
/**
* Check if the provided character is a base-10 digit.
*
* @param ch
* The character which is checked.
*
* @return {@code true} if the character is a digit.
*/
private static boolean isDigit(char ch)
{
return '0' <= ch && ch <= '9';
}
/**
* Check if the provided character is suitable for constraint encoding in the signature.
*
* @param ch
* The character which is checked.
*
* @return {@code true} if the character is suitable.
*/
private static boolean isConstraint(char ch)
{
return 'a' <= ch && ch <= 'z';
}
/**
* Helper which sets restrictions like mandatory or unique to a specified field.
*
* @param prop
* The property for which the restriction analysis is done.
* @param tempTable
* Flag which indicates that the property is part of a temporary table.
* @param sb
* The string builder which should be completed with the found restrictions.
*
* @return {@code true} if we could process the constraints of the property
*/
private static boolean processFieldConstraints(Property prop,
boolean tempTable,
StringBuilder sb)
{
if (!tempTable && prop.mandatory)
{
sb.append(CHAR_MANDANTORY);
}
return true;
}
private static boolean processFieldName(Property prop,
boolean tempTable,
StringBuilder sb,
boolean sqlName)
{
String name = prop.legacy;
if (name.indexOf(PROPERTY_NAME_DELIMITER_START) != -1 ||
name.indexOf(PROPERTY_NAME_DELIMITER_FINISH) != -1)
{
return false;
}
sb.append(PROPERTY_NAME_DELIMITER_START);
sb.append(name);
sb.append(PROPERTY_NAME_DELIMITER_FINISH);
if(sqlName)
{
sb.append("SQL");
sb.append(PROPERTY_NAME_DELIMITER_START);
sb.append(prop.column);
sb.append(PROPERTY_NAME_DELIMITER_FINISH);
}
return true;
}
/**
* Helper for anonymous functions used in identifying a suitable character for a specified data type.
*/
interface TypeIdentifierProvider
{
/** Single provider method for a character based on a base data type class. */
Character provide(Class<? extends BaseDataType> type);
}
/**
* Helper for anonymous functions used in identifying a suitable character for a specified data type.
*/
interface IteratorProvider<T>
{
/** Single provider method for an iterator */
Iterator<T> provide();
}
}