# 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();
       }
    }