# TextOps.java

    /*
    ** Module   : TextOps.java
    ** Abstract : Progress 4GL compatible TextOps object
    **
    ** Copyright (c) 2012-2025, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- ---------------------------------------Description----------------------------------------
    ** 001 AIL 20121206 Created initial version by moving static methods from the character class as
    **                  this methods will be used with character and also longchar arguments.
    ** 002 CA  20130226 Added LENGTH function support when the type is an expression.
    ** 003 CA  20130306 Added missing numEntries(String, String).
    ** 004 OM  20130322 Added missing length forms.
    ** 005 VIG 20131010 Added null processing for type argument in length-methods.
    ** 006 OM  20140924 Added extra parameter to matches methods so they can handle unix-escapes as
    **                  configured/converted. Fixed case-sensitivity of matches methods.
    ** 007 GES 20141003 Embedded null character handling fixes.
    ** 008 GES 20141130 Added variants of substring().
    ** 009 GES 20150205 Added longchar operand support to concat().
    ** 010 ECF 20150801 Performance optimizations in string operations and reflection logic.
    ** 011 EVL 20160224 Javadoc fixes to make compatible with Oracle Java 8 for Solaris 10.
    ** 012 OM  20160603 Fixed rtrimming to space character only.
    ** 013 ECF 20171227 Cache regex patterns to avoid redundant, expensive compilation.
    ** 014 SVL 20180215 trim() was made public.
    ** 015 CA  20181208 LENGTH function can have "char" as shorthand for "character" mode.
    ** 016 OM  20190301 Fixed numEntries() for unknown parameters. Deprecated numEntriesOf() methods.
    ** 017 EVL 20190415 Adding runtime support for CODEPAGE-CONVERT function.
    ** 018 CA  20190723 Fixed MATCHES - the DOT must match anything, included \r\n chars.
    ** 019 CA  20200110 Fixed MATCHES - pattern must match the entire text, not partial.
    ** 020 VVT 20200203 The concat(Object ... list) method reworked (see #4433).
    ** 021 ME  20200401 Add `entries` helper methods to get all entries as an array instead of looping
    **                  using entry.
    ** 022 OM  20200609 Small optimizations in static trim().
    ** 023 HC  20200726 Added lookup method overload.
    ** 024 ME  20200810 Added a fallback clause to handle non-String/non-BDT in substituteImpl().
    **     EVL 20200922 Rework for frequently used string operations.  Reverted from 11575 to 11573 back due to
    **                  slowing down.
    **     ME  20201009 Added helper method to check if text is empty (unknown or contains only spaces).
    **     ME  20201127 Use resource id for objects in substitute.
    **     OM  20201202 Fixed support for substitution of Java Boolean values.
    **     VVT 20210106 splitInt(): typo in javadoc fixed.
    **     ME  20210128 Avoid using java's trim to check if string is empty, 4GL only consider spaces to be empty
    **                  (not tab, cr, lf).
    **     CA  20210322 Fixed SUBSTITUTE when a legacy enum instance is used.
    **     CA  20210328 Added CAPS operator.
    **     OM  20210414 codePageConvert() returns an object with expected CP.
    **     AIL 20210422 Added error to REPLACE function if used with empty second parameter.
    **     CA  20210423 Fixed I18nOps.convmap2Java access (key must be uppercased).
    **     ECF 20210511 Added rightTrimLower.
    **     CA  20210628 Fixed DATETIME and DATETIME-TZ toStringExport() usage.
    **     CA  20220117 Improved isEmpty performance, just check if the text has another char other than space.
    **     CA  20221021 Instantiate the result with the proper codepage for REPLACE, [LEFT-/RIGHT-]TRIM, SUBSTRING,
    **                  UPPER and LOWER functions, when the source is a longchar.
    **     TJD 20220504 Java 11 compatibility minor changes
    **     CA  20220426 Added columnLength() stub.
    **     CA  20220514 Added stubs for IS-LEAD-BYTE() builtin function.
    **     OM  20220524 Added intermediary runtime support for LENGTH(..., COLUMN). Local optimization.
    ** 025 HC  20230118 Eliminated some of the uses of String.toUpperCase and/or String.toLowerCase
    **                  for performance.
    ** 026 EVL 20230317 Changed getting Text object length to use internal lengthInt() method.
    ** 027 VVT 20230307 Fixed formatting unknown longchars with SUBSTITUTE. See #7175.
    ** 028 AL2 20230512 Right trimming with white-spaces doesn't require case insensitivity checks.
    **     RAA 20230525 Creating a new BaseDataType instance is now done using BaseDataTypeFactory.
    ** 029 OM  20231128 Skip checking for error 11679 when the source not specified in codePageConvert.
    ** 030 OM  20231219 Fixed regression in H029.
    ** 031 CA  20240214 Added UPPER(POLY) and LOWER(POLY) overloads.
    ** 032 AL2 20240221 Fixed r-index semantics to honor the starting point more accurately.
    ** 033 CA  20240323 Optimized 'trim' and 'replace' - if the trim list of from/to strings don't have any
    **                  unicode alphabet codepoints, then assume case-sensitive, to avoid uppercase.
    **         20240324 Added a cache for uppercase strings.  Optimized 'instantiate'.
    **         20240324 Use logical constants for TRUE, FALSE, UNKNOWN, within internal FWD runtime.
    **         20240331 The 'upperCaseCache' cache must have different sizes for FWD server and client.
    ** 034 CA  20240401 Fixed regression in CA/033 - this class is used during conversion, so the static c'tor
    **                  needs to be aware that 'there is no session', and create a small cache.
    ** 035 CA  20240402 'trim' can receive a null String when called from H2.
    ** 036 CA  20240423 Optimized entry, split and numEntries APIs, to create a minimal number of String instances.
    ** 037 CA  20240429 Fixed regression in 'splitInt' when there is a trailing empty element as in "`0`".
    ** 038 CA  20240809 Improved 'replaceAll' and 'substitute' functions.
    ** 039 CA  20240924 Implemented KMP string search for 'replaceAll' function.
    ** 040 CA  20241030 Avoid 'Text.javaSpacifyNull' if the string is known to already have been processed.
    **                  Improved caching of match patterns.
    ** 041 AS  20241104 Removed the prefix from the 560 error message.
    ** 042 AB2 20241115 Changed the setCaseSensitive calls from replaceAllImpl to take the original
    **                  flag value (See #9333).
    ** 043 AS  20241202 Added 11672 and 14434 errors in columnLength.
    ** 044 AS  20250130 Added missing implementation token support.
    ** 045 ICP 20250123 Used integer.of and int64.of to leverage caches instances.
    ** 046 ICP 20250123 Used integer.of to leverage caches instances.
    ** 047 ES  20250221 Switch to throwing the StopConditionException through ErrorManager.
    */

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

    import java.io.*;
    import java.lang.reflect.Array;
    import java.util.*;
    import java.util.concurrent.*;
    import java.util.concurrent.atomic.*;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    import java.util.stream.Collectors;

    import com.goldencode.cache.LRUCache;
    import com.goldencode.p2j.net.SessionManager;
    import com.goldencode.p2j.oo.lang.*;
    import com.goldencode.util.StringHelper;

    /**
     * A class that provides Progress 4GL compatible character and longchar
     * functions and operators.  This class provides all functionality as statics
     * and has no instance data.
     * <p>
     * The following is the mapping of Progress language features to the
     * corresponding feature in this class:
     * <p>
     * <pre>
     * +                operator (concatenation) {@link #concat}
     * num-entries      function                 {@link #numEntries}
     * begins           operator                 {@link #begins} and {@link #_begins}
     * matches          operator                 {@link #matches} and {@link #_matches}
     * can-do           function                 {@link #matchesList}
     * caps             function                 {@link #toUpperCase}
     * compare          function                 {@link #compare}
     * entry            function                 {@link #entry}
     * index            function                 {@link #indexOf}
     * lc               function                 {@link #toLowerCase}
     * left-trim        function                 {@link #leftTrim}
     * length           function                 {@link #length} and {@link #byteLength}
     * lookup           function                 {@link #lookup}
     * maximum          function                 {@link #maximum}
     * minimum          function                 {@link #minimum}
     * r-index          function                 {@link #lastIndexOf}
     * replace          function                 {@link #replaceAll}
     * right-trim       function                 {@link #rightTrim}
     * substitute       function                 {@link #substitute}
     * substring        function                 {@link #substring}
     * trim             function                 {@link #trim}
     * entry            language statement       {@link Text#replaceEntry}
     * overlay          language statement       {@link Text#overlay}
     * substring        language statement       {@link Text#replace}
     * </pre>
     * <p>
     * There are convenience versions of the begins and matches operators that
     * directly return <code>boolean</code> values such that unwrapping is not
     * needed.  These are named the same as the operator methods above except
     * there is no 'With' appended.  For example, the begins operator is
     * {@link #begins}.
     * <p>
     * The following is the list of Progress language features which optionally
     * return a <code>character</code> instance that reflects the case-sensitivity
     * of the operands or input values:
     * <pre>
     * +                operator
     * caps             function                       {@link #toUpperCase}
     * lc               function                       {@link #toLowerCase}
     * left-trim        function                       {@link #leftTrim}
     * maximum          function                       {@link #maximum}
     * minimum          function                       {@link #minimum}
     * replace          function                       {@link #replaceAll}
     * right-trim       function                       {@link #rightTrim}
     * substring        function                       {@link #substring}
     * trim             function                       {@link #trim}
     * </pre>
     * <p>
     * Strangely enough, the <code>chr, entry</code> and <code>substitute</code>
     * functions DO NOT provide this support in Progress.  Each of the
     * corresponding backing methods properly duplicate this behavior.
     * <p>
     * This class implements unknown value logic for all operators and functions.
     * Generally, if any parameter of the these methods is unknown, the result is
     * unknown.
     */
    public final class TextOps
    {
       /** Specifies the less than operator for {@link #compare}. */
       private static final int LT = 0;

       /** Specifies the less than or equal operator for {@link #compare}. */
       private static final int LE = 1;

       /** Specifies the greater than operator for {@link #compare}. */
       private static final int GT = 2;

       /** Specifies the greater than or equal operator {@link #compare}. */
       private static final int GE = 3;

       /** Specifies the equal operator for {@link #compare}. */
       private static final int EQ = 4;

       /** Specifies the not equal operator for {@link #compare}. */
       private static final int NE = 5;

       /** Specifies the matches operator for {@link #compare}. */
       private static final int MATCHES = 6;

       /** Specifies the begins operator for {@link #compare}. */
       private static final int BEGINS = 7;

       /** Specifies the RAW type for {@link #compare} or {@link #substring}. */
       private static final int RAW = 6;

       /** Specifies the COLUMN type for {@link #compare} or {@link #substring}. */
       private static final int COLUMN = 7;

       /** Specifies the FIXED type for {@link #compare} or {@link #substring}. */
       private static final int FIXED = 8;

       /** Specifies the CHARACTER type for {@link #compare} or {@link #substring}. */
       private static final int CHARACTER = 9;

       /** Specifies the CASE-SENSITIVE strength for {@link #compare}. */
       private static final int CASE_SENSITIVE = 10;

       /** Specifies the CASE-INSENSITIVE strength for {@link #compare}. */
       private static final int CASE_INSENSITIVE = 11;

       /** Specifies the CAPS type for {@link #compare} or {@link #substring}. */
       private static final int CAPS = 12;

       /** Maximum number of patterns to cache before clearing cache */
       private static final int MAX_CACHED_PATTERNS = 32768;

       /** Cache of regex patterns to avoid unnecessary compilation, used by {@link #matchesList}. */
       private static final LRUCache<String, Pattern> listPatternCache = new LRUCache<>(MAX_CACHED_PATTERNS);

       /** Cache of regex patterns to avoid unnecessary compilation, used by {@link #matchImpl}. */
       private static final LRUCache<String, Pattern> matchPatternCache = new LRUCache<>(MAX_CACHED_PATTERNS);

       /** Cache of uppercase {@link String} values. */
       private static final LRUCache<String, String> upperCaseCache;

       /** A token used for non repetitive logging of missing columnLength implementation.
        *  It is to be removed from the class attributes after the feature is implemented. */
       private static volatile Object columnLengthToken = null;

       static
       {
          // we are outside FWD server, assume a small cache
          boolean isClient = SessionManager.get() == null || SessionManager.get().isLeaf();

          // this cache is not configurable; FWD client's heap is usually small - so a use a smaller size.
          int defaultSize = isClient ? 1024 : 1024 * 1024;
          upperCaseCache = new LRUCache<>(defaultSize);
       }

       /**
        * Get the uppercase version of this string (maybe from the {@link #upperCaseCache cache}.
        * <p>
        * Only strings with length less than 128 are cached.
        *
        * @return   The uppercase instance of this string.
        */
       public static String getUpper(String str)
       {
          if (str == null)
          {
             return null;
          }

          // TODO: use the correct locale for switching cases (see toUpperCaseImpl)

          if (str.length() > 128)
          {
             // cache only small strings
             return str.toUpperCase();
          }

          String upper = null;
          synchronized (upperCaseCache)
          {
             // TODO: cache by codepage
             upper = upperCaseCache.get(str);
             if (upper == null)
             {
                upper = str.toUpperCase();
                upperCaseCache.put(str, upper);
             }
          }

          return upper;
       }

       /**
        * Tests if the first operand matches a regular expression that is provided by the caller,
        * basing case sensitivity on the left operand.
        * <p>
        * The Progress style matching pattern passed as a parameter is converted to a regular
        * expression using {@link SQLHelper#convertToRegEx} during this method.
        *
        * @param   op
        *          String to check for a match.
        * @param   pattern
        *          Progress style matching pattern.
        * @param   windows
        *          <code>true</code> if the backslash does not have special meaning. On Unix, P4GL
        *          the backslash (\)is an escape character, like tilde (\).
        *
        * @return  <code>true</code> if op matches the regular expression or <code>false</code> if
        *          there is no match or if either operand is <code>unknown</code>.
        */
       public static boolean _matches(Text op, character pattern, boolean windows)
       {
          if (op.isUnknown() || pattern.isUnknown())
          {
             return false;
          }

          boolean caseSensitive = op.isCaseSensitive() || pattern.isCaseSensitive();
          return _matches(op.getValue(), pattern.getValue(), caseSensitive, windows);
       }

       /**
        * Tests if the first operand matches a regular expression that is provided by the caller,
        * basing case sensitivity on the left operand.
        * <p>
        * This form of the method is unix-escape independent because Progress style matching pattern
        * passed as a parameter is already transformed at conversion time to a regular expression.
        *
        * @param   op
        *          String to check for a match.
        * @param   pattern
        *          RegExp matching pattern transformed from Progress at conversion time.
        *
        * @return  <code>true</code> if op matches the regular expression or <code>false</code> if
        *          there is no match or if the left operand is <code>unknown</code>.
        */
       public static boolean _matches(Text op, String pattern)
       {
          if (op.isUnknown())
          {
             return false;
          }

          String string = op.getValue();
          if (!op.isCaseSensitive())
          {
             string = string.toUpperCase();
             pattern = pattern.toUpperCase();
          }

          return matchImpl(string, pattern);
       }

       /**
        * Tests if the first operand matches a regular expression that is provided by the caller,
        * case-insensitively.
        * <p>
        * The Progress style matching pattern passed as a parameter is converted to a regular
        * expression using {@link SQLHelper#convertToRegEx} during this method.
        *
        * @param   op
        *          String to check for a match.
        * @param   pattern
        *          Progress style matching pattern.
        * @param   windows
        *          <code>true</code> if the backslash does not have special meaning. On Unix, P4GL
        *          the backslash (\)is an escape character, like tilde (\).
        *
        * @return  <code>true</code> if op matches the regular expression or <code>false</code> if
        *          there is no match or if the right operand is <code>unknown</code>.
        */
       public static boolean _matches(String op, character pattern, boolean windows)
       {
          if (pattern.isUnknown())
          {
             return false;
          }

          return _matches(op, pattern.getValue(), pattern.isCaseSensitive(), windows);
       }

       /**
        * Tests if the first operand matches a regular expression that is provided by the caller,
        * ignoring the case of the operand and the pattern.
        * <p>
        * This form of the method is unix-escape independent because Progress style matching pattern
        * passed as a parameter is already transformed at conversion time to a regular expression.
        * By default, in P4GL character operations are case insensitive.
        *
        * @param   op
        *          String to check for a match.
        * @param   pattern
        *          RegExp matching pattern transformed from Progress at conversion time.
        *
        * @return   <code>true</code> if op matches the regular expression.
        */
       public static boolean _matches(String op, String pattern)
       {
          op = op.toUpperCase();
          pattern = pattern.toUpperCase();

          return matchImpl(op, pattern);
       }

       /**
        * Tests if the first operand matches a regular expression that is provided by the caller,
        * honoring the case sensitivity preference of the caller.
        * <p>
        * The Progress style matching pattern passed as a parameter is converted to a regular
        * expression using {@link SQLHelper#convertToRegEx} during this method.
        *
        * @param   op
        *          String to check for a match.
        * @param   pattern
        *          Progress style matching pattern.
        * @param   caseSens
        *          <code>true</code> if a case-sensitive comparison should be used.
        * @param   windows
        *          <code>true</code> if the backslash does not have special meaning. On Unix, P4GL
        *          the backslash (\)is an escape character, like tilde (\).
        *
        * @return  <code>true</code> if op matches the regular expression.
        */
       public static boolean _matches(String op, String pattern, boolean caseSens, boolean windows)
       {
          String up = caseSens ? op : op.toUpperCase();

          pattern = caseSens ? pattern : pattern.toUpperCase();
          return matchImpl(up, SQLHelper.convertToRegEx(pattern, windows));
       }

       /**
        * Tests if the first operand begins with the second operand, implementing
        * a case-sensitive comparison if either of the two operands is case-sensitive.
        *
        * @param   op1
        *          First string to compare.
        * @param   op2
        *          Second string to compare.
        *
        * @return  <code>true</code> if op1 begins with op2 or <code>false</code> if there is no
        *          match or if either operand is <code>unknown value</code>.
        */
       public static boolean _begins(Text op1, character op2)
       {
          if (op1.isUnknown() || op2.isUnknown())
          {
             return false;
          }

          boolean cs = op1.isCaseSensitive() || op2.isCaseSensitive();

          return _begins(op1.getValue(), op2.getValue(), cs);
       }

       /**
        * Tests if the first operand begins with the second operand, implementing
        * a case-sensitive comparison if the right operand is case-sensitive.
        *
        * @param   op1
        *          First string to compare.
        * @param   op2
        *          Second string to compare.
        *
        * @return  <code>true</code> if op1 begins with op2 or <code>false</code> if there is no
        *          match or if the right operand is <code>unknown value</code>.
        */
       public static boolean _begins(String op1, character op2)
       {
          if (op2.isUnknown())
          {
             return false;
          }

          return _begins(op1, op2.getValue(), op2.isCaseSensitive());
       }

       /**
        * Tests if the first operand begins with the second operand, implementing a case-sensitive
        * comparison if the left operand is case-sensitive.
        *
        * @param   op1
        *          First string to compare.
        * @param   op2
        *          Second string to compare.
        *
        * @return  <code>true</code> if op1 begins with op2 or <code>false</code> if there is no
        *          match or if the left operand is <code>unknown value</code>.
        */
       public static boolean _begins(Text op1, String op2)
       {
          if (op1.isUnknown())
          {
             return false;
          }

          return _begins(op1.getValue(), op2, op1.isCaseSensitive());
       }

       /**
        * Tests if the first operand begins with the second operand, ignoring the case of the two
        * operands.
        *
        * @param   op1
        *          First string to compare.
        * @param   op2
        *          Second string to compare.
        *
        * @return  <code>true</code> if op1 begins with op2.
        */
       public static boolean _begins(String op1, String op2)
       {
          return _begins(op1, op2, false);
       }

       /**
        * Tests if the first operand begins with the second operand, honoring the case sensitivity
        * preference of the caller.
        *
        * @param   op1
        *          First string to compare.
        * @param   op2
        *          Second string to compare.
        * @param   caseSens
        *          <code>true</code> if a case-sensitive comparison should be used.
        *
        * @return  <code>true</code> if op1 begins with op2.
        */
       public static boolean _begins(String op1, String op2, boolean caseSens)
       {
          String up1 = caseSens ? op1 : op1.toUpperCase();
          String up2 = caseSens ? op2 : op2.toUpperCase();

          return up1.startsWith(up2);
       }

       /**
        * Tests if the first operand begins with the second operand, implementing
        * a case-sensitive comparison if either of the two operands is
        * case-sensitive.
        *
        * @param    op1
        *           First string to compare.
        * @param    op2
        *           Second string to compare.
        *
        * @return   <code>true</code> if op1 begins with op2 or
        *           <code>unknown value</code> if either operand is
        *           <code>unknown value</code>.
        */
       public static logical begins(Text op1, character op2)
       {
          if (op1.isUnknown() || op2.isUnknown())
          {
             return logical.UNKNOWN;
          }

          boolean cs = (op1.isCaseSensitive() || op2.isCaseSensitive());

          return begins(op1.getValue(), op2.getValue(), cs);
       }

       /**
        * Tests if the first operand begins with the second operand, implementing a case-sensitive
        * comparison if the right operand is case-sensitive.
        *
        * @param   op1
        *          First string to compare.
        * @param   op2
        *          Second string to compare.
        *
        * @return  <code>true</code> if op1 begins with op2 or <code>unknown value</code> if the
        *          right operand is <code>unknown value</code>.
        */
       public static logical begins(String op1, character op2)
       {
          if (op2.isUnknown())
          {
             return logical.UNKNOWN;
          }

          return begins(op1, op2.getValue(), op2.isCaseSensitive());
       }

       /**
        * Tests if the first operand begins with the second operand, implementing a case-sensitive
        * comparison if the left operand is case-sensitive.
        *
        * @param   op1
        *          First string to compare.
        * @param   op2
        *          Second string to compare.
        *
        * @return  <code>true</code> if op1 begins with op2 or <code>unknown value</code> if the left
        *          operand is <code>unknown value</code>.
        */
       public static logical begins(Text op1, String op2)
       {
          if (op1.isUnknown())
          {
             return logical.UNKNOWN;
          }

          return begins(op1.getValue(), op2, op1.isCaseSensitive());
       }

       /**
        * Tests if the first operand begins with the second operand, ignoring the
        * case of the two operands.
        *
        * @param    op1
        *           First string to compare.
        * @param    op2
        *           Second string to compare.
        *
        * @return   <code>true</code> if op1 begins with op2.
        */
       public static logical begins(String op1, String op2)
       {
          return begins(op1, op2, false);
       }

       /**
        * Tests if the first operand begins with the second operand, honoring the
        * case sensitivity preference of the caller.
        *
        * @param    op1
        *           First string to compare.
        * @param    op2
        *           Second string to compare.
        * @param    caseSens
        *           <code>true</code> if a case-sensitive comparison should be used.
        *
        * @return   <code>true</code> if op1 begins with op2.
        */
       public static logical begins(String op1, String op2, boolean caseSens)
       {
          String up1 = caseSens ? op1 : op1.toUpperCase();
          String up2 = caseSens ? op2 : op2.toUpperCase();

          return logical.of(up1.startsWith(up2));
       }

       /**
        * Implementation of the IS-LEAD-BYTE() builtin function.
        *
        * @param    txt
        *           The text to check.
        *
        * @return   TODO
        */
       public static logical isLeadByte(String txt)
       {
          UnimplementedFeature.missing("IS-LEAD-BYTE() is not implemented.");
          return logical.UNKNOWN;
       }

       /**
        * Implementation of the IS-LEAD-BYTE() builtin function.
        *
        * @param    txt
        *           The text to check.
        *
        * @return   TODO
        */
       public static logical isLeadByte(Text txt)
       {
          UnimplementedFeature.missing("IS-LEAD-BYTE() is not implemented.");
          return logical.UNKNOWN;
       }

       /**
        * Returns the length of the given <code>character</code> literal in display or print character-columns.
        *
        * @param    s
        *           The literal to test.
        *
        * @return   The number of bytes in the given literal.
        */
       public static integer columnLength(Text s)
       {
          if (s.getClass() == longchar.class)
          {
             ErrorManager.recordOrThrowError(11672,
                     "\"Column\" is not a valid option for a LONGCHAR variable", false);
          }
          else if (s.getClass() == clob.class)
          {
             ErrorManager.recordOrThrowError(14434,
                     "Column is not a valid LENGTH option for CLOBs", false);
          }
          return columnLength(s.getValue());
       }

       /**
        * Returns the length of the given <code>character</code> literal in display or print character-columns.
        *
        * @param    s
        *           The literal to test.
        *
        * @return   The number of bytes in the given literal.
        */
       public static integer columnLength(String s)
       {
          columnLengthToken = UnimplementedFeature.
             missing("LENGTH(..., 'COLUMN') is not implemented. Temporarily, returning LENGTH(..., 'CHAR'), instead.",
                     columnLengthToken);
          return length(s);
       }

       /**
        * Returns the length of the given <code>character</code> or
        * <code>longchar</code> instance in bytes.
        *
        * @param    c
        *           The instance to test.
        *
        * @return   The number of bytes in the given instance or -1 if the
        *           instance is <code>unknown value</code>.
        */
       public static int byteLengthOf(Text c)
       {
          if (c.isUnknown())
             return -1;

          return c.getValue().getBytes().length;
       }

       /**
        * Returns the length of the given <code>character</code> literal in bytes.
        *
        * @param    s
        *           The literal to test.
        *
        * @return   The number of bytes in the given literal.
        */
       public static integer byteLength(String s)
       {
          return integer.of(s.getBytes().length);
       }

       /**
        * Returns the length of the given {@code character} or {@code longchar} instance in bytes. This method
        * takes into account the CP used. For a {@code longchar} instance its codepage is used (as configured
        * with {@code FIX-CODEPAGE} statement), for a {@code character} instance, the CPINTERNAL is used.
        * <p>
        * <b>Note:</b> FWD is using by default UTF-8 as CPINTERNAL which differs from ISO8859-1 of ABL. Some
        * result may mot be consistent.
        *
        * @param   c
        *          The instance to test.
        *
        * @return  The number of bytes in the given instance or {@code unknown value} if the instance is
        *          {@code unknown value}.
        */
       public static integer byteLength(Text c)
       {
          if (c.isUnknown())
          {
             return integer.UNKNOWN;
          }

          // detect the 4GL CODEPAGE
          String ablCP = null;
          if (c instanceof longchar)
          {
             ablCP = ((longchar) c)._getCodePage();
          }
          if (ablCP == null)
          {
             // defaulting to FWD internal CP for CHARACTER and LONGCHAR values which do not have CP fixed
             ablCP = I18nOps._getCPInternal();
          }

          // convert the 4GL CODEPAGE to Java CHARSET
          String charSet = I18nOps.getJavaCharset(ablCP);
          if (charSet == null)
          {
             // failed to obtain the java charset for [ablCP]
             return integer.UNKNOWN;
          }

          try
          {
             return integer.of(c.getValue().getBytes(charSet).length);
          }
          catch (UnsupportedEncodingException e)
          {
             // the CHARSET failed to work as expected
             return integer.UNKNOWN;
          }
       }

       /**
        * Convert the given text from one code page to another. As no codepage is
        * specified for this API, no conversion occurs.
        *
        * @param    text
        *           The text which needs to be converted.
        *
        * @return   The same text, with no change.
        */
       public static <T extends Text> T codePageConvert(T text)
       {
          return text;
       }

       /**
        * Convert the given text using the specified codepage.
        *
        * @param    text
        *           The text to be converted.
        * @param    target
        *           The target codepage.
        *
        * @return   The converted text.
        */
       public static <T extends Text> T codePageConvert(T text, String target)
       {
          return codePageConvert(text, target, (String) null);
       }

       /**
        * Convert the given text using the specified codepage, assuming the text is encoded with the given source
        * codepage.
        * <p>
        * Note: FWD implementation: actually no change is performed on the parameter value, only the returned
        *       object's CP is configured so that further usage of it will use the desired target CP.
        *
        * @param    text
        *           The text to be converted.
        * @param    target
        *           The target codepage.
        * @param    source
        *           The source codepage. If the text is a {@link longchar} instance, then the text's internal
        *           codepage is used.
        *
        * @return   The converted text.
        */
       public static <T extends Text> T codePageConvert(T text, String target, String source)
       {
          if (target == null)
          {
             target = I18nOps._getCPInternal();
          }
          String javaTcs = I18nOps.convmap2Java.get(target);
          if (javaTcs == null)
          {
             ErrorManager.recordOrThrowError(912, target, I18nOps.CONVERSION_MAP_FILE);
             // Code page attribute table for <code-page> was not found in <filename>.
             return null;
          }

          boolean check11679 = true;
          if (source == null)
          {
             if (text instanceof longchar)
             {
                source = ((longchar) text)._getCodePage();
             }

             if (source != null)
             {
                check11679 = false; // skip checking for error 11679 since the source is correct
             }
             else
             {
                source = I18nOps._getCPInternal();
             }
          }

          String javaScs = I18nOps.convmap2Java.get(source);
          if (javaScs == null)
          {
             ErrorManager.recordOrThrowError(912, source, I18nOps.CONVERSION_MAP_FILE);
             // Code page attribute table for <code-page> was not found in <filename>.
             return null;
          }

          if (check11679 && text instanceof longchar)
          {
             String crtCp = ((longchar) text)._getCodePage();
             if (crtCp == null)
             {
                crtCp = I18nOps._getCPInternal();
             }

             if (!crtCp.equals(source))
             {
                ErrorManager.recordOrThrowError(11679);
                // Mismatch between source-codepage argument and LONGCHAR codepage. (11679)
                return null;
             }
          }

          String value = text.isUnknown() ? null : text.value;
          // TODO: execute a true CP conversion in order to detect errors?

          Text ret;
          Class<? extends Text> tClass = text.getClass();
          if (tClass == character.class)
          {
             // the [character] type doesn't know its CP
             ret = new character(value);
          }
          else if (tClass == clob.class)
          {
             // the [character] type knows its CP from the specific constructor
             ret = new clob(value, target);
          }
          else // must be a 'pure' longchar
          {
             // for the [longchar] type the CP must be fixed before assigning the actual value
             ret = new longchar();
             ((longchar) ret).fixCodePage(target);
             ret.assign(value);
          }

          ret.setCaseSensitive(text.caseSens);

          return (T) ret;
       }

       /**
        * Convert the given text using the specified codepage, assuming the text
        * is encoded with the given source codepage.
        *
        * @param    text
        *           The text to be converted.
        * @param    target
        *           The target codepage.
        * @param    source
        *           The source codepage. If the text is a {@link longchar}
        *           instance, then the text's internal codepage is used.
        *
        * @return   The converted text.
        */
       public static <T extends Text> T codePageConvert(T         text,
                                                        String    target,
                                                        character source)
       {
          // validate source
          String srcValue = source == null || source.isUnknown() ? (String) null : source.getValue();

          return codePageConvert(text, target, srcValue);
       }

       /**
        * Convert the given text using the specified codepage.
        *
        * @param    text
        *           The text to be converted.
        * @param    target
        *           The target codepage.
        *
        * @return   The converted text.
        */
       public static <T extends Text> T codePageConvert(T text, character target)
       {
          // validate target
          String tarValue = target == null || target.isUnknown() ? (String) null : target.getValue();

          return codePageConvert(text,
                                 tarValue,
                                 text instanceof longchar ? ((longchar) text)._getCodePage() : (String) null);
       }

       /**
        * Convert the given text using the specified codepage, assuming the text
        * is encoded with the given source codepage.
        *
        * @param    text
        *           The text to be converted.
        * @param    target
        *           The target codepage.
        * @param    source
        *           The source codepage. If the text is a {@link longchar}
        *           instance, then the text's internal codepage is used.
        *
        * @return   The converted text.
        */
       public static <T extends Text> T codePageConvert(T         text,
                                                        character target,
                                                        String    source)
       {
          // validate target
          String tarValue = target == null || target.isUnknown() ? (String) null : target.getValue();

          return codePageConvert(text, tarValue, source);
       }

       /**
        * Convert the given text using the specified codepage, assuming the text
        * is encoded with the given source codepage.
        *
        * @param    text
        *           The text to be converted.
        * @param    target
        *           The target codepage.
        * @param    source
        *           The source codepage. If the text is a {@link longchar}
        *           instance, then the text's internal codepage is used.
        *
        * @return   The converted text.
        */
       public static <T extends Text> T codePageConvert(T         text,
                                                        character target,
                                                        character source)
       {
          // validate target and source
          String tarValue = target == null || target.isUnknown() ? (String) null : target.getValue();
          String srcValue = source == null || source.isUnknown() ? (String) null : source.getValue();

          return codePageConvert(text, tarValue, srcValue);
       }

       /**
        * Compare the two texts using the given operator and the given text.
        * The operator must evaluate to one of the following relational operators:
        * <ul>
        * <li> LT (or &lt;)
        * <li> LE (or &lt;=)
        * <li> EQ (or =)
        * <li> GE (or &gt;=)
        * <li> GT (or &gt;)
        * <li> NE (or &lt;&gt;)
        * <li> BEGINS
        * <li> MATCHES
        * </ul>
        * The strength must evaluate to one of the given values:
        * <ul>
        * <li> RAW - compares the two strings using the numeric values in the
        *      current code page.
        * <li> CASE-SENSITIVE - performs a case-sensitive comparison
        * <li> CASE-INSENSITIVE - performs a case-insensitive comparison
        * <li> CAPS - converts any lowercase letters in the two strings to
        *      uppercase letters, and then performs a raw comparison of the
        *      resulting strings.
        *      When neither string contains a wildcard character, this option
        *      behaves the same as the MATCHES operator.
        * </ul>
        *
        * @param    op1
        *           The first string to be compared.
        * @param    operator
        *           The operator to be used for this comparison. Must evaluate to
        *           one of the strings presented above.
        * @param    op2
        *           The second string to be compared.
        * @param    strength
        *           The used strength. Must evaluate to one of the strings
        *           presented above.
        *
        * @return   A {@link logical} value representing the result of the
        *           comparison, depending on the operator and strength used.
        */
       public static logical compare(Text      op1,
                                     character operator,
                                     Text      op2,
                                     character strength)
       {
          return compare(op1, operator, op2, strength, new character());
       }

       /**
        * Compare the two texts using the given operator and the given text.
        * The operator must evaluate to one of the following relational operators:
        * <ul>
        * <li> LT (or &lt;)
        * <li> LE (or &lt;=)
        * <li> EQ (or =)
        * <li> GE (or &gt;=)
        * <li> GT (or &gt;)
        * <li> NE (or &lt;&gt;)
        * <li> BEGINS
        * <li> MATCHES
        * </ul>
        * The strength must evaluate to one of the given values:
        * <ul>
        * <li> RAW - compares the two strings using the numeric values in the
        *      current code page.
        * <li> CASE-SENSITIVE - performs a case-sensitive comparison
        * <li> CASE-INSENSITIVE - performs a case-insensitive comparison
        * <li> CAPS - converts any lowercase letters in the two strings to
        *      uppercase letters, and then performs a raw comparison of the
        *      resulting strings.
        *      When neither string contains a wildcard character, this option
        *      behaves the same as the MATCHES operator.
        * </ul>
        *
        * @param    op1
        *           The first string to be compared.
        * @param    operator
        *           The operator to be used for this comparison. Must evaluate to
        *           one of the strings presented above.
        * @param    op2
        *           The second string to be compared.
        * @param    strength
        *           The used strength. Must evaluate to one of the strings
        *           presented above.
        *
        * @return   A {@link logical} value representing the result of the
        *           comparison, depending on the operator and strength used.
        */
       public static logical compare(Text      op1,
                                     String    operator,
                                     Text      op2,
                                     character strength)
       {
          return compare(op1, new character(operator), op2, strength);
       }

       /**
        * Compare the two texts using the given operator and the given text.
        * The operator must evaluate to one of the following relational operators:
        * <ul>
        * <li> LT (or &lt;)
        * <li> LE (or &lt;=)
        * <li> EQ (or =)
        * <li> GE (or &gt;=)
        * <li> GT (or &gt;)
        * <li> NE (or &lt;&gt;)
        * <li> BEGINS
        * <li> MATCHES
        * </ul>
        * The strength must evaluate to one of the given values:
        * <ul>
        * <li> RAW - compares the two strings using the numeric values in the
        *      current code page.
        * <li> CASE-SENSITIVE - performs a case-sensitive comparison
        * <li> CASE-INSENSITIVE - performs a case-insensitive comparison
        * <li> CAPS - converts any lowercase letters in the two strings to
        *      uppercase letters, and then performs a raw comparison of the
        *      resulting strings.
        *      When neither string contains a wildcard character, this option
        *      behaves the same as the MATCHES operator.
        * </ul>
        *
        * @param    op1
        *           The first string to be compared.
        * @param    operator
        *           The operator to be used for this comparison. Must evaluate to
        *           one of the strings presented above.
        * @param    op2
        *           The second string to be compared.
        * @param    strength
        *           The used strength. Must evaluate to one of the strings
        *           presented above.
        *
        * @return   A {@link logical} value representing the result of the
        *           comparison, depending on the operator and strength used.
        */
       public static logical compare(Text      op1,
                                     character operator,
                                     Text      op2,
                                     String    strength)
       {
          return compare(op1, operator, op2, new character(strength), new character());
       }

       /**
        * Compare the two texts using the given operator and the given text.
        * The operator must evaluate to one of the following relational operators:
        * <ul>
        * <li> LT (or &lt;)
        * <li> LE (or &lt;=)
        * <li> EQ (or =)
        * <li> GE (or &gt;=)
        * <li> GT (or &gt;)
        * <li> NE (or &lt;&gt;)
        * <li> BEGINS
        * <li> MATCHES
        * </ul>
        * The strength must evaluate to one of the given values:
        * <ul>
        * <li> RAW - compares the two strings using the numeric values in the
        *      current code page.
        * <li> CASE-SENSITIVE - performs a case-sensitive comparison
        * <li> CASE-INSENSITIVE - performs a case-insensitive comparison
        * <li> CAPS - converts any lowercase letters in the two strings to
        *      uppercase letters, and then performs a raw comparison of the
        *      resulting strings.
        *      When neither string contains a wildcard character, this option
        *      behaves the same as the MATCHES operator.
        * </ul>
        *
        * @param    op1
        *           The first string to be compared.
        * @param    operator
        *           The operator to be used for this comparison. Must evaluate to
        *           one of the strings presented above.
        * @param    op2
        *           The second string to be compared.
        * @param    strength
        *           The used strength. Must evaluate to one of the strings
        *           presented above.
        *
        * @return   A {@link logical} value representing the result of the
        *           comparison, depending on the operator and strength used.
        */
       public static logical compare(Text   op1,
                                     String operator,
                                     Text   op2,
                                     String strength)
       {
          return compare(op1,
                         new character(operator),
                         op2,
                         new character(strength));
       }

       /**
        * Compare the two texts using the given operator and the given text.
        * The operator must evaluate to one of the following relational operators:
        * <ul>
        * <li> LT (or &lt;)
        * <li> LE (or &lt;=)
        * <li> EQ (or =)
        * <li> GE (or &gt;=)
        * <li> GT (or &gt;)
        * <li> NE (or &lt;&gt;)
        * <li> BEGINS
        * <li> MATCHES
        * </ul>
        * The strength must evaluate to one of the given values:
        * <ul>
        * <li> RAW - compares the two strings using the numeric values in the
        *      current code page.
        * <li> CASE-SENSITIVE - performs a case-sensitive comparison
        * <li> CASE-INSENSITIVE - performs a case-insensitive comparison
        * <li> CAPS - converts any lowercase letters in the two strings to
        *      uppercase letters, and then performs a raw comparison of the
        *      resulting strings.
        *      When neither string contains a wildcard character, this option
        *      behaves the same as the MATCHES operator.
        * </ul>
        *
        * @param    op1
        *           The first string to be compared.
        * @param    operator
        *           The operator to be used for this comparison. Must evaluate to
        *           one of the strings presented above.
        * @param    op2
        *           The second string to be compared.
        * @param    strength
        *           The used strength. Must evaluate to one of the strings
        *           presented above.
        *
        * @return   A {@link logical} value representing the result of the
        *           comparison, depending on the operator and strength used.
        */
       public static logical compare(Text   op1,
                                     String operator,
                                     String op2,
                                     String strength)
       {
          return compare(op1,
                         new character(operator),
                         new character(op2),
                         new character(strength));
       }
       /**
        * Compare the two texts using the given operator and the given text.
        * The operator must evaluate to one of the following relational operators:
        * <ul>
        * <li> LT (or &lt;)
        * <li> LE (or &lt;=)
        * <li> EQ (or =)
        * <li> GE (or &gt;=)
        * <li> GT (or &gt;)
        * <li> NE (or &lt;&gt;)
        * <li> BEGINS
        * <li> MATCHES
        * </ul>
        * The strength must evaluate to one of the given values:
        * <ul>
        * <li> RAW - compares the two strings using the numeric values in the
        *      current code page.
        * <li> CASE-SENSITIVE - performs a case-sensitive comparison
        * <li> CASE-INSENSITIVE - performs a case-insensitive comparison
        * <li> CAPS - converts any lowercase letters in the two strings to
        *      uppercase letters, and then performs a raw comparison of the
        *      resulting strings.
        *      When neither string contains a wildcard character, this option
        *      behaves the same as the MATCHES operator.
        * </ul>
        *
        * @param    op1
        *           The first string to be compared.
        * @param    operator
        *           The operator to be used for this comparison. Must evaluate to
        *           one of the strings presented above.
        * @param    op2
        *           The second string to be compared.
        * @param    strength
        *           The used strength. Must evaluate to one of the strings
        *           presented above.
        *
        * @return   A {@link logical} value representing the result of the
        *           comparison, depending on the operator and strength used.
        */
       public static logical compare(String op1,
                                     String operator,
                                     String op2,
                                     String strength)
       {
          return compare(new character(op1),
                         new character(operator),
                         new character(op2),
                         new character(strength));
       }

       /**
        * Compare the two texts using the given operator and the given text.
        * The operator must evaluate to one of the following relational operators:
        * <ul>
        * <li> LT (or &lt;)
        * <li> LE (or &lt;=)
        * <li> EQ (or =)
        * <li> GE (or &gt;=)
        * <li> GT (or &gt;)
        * <li> NE (or &lt;&gt;)
        * <li> BEGINS
        * <li> MATCHES
        * </ul>
        * The strength must evaluate to one of the given values:
        * <ul>
        * <li> RAW - compares the two strings using the numeric values in the
        *      current code page.
        * <li> CASE-SENSITIVE - performs a case-sensitive comparison
        * <li> CASE-INSENSITIVE - performs a case-insensitive comparison
        * <li> CAPS - converts any lowercase letters in the two strings to
        *      uppercase letters, and then performs a raw comparison of the
        *      resulting strings.
        *      When neither string contains a wildcard character, this option
        *      behaves the same as the MATCHES operator.
        * </ul>
        *
        * @param    op1
        *           The first string to be compared.
        * @param    operator
        *           The operator to be used for this comparison. Must evaluate to
        *           one of the strings presented above.
        * @param    op2
        *           The second string to be compared.
        * @param    strength
        *           The used strength. Must evaluate to one of the strings
        *           presented above.
        * @param    collation
        *           The used collation. When <code>null</code>, the default
        *           collation is used.
        *
        * @return   A {@link logical} value representing the result of the
        *           comparison, depending on the operator and strength used.
        */
       public static logical compare(Text      op1,
                                     character operator,
                                     Text      op2,
                                     character strength,
                                     character collation)
       {
          int op = getCompareOperator(operator);
          int str = getStrength(strength);

          if (op < 0 || str < 0)
          {
             return logical.UNKNOWN;
          }

          // TODO: validate collation, if not-null

          return compare(op1, op, op2, str, collation.getValue());
       }

       /**
        * Compare the two texts using the given operator and the given text.
        * The operator must evaluate to one of the following relational operators:
        * <ul>
        * <li> LT (or &lt;)
        * <li> LE (or &lt;=)
        * <li> EQ (or =)
        * <li> GE (or &gt;=)
        * <li> GT (or &gt;)
        * <li> NE (or &lt;&gt;)
        * <li> BEGINS
        * <li> MATCHES
        * </ul>
        * The strength must evaluate to one of the given values:
        * <ul>
        * <li> RAW - compares the two strings using the numeric values in the
        *      current code page.
        * <li> CASE-SENSITIVE - performs a case-sensitive comparison
        * <li> CASE-INSENSITIVE - performs a case-insensitive comparison
        * <li> CAPS - converts any lowercase letters in the two strings to
        *      uppercase letters, and then performs a raw comparison of the
        *      resulting strings.
        *      When neither string contains a wildcard character, this option
        *      behaves the same as the MATCHES operator.
        * </ul>
        *
        * @param    op1
        *           The first string to be compared.
        * @param    operator
        *           The operator to be used for this comparison. Must evaluate to
        *           one of the strings presented above.
        * @param    op2
        *           The second string to be compared.
        * @param    strength
        *           The used strength. Must evaluate to one of the strings
        *           presented above.
        * @param    collation
        *           The used collation. When <code>null</code>, the default
        *           collation is used.
        *
        * @return   A {@link logical} value representing the result of the
        *           comparison, depending on the operator and strength used.
        */
       public static logical compare(Text      op1,
                                     String    operator,
                                     Text      op2,
                                     character strength,
                                     character collation)
       {
          return compare(op1, new character(operator), op2, strength, collation);
       }

       /**
        * Compare the two texts using the given operator and the given text.
        * The operator must evaluate to one of the following relational operators:
        * <ul>
        * <li> LT (or &lt;)
        * <li> LE (or &lt;=)
        * <li> EQ (or =)
        * <li> GE (or &gt;=)
        * <li> GT (or &gt;)
        * <li> NE (or &lt;&gt;)
        * <li> BEGINS
        * <li> MATCHES
        * </ul>
        * The strength must evaluate to one of the given values:
        * <ul>
        * <li> RAW - compares the two strings using the numeric values in the
        *      current code page.
        * <li> CASE-SENSITIVE - performs a case-sensitive comparison
        * <li> CASE-INSENSITIVE - performs a case-insensitive comparison
        * <li> CAPS - converts any lowercase letters in the two strings to
        *      uppercase letters, and then performs a raw comparison of the
        *      resulting strings.
        *      When neither string contains a wildcard character, this option
        *      behaves the same as the MATCHES operator.
        * </ul>
        *
        * @param    op1
        *           The first string to be compared.
        * @param    operator
        *           The operator to be used for this comparison. Must evaluate to
        *           one of the strings presented above.
        * @param    op2
        *           The second string to be compared.
        * @param    strength
        *           The used strength. Must evaluate to one of the strings
        *           presented above.
        * @param    collation
        *           The used collation. When <code>null</code>, the default
        *           collation is used.
        *
        * @return   A {@link logical} value representing the result of the
        *           comparison, depending on the operator and strength used.
        */
       public static logical compare(Text      op1,
                                     character operator,
                                     Text      op2,
                                     String    strength,
                                     character collation)
       {
          return compare(op1, operator, op2, new character(strength), collation);
       }

       /**
        * Compare the two texts using the given operator and the given text.
        * The operator must evaluate to one of the following relational operators:
        * <ul>
        * <li> LT (or &lt;)
        * <li> LE (or &lt;=)
        * <li> EQ (or =)
        * <li> GE (or &gt;=)
        * <li> GT (or &gt;)
        * <li> NE (or &lt;&gt;)
        * <li> BEGINS
        * <li> MATCHES
        * </ul>
        * The strength must evaluate to one of the given values:
        * <ul>
        * <li> RAW - compares the two strings using the numeric values in the
        *      current code page.
        * <li> CASE-SENSITIVE - performs a case-sensitive comparison
        * <li> CASE-INSENSITIVE - performs a case-insensitive comparison
        * <li> CAPS - converts any lowercase letters in the two strings to
        *      uppercase letters, and then performs a raw comparison of the
        *      resulting strings.
        *      When neither string contains a wildcard character, this option
        *      behaves the same as the MATCHES operator.
        * </ul>
        *
        * @param    op1
        *           The first string to be compared.
        * @param    operator
        *           The operator to be used for this comparison. Must evaluate to
        *           one of the strings presented above.
        * @param    op2
        *           The second string to be compared.
        * @param    strength
        *           The used strength. Must evaluate to one of the strings
        *           presented above.
        * @param    collation
        *           The used collation. When <code>null</code>, the default
        *           collation is used.
        *
        * @return   A {@link logical} value representing the result of the
        *           comparison, depending on the operator and strength used.
        */
       public static logical compare(Text      op1,
                                     String    operator,
                                     Text      op2,
                                     String    strength,
                                     character collation)
       {
          return compare(op1,
                         new character(operator),
                         op2,
                         new character(strength),
                         collation);
       }

       /**
        * Compare the two texts using the given operator and the given text.
        * The operator must evaluate to one of the following relational operators:
        * <ul>
        * <li> LT (or &lt;)
        * <li> LE (or &lt;=)
        * <li> EQ (or =)
        * <li> GE (or &gt;=)
        * <li> GT (or &gt;)
        * <li> NE (or &lt;&gt;)
        * <li> BEGINS
        * <li> MATCHES
        * </ul>
        * The strength must evaluate to one of the given values:
        * <ul>
        * <li> RAW - compares the two strings using the numeric values in the
        *      current code page.
        * <li> CASE-SENSITIVE - performs a case-sensitive comparison
        * <li> CASE-INSENSITIVE - performs a case-insensitive comparison
        * <li> CAPS - converts any lowercase letters in the two strings to
        *      uppercase letters, and then performs a raw comparison of the
        *      resulting strings.
        *      When neither string contains a wildcard character, this option
        *      behaves the same as the MATCHES operator.
        * </ul>
        *
        * @param    op1
        *           The first string to be compared.
        * @param    operator
        *           The operator to be used for this comparison. Must evaluate to
        *           one of the strings presented above.
        * @param    op2
        *           The second string to be compared.
        * @param    strength
        *           The used strength. Must evaluate to one of the strings
        *           presented above.
        * @param    collation
        *           The used collation. When <code>null</code>, the default
        *           collation is used.
        *
        * @return   A {@link logical} value representing the result of the
        *           comparison, depending on the operator and strength used.
        */
       public static logical compare(Text      op1,
                                     character operator,
                                     Text      op2,
                                     character strength,
                                     String    collation)
       {
          character chcol = collation == null ? (character) null
                                              : new character(collation);

          return compare(op1, operator, op2, strength, chcol);
       }

       /**
        * Compare the two texts using the given operator and the given text.
        * The operator must evaluate to one of the following relational operators:
        * <ul>
        * <li> LT (or &lt;)
        * <li> LE (or &lt;=)
        * <li> EQ (or =)
        * <li> GE (or &gt;=)
        * <li> GT (or &gt;)
        * <li> NE (or &lt;&gt;)
        * <li> BEGINS
        * <li> MATCHES
        * </ul>
        * The strength must evaluate to one of the given values:
        * <ul>
        * <li> RAW - compares the two strings using the numeric values in the
        *      current code page.
        * <li> CASE-SENSITIVE - performs a case-sensitive comparison
        * <li> CASE-INSENSITIVE - performs a case-insensitive comparison
        * <li> CAPS - converts any lowercase letters in the two strings to
        *      uppercase letters, and then performs a raw comparison of the
        *      resulting strings.
        *      When neither string contains a wildcard character, this option
        *      behaves the same as the MATCHES operator.
        * </ul>
        *
        * @param    op1
        *           The first string to be compared.
        * @param    operator
        *           The operator to be used for this comparison. Must evaluate to
        *           one of the strings presented above.
        * @param    op2
        *           The second string to be compared.
        * @param    strength
        *           The used strength. Must evaluate to one of the strings
        *           presented above.
        * @param    collation
        *           The used collation. When <code>null</code>, the default
        *           collation is used.
        *
        * @return   A {@link logical} value representing the result of the
        *           comparison, depending on the operator and strength used.
        */
       public static logical compare(Text      op1,
                                     String    operator,
                                     Text      op2,
                                     character strength,
                                     String    collation)
       {
          return compare(op1, new character(operator), op2, strength, collation);
       }

       /**
        * Compare the two texts using the given operator and the given text.
        * The operator must evaluate to one of the following relational operators:
        * <ul>
        * <li> LT (or &lt;)
        * <li> LE (or &lt;=)
        * <li> EQ (or =)
        * <li> GE (or &gt;=)
        * <li> GT (or &gt;)
        * <li> NE (or &lt;&gt;)
        * <li> BEGINS
        * <li> MATCHES
        * </ul>
        * The strength must evaluate to one of the given values:
        * <ul>
        * <li> RAW - compares the two strings using the numeric values in the
        *      current code page.
        * <li> CASE-SENSITIVE - performs a case-sensitive comparison
        * <li> CASE-INSENSITIVE - performs a case-insensitive comparison
        * <li> CAPS - converts any lowercase letters in the two strings to
        *      uppercase letters, and then performs a raw comparison of the
        *      resulting strings.
        *      When neither string contains a wildcard character, this option
        *      behaves the same as the MATCHES operator.
        * </ul>
        *
        * @param    op1
        *           The first string to be compared.
        * @param    operator
        *           The operator to be used for this comparison. Must evaluate to
        *           one of the strings presented above.
        * @param    op2
        *           The second string to be compared.
        * @param    strength
        *           The used strength. Must evaluate to one of the strings
        *           presented above.
        * @param    collation
        *           The used collation. When <code>null</code>, the default
        *           collation is used.
        *
        * @return   A {@link logical} value representing the result of the
        *           comparison, depending on the operator and strength used.
        */
       public static logical compare(Text      op1,
                                     character operator,
                                     Text      op2,
                                     String    strength,
                                     String    collation)
       {
          return compare(op1, operator, op2, new character(strength), collation);
       }

       /**
        * Compare the two texts using the given operator and the given text.
        * The operator must evaluate to one of the following relational operators:
        * <ul>
        * <li> LT (or &lt;)
        * <li> LE (or &lt;=)
        * <li> EQ (or =)
        * <li> GE (or &gt;=)
        * <li> GT (or &gt;)
        * <li> NE (or &lt;&gt;)
        * <li> BEGINS
        * <li> MATCHES
        * </ul>
        * The strength must evaluate to one of the given values:
        * <ul>
        * <li> RAW - compares the two strings using the numeric values in the
        *      current code page.
        * <li> CASE-SENSITIVE - performs a case-sensitive comparison
        * <li> CASE-INSENSITIVE - performs a case-insensitive comparison
        * <li> CAPS - converts any lowercase letters in the two strings to
        *      uppercase letters, and then performs a raw comparison of the
        *      resulting strings.
        *      When neither string contains a wildcard character, this option
        *      behaves the same as the MATCHES operator.
        * </ul>
        *
        * @param    op1
        *           The first string to be compared.
        * @param    operator
        *           The operator to be used for this comparison. Must evaluate to
        *           one of the strings presented above.
        * @param    op2
        *           The second string to be compared.
        * @param    strength
        *           The used strength. Must evaluate to one of the strings
        *           presented above.
        * @param    collation
        *           The used collation. When <code>null</code>, the default
        *           collation is used.
        *
        * @return   A {@link logical} value representing the result of the
        *           comparison, depending on the operator and strength used.
        */
       public static logical compare(Text   op1,
                                     String operator,
                                     Text   op2,
                                     String strength,
                                     String collation)
       {
          return compare(op1,
                         new character(operator),
                         op2,
                         new character(strength),
                         collation);
       }

       /**
        * Compares two character values with a runtime-specified strength and
        * operator.  This method will probably generate incorrect results in a
        * DBCS environment since it will use UNICODE as the basis for conversion.
        * The result should match up with the Progress equivalents in the ASCII
        * range.
        * <p>
        * This method does not handle the case of user-specified collation tables
        * as is possible in Progress.
        * <p>
        * In addition, the case-sensitive and case-insensitive strength options
        * are likely to differ in some results since the current implementation
        * really just implements a direct numeric comparison based on each
        * UNICODE character's value (though case-insensitive support is
        * provided but it just converts everything to the same case before
        * implementing the 'raw' lexicographic comparison.
        *
        * @param    op1
        *           The left operand.
        * @param    operator
        *           One of {@link TextOps#LT}, {@link TextOps#LE},
        *           {@link TextOps#GT}, {@link TextOps#GE},
        *           {@link TextOps#EQ} or {@link TextOps#NE}.
        * @param    op2
        *           The right operand.
        * @param    strength
        *           One of {@link TextOps#RAW}, {@link TextOps#CASE_SENSITIVE}
        *           or {@link TextOps#CASE_INSENSITIVE}.
        *
        * @return   The result of the condition evaluation.
        */
       public static logical compare(Text   op1,
                                     int    operator,
                                     Text   op2,
                                     int    strength)
       {
          return compare(op1, operator, op2, strength, null);
       }

       /**
        * Compares two character values with a runtime-specified strength and
        * operator.  This method will probably generate incorrect results in a
        * DBCS environment since it will use UNICODE as the basis for conversion.
        * The result should match up with the Progress equivalents in the ASCII
        * range.
        * <p>
        * This method does not handle the case of user-specified collation tables
        * as is possible in Progress.
        * <p>
        * In addition, the case-sensitive and case-insensitive strength options
        * are likely to differ in some results since the current implementation
        * really just implements a direct numeric comparison based on each
        * UNICODE character's value (though case-insensitive support is
        * provided but it just converts everything to the same case before
        * implementing the 'raw' lexicographic comparison.
        *
        * @param    op1
        *           The left operand.
        * @param    operator
        *           One of {@link TextOps#LT}, {@link TextOps#LE}, {@link TextOps#GT}, {@link TextOps#GE},
        *           {@link TextOps#EQ} or {@link TextOps#NE}.
        * @param    op2
        *           The right operand.
        * @param    strength
        *           One of {@link TextOps#RAW}, {@link TextOps#CASE_SENSITIVE}, {@link TextOps#CAPS}
        *           or {@link TextOps#CASE_INSENSITIVE}.
        * @param    collation
        *           The used collation. When {@code null}, the default collation is used.
        *
        * @return   The result of the condition evaluation.
        */
       public static logical compare(Text   op1,
                                     int    operator,
                                     Text   op2,
                                     int    strength,
                                     String collation)
       {
          // TODO: add collation support
          // TODO: finish support for MATCHES and BEGINS operators

          if (operator != LT && operator != LE && operator != GT &&
              operator != GE && operator != EQ && operator != NE &&
              operator != MATCHES && operator != BEGINS)
          {
             // unrecognized operator
             return logical.UNKNOWN;
          }

          // unknown value logic
          if (op1.isUnknown() && op2.isUnknown())
          {
             // both are unknown
             if (operator == LE || operator == GE || operator == EQ)
             {
                return logical.of(true);
             }
             else
             {
                return logical.of(false);
             }
          }
          else
          {
             // both are NOT unknown BUT...
             if (op1.isUnknown() || op2.isUnknown())
             {
                // one or the other is unknown
                if (operator == NE)
                {
                   return logical.of(true);
                }
                else
                {
                   return logical.of(false);
                }
             }
          }

          if (operator == BEGINS)
          {
             String oper1 = op1.getValue();
             String oper2 = op2.getValue();
             if (strength == CASE_INSENSITIVE || strength == CAPS)
             {
                oper1 = oper1.toUpperCase();
                oper2 = oper2.toUpperCase();
             }

             return logical.of(oper1.startsWith(oper2));
          }
          else if (operator == MATCHES)
          {
             UnimplementedFeature.missing("COMPARE / MATCHES operator");
             return logical.UNKNOWN;
          }

          int result;
          // TODO: add support for the other strength types: PRIMARY, SECONDARY, TERTIARY, QUATERNARY
          if (strength == RAW || strength == CASE_SENSITIVE)
          {
             // RAW and CASE_SENSITIVE are the same right now
             result = op1.getValue().compareTo(op2.getValue());
          }
          else if (strength == CASE_INSENSITIVE || strength == CAPS)
          {
             result = op1.getValue().compareToIgnoreCase(op2.getValue());
          }
          else
          {
             // unrecognized strength
             return logical.UNKNOWN;
          }

          boolean conclude = false;

          switch (operator)
          {
             case LT:
                conclude = (result < 0);
                break;
             case LE:
                conclude = (result <= 0);
                break;
             case GT:
                conclude = (result > 0);
                break;
             case GE:
                conclude = (result >= 0);
                break;
             case EQ:
                conclude = (result == 0);
                break;
             case NE:
                conclude = (result != 0);
                break;
          }

          return logical.of(conclude);
       }

       /**
        * Implements the <code>+</code> concatenation operator.  If any operand is the
        * <code>unknown value</code>, the result is <code>unknown value</code>. If any operand is
        * case-sensitive, the result will be case-sensitive. <code>character</code> operands in
        * the leftmost position will not be truncated at any embedded <code>null</code> char
        * but all other <code>character</code> operands do in fact get silently truncated. This
        * is how it works in the 4GL!  Since there is no known way to get an embedded
        * <code>null</code> into a <code>longchar</code> or into 4GL string literals, no special
        * <code>null</code> handling is provided for those types.
        * <p>
        * It is valid to pass a mixture of <code>BaseDataType</code> and other object types to this
        * method.  Types that are not <code>character</code> or <code>longchar</code> will not affect
        * the case-sensitivity or unknown status of the result. These other types will have the
        * result of <code>toString()</code> inserted into the returned value.
        * <p>
        * TODO: this should return Text, but for now we let it return character, as this can be used
        * with lots of builtin functions/methods which expect a character parameter, not a Text
        * parameter.
        *
        * @param    list
        *           The operands.  In the 4GL, only <code>character</code>, <code>longchar</code>
        *           and string literals are possible types.  For this reason, converted code would
        *           not be expected to pass anything else.
        *
        * @return   The result of the operator.
        */
       public static character concat(Object ... list)
       {
          StringBuilder sb = new StringBuilder();

          boolean caseSens = false;
          boolean first    = true;
          boolean javaSpacifyNull = false;

          for (Object txt : list)
          {
             inner:
             while (true)
             {
                if (txt instanceof String)
                {
                   javaSpacifyNull = javaSpacifyNull || ((String) txt).indexOf('\0') >= 0;
                   sb.append((String) txt);
                   break inner;
                }

                if (txt instanceof Text)
                {
                   Text op = (Text) txt;

                   // any character or longchar operand that is unknown causes the entire
                   // result to be unknown
                   if (op.isUnknown())
                   {
                      return new character();
                   }

                   // any character or longchar operand that is case-sensitive causes the
                   // entire result to be case-sensitive
                   caseSens = (caseSens || op.isCaseSensitive());

                   if (txt instanceof character && !first)
                   {
                      // the leftmost operand does not truncate at the embedded null, but all
                      // subsequent operands do (the safeValue() call truncates at the embedded null
                      // char)
                      txt = ((character) op).safeValue();

                      // null was already removed, do not alter flag
                      continue inner;
                   }

                   // first character op and any longchar op (no known null handling is needed)
                   txt = op.getValue();

                   // inherit from the actual instance
                   javaSpacifyNull = javaSpacifyNull || op.isJavaSpacifyNull();

                   continue inner;
                }

                if (txt instanceof BaseDataType)
                {
                   txt = new character((BaseDataType) txt);
                   continue inner;
                }

                txt = txt.toString();
             }

             first = false;
          }

          return new character(sb.toString(), caseSens, javaSpacifyNull);
       }

       /**
        * Returns the specified 1-based indexed entry in the passed list.
        *
        * @param    i
        *           The 1-based index into the list.
        * @param    list
        *           The delimited list of entries.
        * @param    delimit
        *           The delimiter character.
        *
        * @return   The entry in the list or the <code>unknown value</code> if
        *           any of the inputs are <code>unknown value</code>.
        */
       public static <T extends Text> T entry(NumberType i,
                                              T          list,
                                              character  delimit)
       throws ErrorConditionException
       {
          if (i.isUnknown() || list.isUnknown() || delimit.isUnknown())
          {
             return (T) instantiateUnknown(list.getClass());
          }

          return (T) entryImpl(list.getClass(),
                               i.intValue(),
                               list.getValue(),
                               safeDelimiter(delimit.getValue()));
       }

       /**
        * Returns the specified 1-based indexed entry in the passed list.
        *
        * @param    i
        *           The 1-based index into the list.
        * @param    list
        *           The delimited list of entries.
        * @param    delimit
        *           The delimiter character.
        *
        * @return   The entry in the list or the <code>unknown value</code> if
        *           any of the inputs are <code>unknown value</code>.
        */
       public static <T extends Text> T entry(double i, T list, character delimit)
       throws ErrorConditionException
       {
          if (list.isUnknown() || delimit.isUnknown())
          {
             return (T) instantiateUnknown(list.getClass());
          }

          return (T) entryImpl(list.getClass(),
                               (int) i,
                               list.getValue(),
                               safeDelimiter(delimit.getValue()));
       }

       /**
        * Returns the specified 1-based indexed entry in the passed list.
        *
        * @param    i
        *           The 1-based index into the list.
        * @param    list
        *           The delimited list of entries.
        * @param    delimit
        *           The delimiter character.
        *
        * @return   The entry in the list or the <code>unknown value</code> if
        *           any of the inputs are <code>unknown value</code>.
        */
       public static character entry(NumberType i, String list, character delimit)
       throws ErrorConditionException
       {
          if (i.isUnknown() || delimit.isUnknown())
          {
             return new character();
          }

          return entryImpl(character.class,
                           i.intValue(),
                           list,
                           safeDelimiter(delimit.getValue()));
       }

       /**
        * Returns the specified 1-based indexed entry in the passed list.
        *
        * @param    i
        *           The 1-based index into the list.
        * @param    list
        *           The delimited list of entries.
        * @param    delimit
        *           The delimiter character.
        *
        * @return   The entry in the list or the <code>unknown value</code> if
        *           any of the inputs are <code>unknown value</code>.
        */
       public static <T extends Text> T entry(NumberType i,
                                              T          list,
                                              String delimit)
       throws ErrorConditionException
       {
          if (i.isUnknown() || list.isUnknown())
          {
             return (T) instantiateUnknown(list.getClass());
          }

          return (T) entryImpl(list.getClass(), i.intValue(),
                               list.getValue(),
                               safeDelimiter(delimit));
       }

       /**
        * Returns the specified 1-based indexed entry in the passed list.
        *
        * @param    i
        *           The 1-based index into the list.
        * @param    list
        *           The delimited list of entries.
        * @param    delimit
        *           The delimiter character.
        *
        * @return   The entry in the list or the <code>unknown value</code> if
        *           any of the inputs are <code>unknown value</code>.
        */
       public static <T extends Text> T entry(double i, T list, String delimit)
       throws ErrorConditionException
       {
          if (list.isUnknown())
          {
             return (T) instantiateUnknown(list.getClass());
          }

          return (T) entryImpl(list.getClass(),
                               (int) i,
                               list.getValue(),
                               safeDelimiter(delimit));
       }

       /**
        * Returns the specified 1-based indexed entry in the passed list.
        *
        * @param    i
        *           The 1-based index into the list.
        * @param    list
        *           The delimited list of entries.
        * @param    delimit
        *           The delimiter character.
        *
        * @return   The entry in the list or the <code>unknown value</code> if
        *           any of the inputs are <code>unknown value</code>.
        */
       public static character entry(NumberType i, String list, String delimit)
       throws ErrorConditionException
       {
          if (i.isUnknown())
          {
             return new character();
          }

          return entryImpl(character.class,
                           i.intValue(),
                           list,
                           safeDelimiter(delimit));
       }

       /**
        * Returns the specified 1-based indexed comma-delimited entry in the
        * passed list.
        *
        * @param    i
        *           The 1-based index into the list.
        * @param    list
        *           The delimited list of entries.
        *
        * @return   The entry in the list or the <code>unknown value</code> if
        *           any of the inputs are <code>unknown value</code>.
        */
       public static <T extends Text> T entry(NumberType i, T list)
       throws ErrorConditionException
       {
          if (i.isUnknown() || list.isUnknown())
          {
             return (T) instantiateUnknown(list.getClass());
          }

          return (T) entryImpl(list.getClass(),
                               i.intValue(),
                               list.getValue(),
                               ',');
       }

       /**
        * Returns the specified 1-based indexed comma-delimited entry in the
        * passed list.
        *
        * @param    i
        *           The 1-based index into the list.
        * @param    list
        *           The delimited list of entries.
        *
        * @return   The entry in the list or the <code>unknown value</code> if
        *           any of the inputs are <code>unknown value</code>.
        */
       public static character entry(NumberType i, String list)
       throws ErrorConditionException
       {
          if (i.isUnknown())
          {
             return new character();
          }

          return entryImpl(character.class, i.intValue(), list, ',');
       }

       /**
        * Returns the specified 1-based indexed comma-delimited entry in the
        * passed list.
        *
        * @param    i
        *           The 1-based index into the list.
        * @param    list
        *           The delimited list of entries.
        *
        * @return   The entry in the list or the <code>unknown value</code> if
        *           any of the inputs are <code>unknown value</code>.
        */
       public static <T extends Text> T entry(double i, T list)
       throws ErrorConditionException
       {
          if (list.isUnknown())
          {
             return (T) instantiateUnknown(list.getClass());
          }

          return (T) entryImpl(list.getClass(), (int) i, list.getValue(), ',');
       }

       /**
        * Returns the specified 1-based indexed comma-delimited entry in the
        * passed list.
        *
        * @param    i
        *           The 1-based index into the list.
        * @param    list
        *           The delimited list of entries.
        *
        * @return   The entry in the list or the <code>unknown value</code> if
        *           any of the inputs are <code>unknown value</code>.
        */
       public static character entry(double i, String list)
       throws ErrorConditionException
       {
          return entry((int) i, list, ',');
       }

       /**
        * Returns the specified 1-based indexed entry in the passed list.
        *
        * @param    i
        *           The 1-based index into the list.
        * @param    list
        *           The delimited list of entries.
        * @param    delimit
        *           The delimiter character.
        *
        * @return   The entry in the list or the <code>unknown value</code> if
        *           any of the inputs are <code>unknown value</code>.
        */
       public static character entry(double i, String list, String delimit)
       throws ErrorConditionException
       {
          return entryImpl(character.class,
                           (int) i,
                           list,
                           safeDelimiter(delimit));
       }

       /**
        * Returns the specified 1-based indexed entry in the passed list.
        *
        * @param    i
        *           The 1-based index into the list.
        * @param    list
        *           The delimited list of entries.
        * @param    delimit
        *           The delimiter character.
        *
        * @return   The entry in the list or the <code>unknown value</code> if
        *           any of the inputs are <code>unknown value</code>.
        */
       public static character entry(double i, String list, char delimit)
       throws ErrorConditionException
       {
          return entryImpl(character.class, (int) i, list, delimit);
       }

       /**
        * Returns the 1-based index of one character expression within another
        * given character expression based on a known starting point for the
        * search.
        *
        * @param    src
        *           The character expression to search in.
        * @param    target
        *           The character expression to search for.
        * @param    i
        *           The starting index to search from.
        *
        * @return   The 1-based index in the source at which the target is found
        *           or 0 if the target is not found or <code>unknown value</code>
        *           if any input is <code>unknown value</code>.
        */
       public static integer indexOf(Text src, Text target, NumberType i)
       {
          if (src.isUnknown() || target.isUnknown() || i.isUnknown())
          {
             return integer.UNKNOWN;
          }

          return indexOf(src.getValue(),
                         target.getValue(),
                         i.doubleValue(),
                         src.isCaseSensitive() || target.isCaseSensitive());
       }

       /**
        * Returns the 1-based index of one character expression within another
        * given character expression based on a known starting point for the
        * search.
        *
        * @param    src
        *           The character expression to search in.
        * @param    target
        *           The character expression to search for.
        * @param    i
        *           The starting index to search from.
        *
        * @return   The 1-based index in the source at which the target is found
        *           or 0 if the target is not found or <code>unknown value</code>
        *           if any input is <code>unknown value</code>.
        */
       public static integer indexOf(String src, Text target, NumberType i)
       {
          if (target.isUnknown() || i.isUnknown())
          {
             return integer.UNKNOWN;
          }

          return indexOf(src,
                         target.getValue(),
                         i.doubleValue(),
                         target.isCaseSensitive());
       }

       /**
        * Returns the 1-based index of one character expression within another
        * given character expression based on a known starting point for the
        * search.
        *
        * @param    src
        *           The character expression to search in.
        * @param    target
        *           The character expression to search for.
        * @param    i
        *           The starting index to search from.
        *
        * @return   The 1-based index in the source at which the target is found
        *           or 0 if the target is not found or <code>unknown value</code>
        *           if any input is <code>unknown value</code>.
        */
       public static integer indexOf(Text src, String target, NumberType i)
       {
          if (src.isUnknown() || i.isUnknown())
          {
             return integer.UNKNOWN;
          }

          return indexOf(src.getValue(),
                         target,
                         i.doubleValue(),
                         src.isCaseSensitive());
       }

       /**
        * Returns the 1-based index of one character expression within another
        * given character expression based on a known starting point for the
        * search.
        *
        * @param    src
        *           The character expression to search in.
        * @param    target
        *           The character expression to search for.
        * @param    i
        *           The starting index to search from.
        *
        * @return   The 1-based index in the source at which the target is found
        *           or 0 if the target is not found or <code>unknown value</code>
        *           if any input is <code>unknown value</code>.
        */
       public static integer indexOf(String src, String target, NumberType i)
       {
          if (i.isUnknown())
          {
             return integer.UNKNOWN;
          }

          return indexOf(src, target, i.doubleValue(), false);
       }

       /**
        * Returns the 1-based index of one character expression within another
        * given character expression based on a known starting point for the
        * search.
        *
        * @param    src
        *           The character expression to search in.
        * @param    target
        *           The character expression to search for.
        * @param    i
        *           The starting index to search from.
        *
        * @return   The 1-based index in the source at which the target is found
        *           or 0 if the target is not found or <code>unknown value</code>
        *           if any input is <code>unknown value</code>.
        */
       public static integer indexOf(Text src, Text target, double i)
       {
          if (src.isUnknown() || target.isUnknown())
          {
             return integer.UNKNOWN;
          }

          return indexOf(src.getValue(),
                         target.getValue(),
                         i,
                         src.isCaseSensitive() || target.isCaseSensitive());
       }

       /**
        * Returns the 1-based index of one character expression within another
        * given character expression based on a known starting point for the
        * search.
        *
        * @param    src
        *           The character expression to search in.
        * @param    target
        *           The character expression to search for.
        * @param    i
        *           The starting index to search from.
        *
        * @return   The 1-based index in the source at which the target is found
        *           or 0 if the target is not found or <code>unknown value</code>
        *           if any input is <code>unknown value</code>.
        */
       public static integer indexOf(String src, Text target, double i)
       {
          if (target.isUnknown())
          {
             return integer.UNKNOWN;
          }

          return indexOf(src, target.getValue(), i, target.isCaseSensitive());
       }

       /**
        * Returns the 1-based index of one character expression within another
        * given character expression based on a known starting point for the
        * search.
        *
        * @param    src
        *           The character expression to search in.
        * @param    target
        *           The character expression to search for.
        * @param    i
        *           The starting index to search from.
        *
        * @return   The 1-based index in the source at which the target is found
        *           or 0 if the target is not found or <code>unknown value</code>
        *           if any input is <code>unknown value</code>.
        */
       public static integer indexOf(Text src, String target, double i)
       {
          if (src.isUnknown())
          {
             return integer.UNKNOWN;
          }

          return indexOf(src.getValue(), target, i, src.isCaseSensitive());
       }

       /**
        * Returns the 1-based index of one character expression within another
        * given character expression.
        *
        * @param    src
        *           The character expression to search in.
        * @param    target
        *           The character expression to search for.
        *
        * @return   The 1-based index in the source at which the target is found
        *           or 0 if the target is not found or <code>unknown value</code>
        *           if any input is <code>unknown value</code>.
        */
       public static integer indexOf(Text src, Text target)
       {
          if (src.isUnknown() || target.isUnknown())
          {
             return integer.UNKNOWN;
          }

          return indexOf(src.getValue(),
                         target.getValue(),
                         1,
                         src.isCaseSensitive() || target.isCaseSensitive());
       }

       /**
        * Returns the 1-based index of one character expression within another
        * given character expression.
        *
        * @param    src
        *           The character expression to search in.
        * @param    target
        *           The character expression to search for.
        *
        * @return   The 1-based index in the source at which the target is found
        *           or 0 if the target is not found or <code>unknown value</code>
        *           if any input is <code>unknown value</code>.
        */
       public static integer indexOf(String src, Text target)
       {
          if (target.isUnknown())
          {
             return integer.UNKNOWN;
          }

          return indexOf(src, target.getValue(), 1, target.isCaseSensitive());
       }

       /**
        * Returns the 1-based index of one character expression within another
        * given character expression.
        *
        * @param    src
        *           The character expression to search in.
        * @param    target
        *           The character expression to search for.
        *
        * @return   The 1-based index in the source at which the target is found
        *           or 0 if the target is not found or <code>unknown value</code>
        *           if any input is <code>unknown value</code>.
        */
       public static integer indexOf(Text src, String target)
       {
          if (src.isUnknown())
          {
             return integer.UNKNOWN;
          }

          return indexOf(src.getValue(), target, 1, src.isCaseSensitive());
       }

       /**
        * Returns the 1-based index of one character expression within another
        * given character expression based on a known starting point for the
        * search.
        *
        * @param    src
        *           The character expression to search in.
        * @param    target
        *           The character expression to search for.
        * @param    ii
        *           The (1-based) starting index from which to search.
        *
        * @return   The 1-based index in the source at which the target is found
        *           or 0 if the target is not found.
        */
       public static integer indexOf(String src, String target, double ii)
       {
          return indexOf(src, target, ii, false);
       }

       /**
        * Returns the 1-based index of one character expression within another
        * given character expression.
        *
        * @param    src
        *           The character expression to search in.
        * @param    target
        *           The character expression to search for.
        *
        * @return   The 1-based index in the source at which the target is found
        *           or 0 if the target is not found.
        */
       public static integer indexOf(String src, String target)
       {
          return indexOf(src, target, 1, false);
       }

       /**
        * Returns the 1-based index of one character expression within another
        * given character expression based on a known starting point for the
        * search.
        *
        * @param    src
        *           The character expression to search in.
        * @param    target
        *           The character expression to search for.
        * @param    ii
        *           The (1-based) starting index from which to search.
        * @param    caseSensitive
        *           <code>true</code> if case must be considered in the search;
        *           <code>false</code> if the search is case insensitive.
        *
        * @return   The 1-based index in the source at which the target is found
        *           or 0 if the target is not found.
        */
       public static integer indexOf(String src,
                                     String target,
                                     double ii,
                                     boolean caseSensitive)
       {
          int i = (int) ii;

          if (src.length() == 0 || target.length() == 0 || i < 1)
          {
             return integer.of(0);
          }

          if (!caseSensitive)
          {
             // TODO: optimisation: in case of case insensitive lookup, we should convert to lowercase
             //       only the characters that are really compared from both strings
             src = src.toLowerCase();
             target = target.toLowerCase();
          }

          return integer.of(src.indexOf(target, i - 1) + 1);
       }

       /**
        * Returns the last 1-based index of one character expression within
        * another given character expression (the search starts at the end
        * of the string and moves toward the beginning) starting at a specified
        * starting index.
        *
        * @param    src
        *           The character expression to search in.
        * @param    target
        *           The character expression to search for.
        * @param    i
        *           The starting index to search from.
        *
        * @return   The 1-based index in the source at which the target is found
        *           or 0 if the target is not found or <code>unknown value</code>
        *           if any input is <code>unknown value</code>.
        */
       public static integer lastIndexOf(Text src, Text target, NumberType i)
       {
          if (src.isUnknown() || target.isUnknown() || i.isUnknown())
          {
             return integer.UNKNOWN;
          }

          return lastIndexOf(src.getValue(), target.getValue(), i.intValue());
       }

       /**
        * Returns the last 1-based index of one character expression within
        * another given character expression (the search starts at the end
        * of the string and moves toward the beginning) starting at a specified
        * starting index.
        *
        * @param    src
        *           The character expression to search in.
        * @param    target
        *           The character expression to search for.
        * @param    i
        *           The starting index to search from.
        *
        * @return   The 1-based index in the source at which the target is found
        *           or 0 if the target is not found or <code>unknown value</code>
        *           if any input is <code>unknown value</code>.
        */
       public static integer lastIndexOf(String src, Text target, NumberType i)
       {
          if (target.isUnknown() || i.isUnknown())
          {
             return integer.UNKNOWN;
          }

          return lastIndexOf(src, target.getValue(), i.intValue());
       }

       /**
        * Returns the last 1-based index of one character expression within
        * another given character expression (the search starts at the end
        * of the string and moves toward the beginning) starting at a specified
        * starting index.
        *
        * @param    src
        *           The character expression to search in.
        * @param    target
        *           The character expression to search for.
        * @param    i
        *           The starting index to search from.
        *
        * @return   The 1-based index in the source at which the target is found
        *           or 0 if the target is not found or <code>unknown value</code>
        *           if any input is <code>unknown value</code>.
        */
       public static integer lastIndexOf(Text src, String target, NumberType i)
       {
          if (src.isUnknown() || i.isUnknown())
          {
             return integer.UNKNOWN;
          }

          return lastIndexOf(src.getValue(), target, i.intValue());
       }

       /**
        * Returns the last 1-based index of one character expression within
        * another given character expression (the search starts at the end
        * of the string and moves toward the beginning) starting at a specified
        * starting index.
        *
        * @param    src
        *           The character expression to search in.
        * @param    target
        *           The character expression to search for.
        * @param    i
        *           The starting index to search from.
        *
        * @return   The 1-based index in the source at which the target is found
        *           or 0 if the target is not found or <code>unknown value</code>
        *           if any input is <code>unknown value</code>.
        */
       public static integer lastIndexOf(String src, String target, NumberType i)
       {
          if (i.isUnknown())
          {
             return integer.UNKNOWN;
          }

          return lastIndexOf(src, target, i.intValue());
       }

       /**
        * Returns the last 1-based index of one character expression within
        * another given character expression (the search starts at the end
        * of the string and moves toward the beginning) starting at a specified
        * starting index.
        *
        * @param    src
        *           The character expression to search in.
        * @param    target
        *           The character expression to search for.
        * @param    i
        *           The starting index to search from.
        *
        * @return   The 1-based index in the source at which the target is found
        *           or 0 if the target is not found or <code>unknown value</code>
        *           if any input is <code>unknown value</code>.
        */
       public static integer lastIndexOf(Text src, Text target, double i)
       {
          if (src.isUnknown() || target.isUnknown())
          {
             return integer.UNKNOWN;
          }

          return lastIndexOf(src.getValue(), target.getValue(), (int) i);
       }

       /**
        * Returns the last 1-based index of one character expression within
        * another given character expression (the search starts at the end
        * of the string and moves toward the beginning) starting at a specified
        * starting index.
        *
        * @param    src
        *           The character expression to search in.
        * @param    target
        *           The character expression to search for.
        * @param    i
        *           The starting index to search from.
        *
        * @return   The 1-based index in the source at which the target is found
        *           or 0 if the target is not found or <code>unknown value</code>
        *           if any input is <code>unknown value</code>.
        */
       public static integer lastIndexOf(String src, Text target, double i)
       {
          if (target.isUnknown())
          {
             return integer.UNKNOWN;
          }

          return lastIndexOf(src, target.getValue(), (int) i);
       }

       /**
        * Returns the last 1-based index of one character expression within
        * another given character expression (the search starts at the end
        * of the string and moves toward the beginning) starting at a specified
        * starting index.
        *
        * @param    src
        *           The character expression to search in.
        * @param    target
        *           The character expression to search for.
        * @param    i
        *           The starting index to search from.
        *
        * @return   The 1-based index in the source at which the target is found
        *           or 0 if the target is not found or <code>unknown value</code>
        *           if any input is <code>unknown value</code>.
        */
       public static integer lastIndexOf(Text src, String target, double i)
       {
          if (src.isUnknown())
          {
             return integer.UNKNOWN;
          }

          return lastIndexOf(src.getValue(), target, (int) i);
       }

       /**
        * Returns the last 1-based index of one character expression within
        * another given character expression (the search starts at the end
        * of the string and moves toward the beginning).
        *
        * @param    src
        *           The character expression to search in.
        * @param    target
        *           The character expression to search for.
        *
        * @return   The 1-based index in the source at which the target is found
        *           or 0 if the target is not found or <code>unknown value</code>
        *           if any input is <code>unknown value</code>.
        */
       public static integer lastIndexOf(Text src, Text target)
       {
          if (src.isUnknown() || target.isUnknown())
          {
             return integer.UNKNOWN;
          }

          return lastIndexOf(src.getValue(), target.getValue());
       }

       /**
        * Returns the last 1-based index of one character expression within
        * another given character expression (the search starts at the end
        * of the string and moves toward the beginning).
        *
        * @param    src
        *           The character expression to search in.
        * @param    target
        *           The character expression to search for.
        *
        * @return   The 1-based index in the source at which the target is found
        *           or 0 if the target is not found or <code>unknown value</code>
        *           if any input is <code>unknown value</code>.
        */
       public static integer lastIndexOf(String src, Text target)
       {
          if (target.isUnknown())
          {
             return integer.UNKNOWN;
          }

          return lastIndexOf(src, target.getValue());
       }

       /**
        * Returns the last 1-based index of one character expression within
        * another given character expression (the search starts at the end
        * of the string and moves toward the beginning).
        *
        * @param    src
        *           The character expression to search in.
        * @param    target
        *           The character expression to search for.
        *
        * @return   The 1-based index in the source at which the target is found
        *           or 0 if the target is not found or <code>unknown value</code>
        *           if any input is <code>unknown value</code>.
        */
       public static integer lastIndexOf(Text src, String target)
       {
          if (src.isUnknown())
          {
             return integer.UNKNOWN;
          }

          return lastIndexOf(src.getValue(), target);
       }

       /**
        * Returns the last 1-based index of one character expression within
        * another given character expression (the search starts at the end
        * of the string and moves toward the beginning) starting at a specified
        * starting index.
        * <p>
        * If an index is specified, this is a "to" index, so the whole pattern should
        * fit in the first i characters.
        *
        * @param    src
        *           The character expression to search in.
        * @param    target
        *           The character expression to search for.
        * @param    ii
        *           The starting index to search from.
        *
        * @return   The 1-based index in the source at which the target is found
        *           or 0 if the target is not found.
        */
       public static integer lastIndexOf(String src, String target, double ii)
       {
          int i = (int) ii;
          int targetLng = target.length();

          if (src.length() == 0 || targetLng == 0 || i < 1)
          {
             return integer.of(0);
          }

          // there is a mismatch in semantics between 4GL r-index and Java lastIndexOf
          // In 4GL: i is a "to" index. Finding "ab" in "ab" is possible only for i = 2 (1-based index)
          // In Java: i is a "from" index. Finding "ab" in "ab" is possible for i = 0 and i = 1 (0-based index)
          int lastIndex = i - targetLng;
          if (lastIndex < 0)
          {
             return integer.of(0);
          }
          return integer.of(src.lastIndexOf(target, lastIndex) + 1);
       }

       /**
        * Returns the last 1-based index of one character expression within
        * another given character expression (the search starts at the end
        * of the string and moves toward the beginning).
        *
        * @param    src
        *           The character expression to search in.
        * @param    target
        *           The character expression to search for.
        *
        * @return   The 1-based index in the source at which the target is found
        *           or 0 if the target is not found.
        */
       public static integer lastIndexOf(String src, String target)
       {
          return lastIndexOf(src, target, src.length() + 1);
       }

       /**
        * Trims the list of contiguous trim characters from the left of an input
        * string.
        * <p>
        * Trim operations are performed based on the case-sensitivity of only
        * the input parameter, the trim character list case-sensitivity is
        * ignored.  Likewise, if the input parameter is case-sensitive, then
        * the returned instance will be case-sensitive.
        *
        * @param    input
        *           The string to trim.
        * @param    list
        *           The list of characters to trim.
        *
        * @return   The trimmed result.
        */
       public static <T extends Text> T leftTrim(T input, character list)
       {
          if (input.isUnknown() || list.isUnknown())
          {
             return (T) instantiateUnknown(input.getClass());
          }

          return (T) leftTrimImpl(input.getClass(),
                                  input.getValue(),
                                  list.getValue(),
                                  input.isCaseSensitive(),
                                  getCodePage(input),
                                  input.isJavaSpacifyNull());
       }

       /**
        * Trims the list of contiguous trim characters from the left of an input
        * string.
        * <p>
        * Trim operations are performed based on the case-sensitivity of the
        * the input parameter. If the input parameter is case-sensitive, then
        * the returned instance will be case-sensitive.
        *
        * @param    input
        *           The string to trim.
        * @param    list
        *           The list of characters to trim.
        *
        * @return   The trimmed result.
        */
       public static <T extends Text> T leftTrim(T input, String list)
       {
          if (input.isUnknown())
          {
             return (T) instantiateUnknown(input.getClass());
          }

          return (T) leftTrimImpl(input.getClass(),
                                  input.getValue(),
                                  list,
                                  input.isCaseSensitive(),
                                  getCodePage(input),
                                  input.isJavaSpacifyNull());
       }

       /**
        * Trims the list of contiguous trim characters from the left of an input
        * string.
        * <p>
        * Trim operations are performed case-insensitively.
        *
        * @param    input
        *           The string to trim.
        * @param    list
        *           The list of characters to trim.
        *
        * @return   The trimmed result.
        */
       public static character leftTrim(String input, character list)
       {
          if (list.isUnknown())
          {
             return new character();
          }

          return leftTrimImpl(character.class, input, list.getValue(), false, null, true);
       }

       /**
        * Trims any contiguous space, tab, line feed and carriage return from the
        * left of an input string.
        * <p>
        * Trim operations are performed based on the case-sensitivity of the
        * input parameter.  Likewise, if the input parameter is case-sensitive,
        * then the returned instance will be case-sensitive.
        *
        * @param    input
        *           The string to trim.
        *
        * @return   The trimmed result.
        */
       public static <T extends Text> T leftTrim(T input)
       {
          if (input.isUnknown())
          {
             return (T) instantiateUnknown(input.getClass());
          }

          return (T) leftTrimImpl(input.getClass(),
                                  input.getValue(),
                                  null,
                                  input.isCaseSensitive(),
                                  getCodePage(input),
                                  input.isJavaSpacifyNull());
       }

       /**
        * Trims the list of contiguous trim characters from the left of an input
        * string.  If the list of trim characters is <code>null</code>
        * the method defaults to space, tab, line feed and carriage return.
        *
        * @param    input
        *           The string to trim.
        * @param    list
        *           The list of characters to trim or <code>null</code> to
        *           use the default list.
        * @param    caseSens
        *           Specifies the case-sensitivity of the trim operation AND of
        *           the returned instance.
        *
        * @return   The trimmed result.
        */
       public static character leftTrim(String  input,
                                        String  list,
                                        boolean caseSens)
       {
          return leftTrimImpl(character.class,
                              input,
                              list,
                              caseSens,
                              null,
                              true);
       }

       /**
        * Trims the list of contiguous trim characters from the left of an input
        * string.  If the list of trim characters is <code>null</code>
        * the method defaults to space, tab, line feed and carriage return.
        * <p>
        * Trim operations and the returned instance will be case-insensitive.
        *
        * @param    input
        *           The string to trim.
        * @param    list
        *           The list of characters to trim or <code>null</code> to
        *           use the default list.
        *
        * @return   The trimmed result.
        */
       public static character leftTrim(String input, String list)
       {
          return leftTrim(input, list, false);
       }

       /**
        * Trims any contiguous space, tab, line feed and carriage return from the
        * left of an input string.
        * <p>
        * Trim operations and the returned instance will be case-insensitive.
        *
        * @param    input
        *           The string to trim.
        *
        * @return   The trimmed result.
        */
       public static character leftTrim(String input)
       {
          return leftTrim(input, (String) null);
       }

       /**
        * Returns the length of the given <code>character</code> instance in
        * characters.
        *
        * @param    c
        *           The instance to test.
        *
        * @return   The number of characters in the given instance or -1 if the
        *           instance is <code>unknown value</code>.
        */
       public static int lengthOf(Text c)
       {
          if (c.isUnknown())
             return -1;

          return c.lengthInt();
       }

       /**
        * Returns the length of the given <code>character</code> instance in
        * characters.
        *
        * @param    c
        *           The instance to test.
        *
        * @return   The number of characters in the given instance
        *           <code>unknown value</code> if the instance is
        *           <code>unknown value</code>.
        */
       public static integer length(Text c)
       {
          if (c.isUnknown())
             return integer.UNKNOWN;

          return integer.of(c.lengthInt());
       }

       /**
        * Returns the length of the given <code>character</code> instance, using the type parameter
        * to determine the measurement.
        *
        * @param    c
        *           The instance to test.
        * @param    type
        *           The measurement mode.
        *           "character" is supposed to be the default measurement unit
        *           for the LENGTH built-in function (when type is null)
        *
        * @return   The length of the given text
        */
       public static integer length(Text c, character type)
       {
          if (type == null)
          {
             return TextOps.length(c.getValue()); // calling directly the dedicated method
          }

          if (type.isUnknown())
          {
             return integer.UNKNOWN;
          }

          return length(c, type.toStringMessage());
       }

       /**
        * Returns the length of the given <code>character</code> instance, using the type parameter
        * to determine the measurement.
        *
        * @param    c
        *           The instance to test.
        * @param    type
        *           The measurement mode.
        *           "character" is supposed to be the default measurement unit
        *           for the LENGTH built-in function (when type is null)
        *
        * @return   The length of the given text
        */
       public static integer length(Text c, String type)
       {
          if (c == null || c.isUnknown())
          {
             return integer.UNKNOWN;
          }

          if (type == null)
          {
             return TextOps.length(c.getValue()); // calling directly the dedicated method
          }

          return TextOps.length(c.getValue(), type);
       }

       /**
        * Returns the length of the given <code>character</code> instance, using the type parameter
        * to determine the measurement.
        *
        * @param    c
        *           The instance to test.
        * @param    type
        *           The measurement mode.
        *           "character" is supposed to be the default measurement unit
        *           for the LENGTH built-in function (when type is null)
        *
        * @return   The length of the given text
        */
       public static integer length(String c, character type)
       {
          if (type == null)
          {
             return TextOps.length(c); // calling directly the dedicated method
          }

          if (type.isUnknown())
          {
             return integer.UNKNOWN;
          }

          return TextOps.length(c, type.toStringMessage());
       }

       /**
        * Returns the length of the given string instance in characters.
        *
        * @param    c
        *           The instance to test.
        *
        * @return   The number of characters in the given instance.
        */
       public static integer length(String c)
       {
          return integer.of(c.length());
       }

       /**
        * Returns the 1-based index of a specified entry in the passed
        * list with a user-specified delimiter. The search will be case-sensitive
        * if either argument is case-sensitive.
        *
        * @param    c
        *           The entry to find in the list.
        * @param    list
        *           The delimited list of entries.
        * @param    delim
        *           The delimiter character used to parse the list into entries.
        *
        * @return   The 1-based index into the list or 0 if no match is found or
        *           the <code>unknown value</code> if any of the inputs are
        *           <code>unknown value</code>.
        */
       public static integer lookup(character c, Text list, character delim)
       {
          if (c.isUnknown() || list.isUnknown() || delim.isUnknown())
          {
             return integer.UNKNOWN;
          }

          boolean cs = (c.isCaseSensitive() || list.isCaseSensitive());

          return lookup(c.getValue(),
                        list.getValue(),
                        safeDelimiter(delim.getValue()),
                        cs);
       }

       /**
        * Returns the 1-based index of a specified entry in the passed
        * list with a user-specified delimiter. The search will be case-sensitive
        * if either argument is case-sensitive.
        *
        * @param    c
        *           The entry to find in the list.
        * @param    list
        *           The delimited list of entries.
        * @param    delim
        *           The delimiter character used to parse the list into entries.
        *
        * @return   The 1-based index into the list or 0 if no match is found or
        *           the <code>unknown value</code> if any of the inputs are
        *           <code>unknown value</code>.
        */
       public static integer lookup(character c, String list, character delim)
       {
          if (c.isUnknown() || delim.isUnknown())
          {
             return integer.UNKNOWN;
          }

          return lookup(c.getValue(),
                        list,
                        safeDelimiter(delim.getValue()),
                        c.isCaseSensitive());
       }

       /**
        * Returns the 1-based index of a specified entry in the passed
        * list with a user-specified delimiter. The search will be case-sensitive
        * if either argument is case-sensitive.
        *
        * @param    c
        *           The entry to find in the list.
        * @param    list
        *           The delimited list of entries.
        * @param    delim
        *           The delimiter character used to parse the list into entries.
        *
        * @return   The 1-based index into the list or 0 if no match is found or
        *           the <code>unknown value</code> if any of the inputs are
        *           <code>unknown value</code>.
        */
       public static integer lookup(String c, Text list, character delim)
       {
          if (list.isUnknown() || delim.isUnknown())
          {
             return integer.UNKNOWN;
          }

          return lookup(c,
                        list.getValue(),
                        safeDelimiter(delim.getValue()),
                        list.isCaseSensitive());
       }

       /**
        * Returns the 1-based index of a specified entry in the passed
        * list with a user-specified delimiter. The search will be case-sensitive
        * if either argument is case-sensitive.
        *
        * @param    c
        *           The entry to find in the list.
        * @param    list
        *           The delimited list of entries.
        * @param    delim
        *           The delimiter character used to parse the list into entries.
        *
        * @return   The 1-based index into the list or 0 if no match is found or
        *           the <code>unknown value</code> if any of the inputs are
        *           <code>unknown value</code>.
        */
       public static integer lookup(character c, Text list, String delim)
       {
          if (c.isUnknown() || list.isUnknown())
          {
             return integer.UNKNOWN;
          }

          boolean cs = (c.isCaseSensitive() || list.isCaseSensitive());

          return lookup(c.getValue(), list.getValue(), safeDelimiter(delim), cs);
       }

       /**
        * Returns the 1-based index of a specified entry in the passed
        * comma-delimited list.  The search will be case-sensitive if either
        * argument is case-sensitive.
        *
        * @param    c
        *           The entry to find in the list.
        * @param    list
        *           The delimited list of entries.
        *
        * @return   The 1-based index into the list or 0 if no match is found or
        *           the <code>unknown value</code> if any of the inputs are
        *           <code>unknown value</code>.
        */
       public static integer lookup(character c, Text list)
       {
          if (c.isUnknown() || list.isUnknown())
          {
             return integer.UNKNOWN;
          }

          boolean cs = (c.isCaseSensitive() || list.isCaseSensitive());

          return lookup(c.getValue(), list.getValue(), ',', cs);
       }

       /**
        * Returns the 1-based index of a specified entry in the passed
        * comma-delimited list.  The search will be case-sensitive if either
        * argument is case-sensitive.
        *
        * @param    c
        *           The entry to find in the list.
        * @param    list
        *           The delimited list of entries.
        *
        * @return   The 1-based index into the list or 0 if no match is found or
        *           the <code>unknown value</code> if any of the inputs are
        *           <code>unknown value</code>.
        */
       public static integer lookup(character c, String list)
       {
          if (c.isUnknown())
          {
             return integer.UNKNOWN;
          }

          return lookup(c.getValue(), list, ',', c.isCaseSensitive());
       }

       /**
        * Returns the 1-based index of a specified entry in the passed
        * comma-delimited list.  The search will be case-sensitive if either
        * argument is case-sensitive.
        *
        * @param    c
        *           The entry to find in the list.
        * @param    list
        *           The delimited list of entries.
        *
        * @return   The 1-based index into the list or 0 if no match is found or
        *           the <code>unknown value</code> if any of the inputs are
        *           <code>unknown value</code>.
        */
       public static integer lookup(NumberType c, String list)
       {
          if (c.isUnknown())
          {
             return integer.UNKNOWN;
          }

          return lookup(c.toStringMessage(), list, ',', false);
       }

       /**
        * Returns the 1-based index of a specified entry in the passed
        * comma-delimited list.  The search will be case-sensitive if either
        * argument is case-sensitive.
        *
        * @param    c
        *           The entry to find in the list.
        * @param    list
        *           The delimited list of entries.
        *
        * @return   The 1-based index into the list or 0 if no match is found or
        *           the <code>unknown value</code> if any of the inputs are
        *           <code>unknown value</code>.
        */
       public static integer lookup(String c, Text list)
       {
          if (list.isUnknown())
          {
             return integer.UNKNOWN;
          }

          return lookup(c, list.getValue(), ',', list.isCaseSensitive());
       }

       /**
        * Returns the 1-based index of a specified entry in the passed
        * list with a user-specified delimiter.  The search will be done
        * case-insensitively.
        *
        * @param    c
        *           The entry to find in the list.
        * @param    list
        *           The delimited list of entries.
        * @param    delim
        *           The delimiter character used to parse the list into entries.
        *
        * @return   The 1-based index into the list or 0 if no match is found.
        */
       public static integer lookup(String c, String list, char delim)
       {
          return lookup(c, list, delim, false);
       }

       /**
        * Returns the 1-based index of a specified entry in the passed
        * list with a user-specified delimiter.
        *
        * @param    c
        *           The entry to find in the list.
        * @param    list
        *           The delimited list of entries.
        * @param    delim
        *           The delimiter character used to parse the list into entries.
        * @param    cs
        *           <code>true</code> specifies the match is to be done with
        *           case-sensitivity.
        *
        * @return   The 1-based index into the list or 0 if no match is found.
        */
       public static integer lookup(String c, String list, char delim, boolean cs)
       {
          // split up the list (make sure all possible entries are included,
          // even empty trailing entries --> use -1 as the limit to force this)
          // using our own implementation of the split
          String[] array = splitInt(list, delim);

          // only use up to the first instance of the delimiter in the search
          // pattern
          int    idx    = c.indexOf(delim);
          String search = idx < 0 ? c : c.substring(0, idx);

          // default return value
          int index  = 0;

          // do our search
          for (int i = 0; i < array.length; i++)
          {
             if (cs ? array[i].equals(search) : array[i].equalsIgnoreCase(search))
             {
                // match found!  convert from 0-based to 1-based
                index = i + 1;
                break;
             }
          }

          return integer.of(index);
       }

       /**
        * Returns the 1-based index of a specified entry in the passed
        * comma-delimited list.  The search will be done case-insensitively.
        *
        * @param    c
        *           The entry to find in the list.
        * @param    list
        *           The delimited list of entries.
        *
        * @return   The 1-based index into the list or 0 if no match is found or
        *           the <code>unknown value</code> if any of the inputs are
        *           <code>unknown value</code>.
        */
       public static integer lookup(String c, String list)
       {
          return lookup(c, list, ',', false);
       }

       /**
        * Returns the 1-based index of a specified entry in the passed
        * list with a user-specified delimiter. The search will be case-sensitive
        * if either argument is case-sensitive.
        *
        * @param    c
        *           The entry to find in the list.
        * @param    list
        *           The delimited list of entries.
        * @param    delim
        *           The delimiter character used to parse the list into entries.
        *
        * @return   The 1-based index into the list or 0 if no match is found or
        *           the <code>unknown value</code> if any of the inputs are
        *           <code>unknown value</code>.
        */
       public static integer lookup(String c, String list, String delim)
       {
          return lookup(c, list, safeDelimiter(delim), false);
       }

       /**
        * Returns the 1-based index of a specified entry in the passed
        * list with a user-specified delimiter. The search will be case-sensitive
        * if either argument is case-sensitive.
        *
        * @param    c
        *           The entry to find in the list.
        * @param    list
        *           The delimited list of entries.
        * @param    delim
        *           The delimiter character used to parse the list into entries.
        *
        * @return   The 1-based index into the list or 0 if no match is found or
        *           the <code>unknown value</code> if any of the inputs are
        *           <code>unknown value</code>.
        */
       public static integer lookup(String c, Text list, String delim)
       {
          if (list.isUnknown())
          {
             return integer.UNKNOWN;
          }

          return lookup(c,
                        list.getValue(),
                        safeDelimiter(delim),
                        list.isCaseSensitive());
       }

       /**
        * Returns the 1-based index of a specified entry in the passed
        * list with a user-specified delimiter. The search will be case-sensitive
        * if either argument is case-sensitive.
        *
        * @param    c
        *           The entry to find in the list.
        * @param    list
        *           The delimited list of entries.
        * @param    delim
        *           The delimiter character used to parse the list into entries.
        *
        * @return   The 1-based index into the list or 0 if no match is found or
        *           the <code>unknown value</code> if any of the inputs are
        *           <code>unknown value</code>.
        */
       public static integer lookup(character c, String list, String delim)
       {
          if (c.isUnknown())
          {
             return integer.UNKNOWN;
          }

          return lookup(c.getValue(),
                        list,
                        safeDelimiter(delim),
                        c.isCaseSensitive());
       }

       /**
        * Returns the 1-based index of a specified entry in the passed
        * list with a user-specified delimiter. The search will be case-sensitive
        * if either argument is case-sensitive.
        *
        * @param    c
        *           The entry to find in the list.
        * @param    list
        *           The delimited list of entries.
        * @param    delim
        *           The delimiter character used to parse the list into entries.
        *
        * @return   The 1-based index into the list or 0 if no match is found or
        *           the <code>unknown value</code> if any of the inputs are
        *           <code>unknown value</code>.
        */
       public static integer lookup(String c, String list, character delim)
       {
          if (delim.isUnknown())
          {
             return integer.UNKNOWN;
          }

          return lookup(c, list, safeDelimiter(delim.getValue()), false);
       }

       /**
        * Tests if the first operand matches a regular expression that is provided by the caller,
        * basing case sensitivity on the left operand.
        * <p>
        * The Progress style matching pattern passed as a parameter is converted to a regular
        * expression using {@link SQLHelper#convertToRegEx} during this method.
        *
        * @param   op
        *          String to check for a match.
        * @param   pattern
        *          Progress style matching pattern.
        * @param   windows
        *          <code>true</code> if the backslash does not have special meaning. On Unix, P4GL
        *          the backslash (\)is an escape character, like tilde (\).
        *
        * @return  <code>true</code> if op matches the regular expression or <code>unknown</code>
        *          value if either operand is <code>unknown</code>.
        */
       public static logical matches(Text op, character pattern, boolean windows)
       {
          if (op.isUnknown() || pattern.isUnknown())
          {
             return logical.UNKNOWN;
          }

          boolean caseSensitive = op.isCaseSensitive() || pattern.isCaseSensitive();
          return matches(op.getValue(), pattern.getValue(), caseSensitive, windows);
       }

       /**
        * Tests if the first operand matches a regular expression that is provided by the caller,
        * basing case sensitivity on the left operand.
        * <p>
        * The Progress style matching pattern passed as a parameter is converted to a regular
        * expression using {@link SQLHelper#convertToRegEx} during this method.
        *
        * @param    op
        *           String to check for a match.
        * @param    pattern
        *           Progress style matching pattern.
        * @param   windows
        *          <code>true</code> if the backslash does not have special meaning. On Unix, P4GL
        *          the backslash (\)is an escape character, like tilde (\).
        *
        * @return  <code>true</code> if op matches the regular expression or <code>unknown</code>
        *          value if the left operand is <code>unknown</code>.
        */
       public static logical matches(Text op, String pattern, boolean windows)
       {
          if (op.isUnknown())
          {
             return logical.UNKNOWN;
          }

          return matches(op.getValue(), pattern, op.isCaseSensitive(), windows);
       }

       /**
        * Tests if the first operand matches a regular expression that is provided by the caller,
        * case-insensitively.
        * <p>
        * The Progress style matching pattern passed as a parameter is converted to a regular
        * expression using {@link SQLHelper#convertToRegEx} during this method.
        *
        * @param   op
        *          String to check for a match.
        * @param   pattern
        *          Progress style matching pattern.
        * @param   windows
        *          <code>true</code> if the backslash does not have special meaning. On Unix, P4GL
        *          the backslash (\)is an escape character, like tilde (\).
        *
        * @return  <code>true</code> if op matches the regular expression or <code>unknown</code>
        *          value if the right operand is <code>unknown</code>.
        */
       public static logical matches(String op, character pattern, boolean windows)
       {
          if (pattern.isUnknown())
          {
             return logical.UNKNOWN;
          }

          return matches(op, pattern.getValue(), pattern.isCaseSensitive(), windows);
       }

       /**
        * Tests if the first operand matches a regular expression that is provided by the caller,
        * ignoring the case of the operand and the pattern.
        * <p>
        * The Progress style matching pattern passed as a parameter is converted to a regular
        * expression using {@link SQLHelper#convertToRegEx} during this method.
        *
        * @param   op
        *          String to check for a match.
        * @param   pattern
        *          Progress style matching pattern.
        * @param   windows
        *          <code>true</code> if the backslash does not have special meaning. On Unix, P4GL
        *          the backslash (\)is an escape character, like tilde (\).
        *
        * @return  <code>true</code> if op matches the regular expression.
        */
       public static logical matches(String op, String pattern, boolean windows)
       {
          return matches(op, pattern, false, windows);
       }

       /**
        * Tests if the first operand matches a regular expression that is provided by the caller,
        * honoring the case sensitivity preference of the caller.
        * <p>
        * The Progress style matching pattern passed as a parameter is converted to a regular
        * expression using {@link SQLHelper#convertToRegEx} during this method.
        *
        * @param   op
        *          String to check for a match.
        * @param   pattern
        *          Progress style matching pattern.
        * @param   caseSens
        *          <code>true</code> if a case-sensitive comparison should be used.
        * @param   windows
        *          <code>true</code> if the backslash does not have special meaning. On Unix, P4GL
        *          the backslash (\)is an escape character, like tilde (\).
        *
        * @return  <code>true</code> if op matches the regular expression.
        */
       public static logical matches(String op, String pattern, boolean caseSens, boolean windows)
       {
          String up = caseSens ? op : op.toUpperCase();
          pattern = caseSens ? pattern : pattern.toUpperCase();

          boolean result = matchImpl(up, SQLHelper.convertToRegEx(pattern, windows));
          return logical.of(result);
       }

       /**
        * Tests if the first operand matches a regular expression that is provided by the caller.
        * <p>
        * This form of the method is unix-escape independent because Progress style matching pattern
        * passed as a parameter is already transformed at conversion time to a regular expression.
        * By default, in P4GL character operations are case insensitive.
        *
        * @param   op
        *          String to check for a match.
        * @param   regExp
        *          The RegExp pattern as obtained using {@link SQLHelper#convertToRegEx} from a
        *          Progress style matching pattern known at conversion time.
        *
        * @return  <code>true</code> if op matches the regular expression.
        */
       public static logical matches(String op, String regExp)
       {
          op = op.toUpperCase();
          regExp = regExp.toUpperCase();

          return logical.of(matchImpl(op, regExp));
       }

       /**
        * Tests if the first operand matches a regular expression that is provided by the caller.
        * <p>
        * This form of the method is unix-escape independent because Progress style matching pattern
        * passed as a parameter is already transformed at conversion time to a regular expression.
        *
        * @param   op
        *          String to check for a match.
        * @param   regExp
        *          The RegExp pattern as obtained using {@link SQLHelper#convertToRegEx} from a
        *          Progress style matching pattern known at conversion time.
        *
        * @return  <code>true</code> if op matches the regular expression.
        */
       public static logical matches(Text op, String regExp)
       {
          if (op.isUnknown())
          {
             return logical.UNKNOWN;
          }

          String toMatch = op.getValue();
          if (!op.isCaseSensitive())
          {
             toMatch = toMatch.toUpperCase();
             regExp = regExp.toUpperCase();
          }
          return logical.of(matchImpl(toMatch, regExp));
       }

       /**
        * Tests if the current PROGRESS user is allowed in the list.
        * Since in the P2J environment the security subsystem is based on the
        * Security Manager, this call simply catches the overlooked CAN-DO call
        * in the PROGRESS code.
        *
        * @param    list
        *           comma-separated list of user IDs, possibly with ! and *
        *           modifiers.
        *
        * @return   never
        * @throws   IllegalArgumentException
        *           to catch the untranslated CAN-DO call
        */
       public static logical matchesList(Text list)
       throws IllegalArgumentException
       {
          throw new IllegalArgumentException("untranslated CAN-DO call");
       }

       /**
        * Tests if the current PROGRESS user is allowed in the list.
        * Since in the P2J environment the security subsystem is based on the
        * Security Manager, this call simply catches the overlooked CAN-DO call
        * in the PROGRESS code.
        *
        * @param    list
        *           comma-separated list of user IDs, possibly with ! and *
        *           modifiers.
        *
        * @return   never
        * @throws   IllegalArgumentException
        *           to catch the untranslated CAN-DO call
        */
       public static logical matchesList(String list)
       throws IllegalArgumentException
       {
          throw new IllegalArgumentException("untranslated CAN-DO call");
       }

       /**
        * Tests if a string specified with the "item" parameter is allowed in the
        * comma-separated list of allowed items.
        *
        * @param    list
        *           comma-separated list of allowed items, possibly with ! and *
        *           modifiers.
        * @param    item
        *           an item being tested
        *
        * @return   <code>true</code> if item is allowed in the list
        */
       public static logical matchesList(BaseDataType list, character item)
       {
          return matchesList(new character(list), item);
       }

       /**
        * Tests if a string specified with the "item" parameter is allowed in the
        * comma-separated list of allowed items.
        *
        * @param    list
        *           comma-separated list of allowed items, possibly with ! and *
        *           modifiers.
        * @param    item
        *           an item being tested
        *
        * @return   <code>true</code> if item is allowed in the list
        */
       public static logical matchesList(Text list, character item)
       {
          if (list.isUnknown() || item.isUnknown())
             return logical.UNKNOWN;

          return matchesList(list.getValue(), item.getValue());
       }

       /**
        * Tests if a string specified with the "item" parameter is allowed in the
        * comma-separated list of allowed items.
        *
        * @param    list
        *           comma-separated list of allowed items, possibly with ! and *
        *           modifiers.
        * @param    item
        *           an item being tested
        *
        * @return   <code>true</code> if item is allowed in the list
        */
       public static logical matchesList(BaseDataType list, String item)
       {
          return matchesList(new character(list), item);
       }

       /**
        * Tests if a string specified with the "item" parameter is allowed in the
        * comma-separated list of allowed items.
        *
        * @param    list
        *           comma-separated list of allowed items, possibly with ! and *
        *           modifiers.
        * @param    item
        *           an item being tested
        *
        * @return   <code>true</code> if item is allowed in the list
        */
       public static logical matchesList(Text list, String item)
       {
          if (list.isUnknown())
             return logical.UNKNOWN;

          return matchesList(list.getValue(), item);
       }

       /**
        * Tests if a string specified with the "item" parameter is allowed in the
        * comma-separated list of allowed items.
        *
        * @param    list
        *           comma-separated list of allowed items, possibly with ! and *
        *           modifiers.
        * @param    item
        *           an item being tested
        *
        * @return   <code>true</code> if item is allowed in the list
        */
       public static logical matchesList(String list, character item)
       {
          if (item.isUnknown())
             return logical.UNKNOWN;

          return matchesList(list, item.getValue());
       }

       /**
        * Tests if a string specified with the "item" parameter is allowed in the
        * comma-separated list of allowed items.
        *
        * @param    list
        *           comma-separated list of allowed items, possibly with ! and *
        *           modifiers.
        * @param    item
        *           an item being tested
        *
        * @return   <code>true</code> if item is allowed in the list
        */
       public static logical matchesList(String list, String item)
       {
          boolean result = false;

          // create the regular expression (with proper escapes)
          String regex = null;

          // split up the list (make sure all possible entries are included,
          // even empty trailing entries --> use -1 as the limit to force this)
          // using our own implementation of the split
          String[] array = splitInt(list, ',');

          // checking item against every entry in the list until match is found
          int idx = 0;
          int i = 0;
          boolean negation = false;
          String piece = null;
          // max char array is double from original string
          char[] charBuff = new char[list.length() * 2];
          int charBuffSize = 0;
          char c = 0;
          Pattern p = null;
          Matcher m = null;

          for (idx = 0; idx < array.length; idx ++)
          {
             // isolate negation (!) case
             if (array[idx].length() > 0 && array[idx].charAt(0) == '!')
             {
                piece = array[idx].substring(1);
                negation = true;
             }
             else
             {
                piece = array[idx];
                negation = false;
             }

             // build a regex out of the list entry; only * is converted into .*
             charBuffSize = 0;
             for (i = 0; i < piece.length(); i ++)
             {
                c = piece.charAt(i);
                if (c != '*')
                {
                   // inline optimisation for method isFixupRegexChar()
                   switch (c)
                   {
                      case '(':
                      case ')':
                      case '.':
                      case '*':
                      case '[':
                      case ']':
                      case '^':
                      case '{':
                      case '}':
                      case '?':
                      case '+':
                      case '$':
                      case '|':
                      case '\\':
                      {
                         charBuff[charBuffSize++] = '\\';
                      }
                   }
                   charBuff[charBuffSize] = c;
                }
                else
                {
                   // additional char to add
                   charBuff[charBuffSize++] = '.';
                   charBuff[charBuffSize] = '*';
                }
                // at least one char added
                charBuffSize++;
             }
             regex = new String(charBuff, 0, charBuffSize);

             // check the pattern cache first
             synchronized (listPatternCache)
             {
                p = listPatternCache.get(regex);
             }

             if (p == null)
             {
                // compile regex as case-insensitive and apply it to the item
                p = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);

                synchronized (listPatternCache)
                {
                   listPatternCache.put(regex, p);
                }
             }

             m = p.matcher(item);
             if (!m.matches())
             {
                continue;
             }

             // this is a matching entry from the list; produce the result
             result = !negation;
             break;
          }

          return logical.of(result);
       }

       /**
        * Returns the largest instance (lexicographically) from the list.  This
        * list may not include <code>null</code> values.  Since an instance
        * is returned which is one of the references that was passed in, the
        * case-sensitivity of the input will be the same on output.
        *
        * @param    list
        *           The list of character instances to compare.
        *
        * @return   The largest instance or <code>unknown value</code> if any
        *           entries in the list are unknown or <code>null</code> if
        *           an empty list is passed.
        */
       public static <T extends Text> T maximum(T ... list)
       {
          return (T) CompareOps.maximum(list);
       }

       /**
        * Returns the smallest instance (lexicographically) from the list.  This
        * list may not include <code>null</code> values.  Since an instance
        * is returned which is one of the references that was passed in, the
        * case-sensitivity of the input will be the same on output.
        *
        * @param    list
        *           The list of character instances to compare.
        *
        * @return   The smallest instance or <code>unknown value</code> if any
        *           entries in the list are unknown or <code>null</code> if
        *           an empty list is passed.
        */
       public static <T extends Text> T minimum(T ... list)
       {
          return (T) CompareOps.minimum(list);
       }

       /**
        * Get the normalized form of the given string, based on the specified
        * Unicode normalization form:
        * The <code>format</code> parameter must evaluate to one of the following
        * strings:
        * <ul>
        * <li> NFD - Canonical Decomposition
        * <li> NFC - Canonical Decomposition, followed by Canonical Composition
        * <li> NFKD - Compatibility Decomposition
        * <li> NFKC - Compatibility Decomposition, followed by Canonical Composition
        * <li> NONE - Returns the source string unchanged
        * </ul>
        *
        * @param    text
        *           The text which needs to be normalized, a {@link character} or
        *           {@link longchar} instance.
        * @param    format
        *           The normalization format, one of the strings above.
        *
        * @return   The normalized form.
        */
       public static <T extends Text> T normalize(T text, String format)
       {
          return normalize(text, new character(format));
       }

       /**
        * Get the normalized form of the given string, based on the specified
        * Unicode normalization form:
        * The <code>format</code> parameter must evaluate to one of the following
        * strings:
        * <ul>
        * <li> NFD - Canonical Decomposition
        * <li> NFC - Canonical Decomposition, followed by Canonical Composition
        * <li> NFKD - Compatibility Decomposition
        * <li> NFKC - Compatibility Decomposition, followed by Canonical Composition
        * <li> NONE - Returns the source string unchanged
        * </ul>
        *
        * @param    text
        *           The text which needs to be normalized, a {@link character} or
        *           {@link longchar} instance.
        * @param    format
        *           The normalization format, one of the strings above.
        *
        * @return   The normalized form.
        */
       public static <T extends Text> T normalize(T text, character format)
       {
          // TODO: IMPLEMENT THIS
          return text;
       }

       /**
        * Returns the number of entries in the passed list.
        * <p>
        * TODO: is this method ever used? The return value is incorrect for unknown parameters. It
        *       should have a nullable Integer return type and return null.
        *
        * @param    list
        *           The delimited list of entries.
        * @param    delimit
        *           The delimiter character.
        *
        * @return   The number of entries in the list or -1 if the list is the {@code unknown value}.
        */
       @Deprecated
       public static int numEntriesOf(Text list, character delimit)
       {
          if (list.isUnknown() || delimit.isUnknown())
          {
             return -1;
          }

          return numEntriesOf(list.getValue(), safeDelimiter(delimit.getValue()));
       }

       /**
        * Returns the number of comma-delimited entries in the passed list.
        * <p>
        * TODO: is this method ever used? The return value is incorrect for unknown parameters. It
        *       should have a nullable Integer return type and return null.
        *
        * @param    list
        *           The delimited list of entries.
        *
        * @return   The number of entries in the list or -1 if the list is the {@code unknown value}.
        */
       @Deprecated
       public static int numEntriesOf(Text list)
       {
          if (list.isUnknown())
          {
             return -1;
          }

          return numEntriesOf(list.getValue(), ',');
       }

       /**
        * Returns the number of entries in the passed list.
        * <p>
        * TODO: is this method ever used? The return value is incorrect for unknown parameters. It
        *       should have a nullable Integer return type and return null.
        *
        * @param    list
        *           The delimited list of entries.
        * @param    delimit
        *           The delimiter character.
        *
        * @return   The number of entries in the list.
        */
       @Deprecated
       public static int numEntriesOf(String list, char delimit)
       {
          // split up the list (make sure all possible entries are included,
          // even empty trailing entries --> use -1 as the limit to force this)
          // using our own implementation of the split
          if (list == null || list.isEmpty())
          {
             return 0;
          }

          if (delimit == '\0')
          {
             // special case - any null chars part of the string are not used as delim
             // TODO: 4GL has a weird behavior when doing NUM-ENTRIES(, "~000"), if the string actually contains
             // one or more null characters.
             return 1;
          }

          // convert string to array of chars
          char[] srcChars = list.toCharArray();

          int srcLen = list.length();                // length of src string
          int arraySize = 0;                        // number of substrings
          int i = 0;                                // used to loop inside chars array
          int start = 0;                            // starting point for next substring
          // first find out how much elements should be in array
          for (; i < srcLen; i++)
          {
             if (srcChars[i] == delimit)
             {
                // record the length of the substring
                arraySize++;
                // starting point for next substring just after delimiter
                start = i + 1;
             }
          }
          // last section
          if (i == srcLen && start <= i)
          {
             arraySize++;
          }

          return arraySize;
       }

       /**
        * Returns the number of comma-delimited entries in the passed list.
        * <p>
        * TODO: is this method ever used? The return value is incorrect for unknown parameters. It
        *       should have a nullable Integer return type and return null.
        *
        * @param    list
        *           The delimited list of entries.
        *
        * @return   The number of entries in the list.
        */
       @Deprecated
       public static int numEntriesOf(String list)
       {
          return numEntriesOf(list, ',');
       }

       /**
        * Returns the number of comma-delimited entries in the passed list.
        *
        * @param    list
        *           The delimited list of entries.
        *
        * @return   The number of entries in the list or the
        *           <code>unknown value</code> if
        *           any of the inputs are <code>unknown value</code>.
        */
       public static integer numEntries(Text list)
       {
          if (list.isUnknown())
          {
             return integer.UNKNOWN;
          }

          return integer.of(numEntriesOf(list.getValue()));
       }

       /**
        * Returns the number of entries in the passed list.
        *
        * @param    list
        *           The delimited list of entries.
        * @param    delimit
        *           The delimiter character.
        *
        * @return   The number of entries in the list or the
        *           <code>unknown value</code> if
        *           any of the inputs are <code>unknown value</code>.
        */
       public static integer numEntries(Text list, String delimit)
       {
          if (list.isUnknown() || delimit == null)
          {
             return integer.UNKNOWN;
          }

          return integer.of(numEntriesOf(list.getValue(), safeDelimiter(delimit)));
       }

       /**
        * Returns the number of entries in the passed list.
        *
        * @param    list
        *           The delimited list of entries.
        * @param    delimit
        *           The delimiter character.
        *
        * @return   The number of entries in the list or the
        *           <code>unknown value</code> if
        *           any of the inputs are <code>unknown value</code>.
        */
       public static integer numEntries(String list, character delimit)
       {
          if (list == null || delimit.isUnknown())
          {
             return integer.UNKNOWN;
          }

          return integer.of(numEntriesOf(list, safeDelimiter(delimit.getValue())));
       }

       /**
        * Returns the number of entries in the passed list.
        *
        * @param    list
        *           The delimited list of entries.
        * @param    delimit
        *           The delimiter character.
        *
        * @return   The number of entries in the list or the
        *           <code>unknown value</code> if
        *           any of the inputs are <code>unknown value</code>.
        */
       public static integer numEntries(String list, String delimit)
       {
          if (list == null || delimit == null)
          {
             return integer.UNKNOWN;
          }

          return integer.of(numEntriesOf(list, safeDelimiter(delimit)));
       }

       /**
        * Returns the number of entries in the passed list.
        *
        * @param    list
        *           The delimited list of entries.
        * @param    delimit
        *           The delimiter character.
        *
        * @return   The number of entries in the list or the
        *           <code>unknown value</code> if
        *           any of the inputs are <code>unknown value</code>.
        */
       public static integer numEntries(Text list, character delimit)
       {
          if (list.isUnknown() || delimit.isUnknown())
          {
             return integer.UNKNOWN;
          }

          return integer.of(numEntriesOf(list.getValue(),
                                          safeDelimiter(delimit.getValue())));
       }

       /**
        * Returns the number of entries in the passed list.
        *
        * @param    list
        *           The delimited list of entries.
        * @param    delimit
        *           The delimiter character.
        *
        * @return   The number of entries in the list.
        */
       public static integer numEntries(String list, char delimit)
       {
          if (list == null)
          {
             return integer.UNKNOWN;
          }

          return integer.of(numEntriesOf(list, delimit));
       }

       /**
        * Returns the number of comma-delimited entries in the passed list.
        *
        * @param    list
        *           The delimited list of entries.
        *
        * @return   The number of entries in the list.
        */
       public static integer numEntries(String list)
       {
          if (list == null)
          {
             return integer.UNKNOWN;
          }

          return integer.of(numEntriesOf(list));
       }

       /**
        * Searches for all matches to the <code>from</code> string in the
        * <code>src</code> string and replaces these with the <code>to</code>
        * string.
        * <p>
        * The search and the returned instance are case-sensitive if any of the
        * inputs are case-sensitive.
        *
        * @param    src
        *           The string to operate upon.
        * @param    from
        *           The substring to search for.
        * @param    to
        *           The substring to replace when a match is found.
        *
        * @return   The resulting string or <code>unknown value</code> if any
        *           input is <code>unknown value</code>.
        */
       public static <T extends Text> T replaceAll(T    src,
                                                   Text from,
                                                   Text to)
       {
          if (src.isUnknown() || from.isUnknown() || to.isUnknown())
          {
             return (T) instantiateUnknown(src.getClass());
          }

          boolean cs = (src.isCaseSensitive()  ||
                        from.isCaseSensitive() ||
                        to.isCaseSensitive());

          return (T) replaceAllImpl(src.getClass(),
                                    src.getValue(),
                                    from.getValue(),
                                    to.getValue(),
                                    cs,
                                    getCodePage(src),
                                    src.isJavaSpacifyNull(),
                                    from.isJavaSpacifyNull(),
                                    to.isJavaSpacifyNull());
       }

       /**
        * Searches for all matches to the <code>from</code> string in the
        * <code>src</code> string and replaces these with the <code>to</code>
        * string.
        * <p>
        * The search and the returned instance are case-sensitive if any of the
        * inputs are case-sensitive.
        *
        * @param    src
        *           The string to operate upon.
        * @param    from
        *           The substring to search for.
        * @param    to
        *           The substring to replace when a match is found.
        *
        * @return   The resulting string or <code>unknown value</code> if any
        *           input is <code>unknown value</code>.
        */
       public static <T extends Text> T replaceAll(T      src,
                                                   Text   from,
                                                   String to)
       {
          if (src.isUnknown() || from.isUnknown())
          {
             return (T) instantiateUnknown(src.getClass());
          }

          boolean cs = (src.isCaseSensitive() || from.isCaseSensitive());

          return (T) replaceAllImpl(src.getClass(),
                                    src.getValue(),
                                    from.getValue(),
                                    to,
                                    cs,
                                    getCodePage(src),
                                    src.isJavaSpacifyNull(),
                                    from.isJavaSpacifyNull(),
                                    true);
       }

       /**
        * Searches for all matches to the <code>from</code> string in the
        * <code>src</code> string and replaces these with the <code>to</code>
        * string.
        * <p>
        * The search and the returned instance are case-sensitive if any of the
        * inputs are case-sensitive.
        *
        * @param    src
        *           The string to operate upon.
        * @param    from
        *           The substring to search for.
        * @param    to
        *           The substring to replace when a match is found.
        *
        * @return   The resulting string or <code>unknown value</code> if any
        *           input is <code>unknown value</code>.
        */
       public static <T extends Text> T replaceAll(T      src,
                                                   String from,
                                                   Text   to)
       {
          if (src.isUnknown() || to.isUnknown())
          {
             return (T) instantiateUnknown(src.getClass());
          }

          boolean cs = (src.isCaseSensitive() || to.isCaseSensitive());

          return (T) replaceAllImpl(src.getClass(),
                                    src.getValue(),
                                    from,
                                    to.getValue(),
                                    cs,
                                    getCodePage(src),
                                    src.isJavaSpacifyNull(),
                                    true,
                                    to.isJavaSpacifyNull());
       }

       /**
        * Searches for all matches to the <code>from</code> string in the
        * <code>src</code> string and replaces these with the <code>to</code>
        * string.
        * <p>
        * The search and the returned instance are case-sensitive if any of the
        * inputs are case-sensitive.
        *
        * @param    src
        *           The string to operate upon.
        * @param    from
        *           The substring to search for.
        * @param    to
        *           The substring to replace when a match is found.
        *
        * @return   The resulting string or <code>unknown value</code> if any
        *           input is <code>unknown value</code>.
        */
       public static character replaceAll(String src,
                                          Text   from,
                                          Text   to)
       {
          if (from.isUnknown() || to.isUnknown())
          {
             return new character();
          }

          boolean cs = (from.isCaseSensitive() || to.isCaseSensitive());

          return replaceAllImpl(character.class,
                                src,
                                from.getValue(),
                                to.getValue(),
                                cs,
                                null,
                                true,
                                from.isJavaSpacifyNull(),
                                to.isJavaSpacifyNull());
       }

       /**
        * Searches for all matches to the <code>from</code> string in the
        * <code>src</code> string and replaces these with the <code>to</code>
        * string.
        * <p>
        * The search and the returned instance are case-sensitive if the
        * <code>src</code> instance is case-sensitive.
        *
        * @param    src
        *           The string to operate upon.
        * @param    from
        *           The substring to search for.
        * @param    to
        *           The substring to replace when a match is found.
        *
        * @return   The resulting string or <code>unknown value</code> if the
        *           <code>src</code> string is <code>unknown value</code>.
        */
       public static <T extends Text> T replaceAll(T      src,
                                                   String from,
                                                   String to)
       {
          if (src.isUnknown())
          {
             return (T) instantiateUnknown(src.getClass());
          }

          return (T) replaceAllImpl(src.getClass(),
                                    src.getValue(),
                                    from,
                                    to,
                                    src.isCaseSensitive(),
                                    getCodePage(src),
                                    src.isJavaSpacifyNull(),
                                    true,
                                    true);
       }

       /**
        * Searches for all matches to the <code>from</code> string in the
        * <code>src</code> string and replaces these with the <code>to</code>
        * string.
        * <p>
        * The search and the returned instance are case-sensitive if the
        * <code>from</code> instance is case-sensitive.
        *
        * @param    src
        *           The string to operate upon.
        * @param    from
        *           The substring to search for.
        * @param    to
        *           The substring to replace when a match is found.
        *
        * @return   The resulting string or <code>unknown value</code> if the
        *           <code>from</code> string is <code>unknown value</code>.
        */
       public static character replaceAll(String src, Text from, String to)
       {
          if (from.isUnknown())
          {
             return new character();
          }

          return replaceAllImpl(character.class,
                                src,
                                from.getValue(),
                                to,
                                from.isCaseSensitive(),
                                null,
                                true,
                                from.isJavaSpacifyNull(),
                                true);
       }

       /**
        * Searches for all matches to the <code>from</code> string in the
        * <code>src</code> string and replaces these with the <code>to</code>
        * string.
        * <p>
        * The search and the returned instance are case-sensitive if the
        * <code>to</code> instance is case-sensitive.
        *
        * @param    src
        *           The string to operate upon.
        * @param    from
        *           The substring to search for.
        * @param    to
        *           The substring to replace when a match is found.
        *
        * @return   The resulting string or <code>unknown value</code> if the
        *           <code>to</code> string is <code>unknown value</code>.
        */
       public static character replaceAll(String src, String from, Text to)
       {
          if (to.isUnknown())
          {
             return new character();
          }

          return replaceAllImpl(character.class,
                                src,
                                from,
                                to.getValue(),
                                to.isCaseSensitive(),
                                null,
                                true,
                                true,
                                to.isJavaSpacifyNull());
       }

       /**
        * Searches for all matches to the <code>from</code> string in the
        * <code>src</code> string and replaces these with the <code>to</code>
        * string.
        * <p>
        * The search and the returned instance are always case-insensitive.
        *
        * @param    src
        *           The string to operate upon.
        * @param    from
        *           The substring to search for.
        * @param    to
        *           The substring to replace when a match is found.
        *
        * @return   The resulting string or <code>unknown value</code> if the
        *           <code>src</code> string is <code>unknown value</code>.
        */
       public static character replaceAll(String src, String from, String to)
       {
          return replaceAllImpl(character.class, src, from, to, false, null, true, true, true);
       }

       /**
        * Searches for all matches to the <code>from</code> string in the
        * <code>src</code> string and replaces these with the <code>to</code>
        * string.
        *
        * @param    src
        *           The string to operate upon.
        * @param    from
        *           The substring to search for.
        * @param    to
        *           The substring to replace when a match is found.
        * @param    cs
        *           Determines if a case-sensitive match should be performed AND
        *           the case-sensitivity of the returned instance.
        *
        * @return   The resulting string.
        */
       public static character replaceAll(String  src,
                                          String  from,
                                          String  to,
                                          boolean cs)
       {
          return replaceAllImpl(character.class, src, from, to, cs, null, true, true, true);
       }

       /**
        * Trims the list of contiguous trim characters from the right of an input
        * string.
        * <p>
        * Trim operations are performed based on the case-sensitivity of only
        * the input parameter, the trim character list case-sensitivity is
        * ignored.  Likewise, if the input parameter is case-sensitive, then
        * the returned instance will be case-sensitive.
        *
        * @param    input
        *           The string to trim.
        * @param    list
        *           The list of characters to trim.
        *
        * @return   The trimmed result.
        */
       public static <T extends Text> T rightTrim(T input, character list)
       {
          if (input.isUnknown() || list.isUnknown())
          {
             return (T) instantiateUnknown(input.getClass());
          }

          return (T) rightTrimImpl(input.getClass(),
                                   input.getValue(),
                                   list.getValue(),
                                   input.isCaseSensitive(),
                                   getCodePage(input),
                                   input.isJavaSpacifyNull());
       }

       /**
        * Trims the list of contiguous trim characters from the right of an input
        * string.
        * <p>
        * Trim operations are performed based on the case-sensitivity of the
        * the input parameter. If the input parameter is case-sensitive, then
        * the returned instance will be case-sensitive.
        *
        * @param    input
        *           The string to trim.
        * @param    list
        *           The list of characters to trim.
        *
        * @return   The trimmed result.
        */
       public static <T extends Text> T rightTrim(T input, String list)
       {
          if (input.isUnknown())
          {
             return (T) instantiateUnknown(input.getClass());
          }

          return (T) rightTrimImpl(input.getClass(),
                                   input.getValue(),
                                   list,
                                   input.isCaseSensitive(),
                                   getCodePage(input),
                                   input.isJavaSpacifyNull());
       }

       /**
        * Right-trim and lower case the given input text. Right-trimming removes the default characters (space,
        * tab, line feed, carriage return).
        *
        * @param    input
        *           Input text.
        *
        * @return   Adjusted form of the text.
        */
       public static String rightTrimLower(String input)
       {
          return trim(input.toLowerCase(), null, false, false);
       }

       /**
        * Trims any contiguous space, tab, line feed and carriage return from the
        * right of an input string.
        * <p>
        * Trim operations are performed based on the case-sensitivity of the
        * input parameter.  Likewise, if the input parameter is case-sensitive,
        * then the returned instance will be case-sensitive.
        *
        * @param    input
        *           The string to trim.
        *
        * @return   The trimmed result.
        */
       public static <T extends Text> T rightTrim(T input)
       {
          if (input.isUnknown())
          {
             return (T) instantiateUnknown(input.getClass());
          }

          return (T) rightTrimImpl(input.getClass(),
                                   input.getValue(),
                                   null,
                                   input.isCaseSensitive(),
                                   getCodePage(input),
                                   input.isJavaSpacifyNull());
       }

       /**
        * Trims the list of contiguous trim characters from the right of an input
        * string.
        * <p>
        * Trim operations are performed based case-insensitively.
        *
        * @param    input
        *           The string to trim.
        * @param    list
        *           The list of characters to trim.
        *
        * @return   The trimmed result.
        */
       public static character rightTrim(String input, character list)
       {
          if (list.isUnknown())
          {
             return new character();
          }

          return rightTrimImpl(character.class, input, list.getValue(), false, null, true);
       }

       /**
        * Trims the list of contiguous trim characters from the right of an input
        * string.  If the list of trim characters is <code>null</code>
        * the method defaults to space, tab, line feed and carriage return.
        * <p>
        * Trim operations and the returned instance will be case-insensitive.

        *
        * @param    input
        *           The string to trim.
        * @param    list
        *           The list of characters to trim or <code>null</code> to
        *           use the default list.
        *
        * @return   The trimmed result.
        */
       public static character rightTrim(String input, String list)
       {
          return rightTrimImpl(character.class, input, list, false, null, true);
       }

       /**
        * Trims any contiguous space, tab, line feed and carriage return from the
        * right of an input string.
        * <p>
        * Trim operations and the returned instance will be case-insensitive.
        *
        * @param    input
        *           The string to trim.
        *
        * @return   The trimmed result.
        */
       public static character rightTrim(String input)
       {
          return rightTrim(input, (String) null);
       }

       /**
        * Trims any contiguous space, tab, line feed and carriage return from the
        * right of an input string.
        * <p>
        * Trim operations and the returned instance will be case-insensitive.
        *
        * @param    input
        *           The string to trim.
        *
        * @return   The trimmed result.
        */
       public static String rightTrimNative(String input)
       {
          return trim(input, null, false, true);
       }

       /**
        * Trims the list of contiguous trim characters from the right of an input
        * string.  If the list of trim characters is <code>null</code>
        * the method defaults to space, tab, line feed and carriage return.
        *
        * @param    input
        *           The string to trim.
        * @param    list
        *           The list of characters to trim or <code>null</code> to
        *           use the default list.
        * @param    caseSens
        *           Specifies the case-sensitivity of the trim operation AND of
        *           the returned instance.
        *
        * @return   The trimmed result.
        */
       public static character rightTrim(String  input,
                                         String  list,
                                         boolean caseSens)
       {
          return rightTrimImpl(character.class, input, list, caseSens, null, true);
       }

       /**
        * Perform positional replacement of '&amp;n' specifiers in the format string
        * <code>fmt</code>, using the array of <code>character</code> and/or
        * <code>String</code> objects as the list of replacements.  The 1-based
        * index position of the '&amp;n' specifiers (where n and range from 1 to
        * 9) is converted to a 0-based index into the <code>replacements</code>
        * array.  Instances of '&amp;&amp;' yield a single '&amp;' in the result.
        * Any specifiers that do not correspond to an object in the array are
        * replaced with an empty string.
        *
        * @param    fmt
        *           The string specifying the text and format specifiers.
        * @param    replacements
        *           The list of replacements.
        *
        * @return   The resulting string or <code>unknown value</code> if the
        *           <code>fmt</code> string is <code>unknown value</code>.
        */
       public static <T extends Text> T substitute(T fmt, Object... replacements)
       throws ErrorConditionException
       {
          if (fmt.isUnknown())
             return (T) instantiateUnknown(fmt.getClass());

          return (T) substituteImpl(fmt.getClass(), fmt.getValue(), replacements);
       }

       /**
        * Perform positional replacement of '&amp;n' specifiers in the format string
        * <code>fmt</code>, using the array of <code>character</code> and/or
        * <code>String</code> objects as the list of replacements.  The 1-based
        * index position of the '&amp;n' specifiers (where n and range from 1 to
        * 9) is converted to a 0-based index into the <code>replacements</code>
        * array.  Instances of '&amp;&amp;' yield a single '&amp;' in the result.
        * Any specifiers that do not correspond to an object in the array are
        * replaced with an empty string.
        *
        * @param    fmt
        *           The string specifying the text and format specifiers.
        * @param    replacements
        *           The list of replacements.
        *
        * @return   The resulting string.
        */
       public static character substitute(String fmt, Object... replacements)
       throws ErrorConditionException
       {
          return substituteImpl(character.class, fmt, replacements);
       }

       /**
        * Returns the substring of the string at the given 1-based index and
        * of the given length.
        * <p>
        * Case-sensitivity of the returned instance is determined by the
        * <code>src</code> instance case-sensitivity.
        *
        * @param    src
        *           The string to operate upon.
        * @param    pos
        *           The 1-based index at which the substring starts.
        * @param    len
        *           The length of the substring to return. Use -1 for the rest of
        *           the string.
        * @param    type
        *           Specifies whether this substring operates on bytes or
        *           characters.  At this time only "raw" and "character" are
        *           supported.
        *
        * @return   The resulting substring or <code>unknown value</code> if any
        *           input is <code>unknown</code>.
        */
       public static <T extends Text> T substring(T          src,
                                                  NumberType pos,
                                                  NumberType len,
                                                  String     type)
       {
          if (src.isUnknown() || pos.isUnknown() || len.isUnknown())
             return (T) instantiateUnknown(src.getClass());

          return (T) substringImpl(src.getClass(),
                                   src.getValue(),
                                   pos.intValue(),
                                   len.intValue(),
                                   type,
                                   src.isCaseSensitive(),
                                   getCodePage(src),
                                   src.isJavaSpacifyNull());
       }

       /**
        * Returns the substring of the string at the given 1-based index and
        * of the given length.
        * <p>
        * Case-sensitivity of the returned instance is determined by the
        * <code>src</code> instance case-sensitivity.
        *
        * @param    src
        *           The string to operate upon.
        * @param    pos
        *           The 1-based index at which the substring starts.
        * @param    len
        *           The length of the substring to return. Use -1 for the rest of
        *           the string.
        * @param    type
        *           Specifies whether this substring operates on bytes or
        *           characters.  At this time only "raw" and "character" are
        *           supported.
        *
        * @return   The resulting substring or <code>unknown value</code> if any
        *           input is <code>unknown</code>.
        */
       public static <T extends Text> T substring(T          src,
                                                  NumberType pos,
                                                  NumberType len,
                                                  character  type)
       {
          if (src.isUnknown() || pos.isUnknown() || len.isUnknown() || type.isUnknown())
             return (T) instantiateUnknown(src.getClass());

          return (T) substringImpl(src.getClass(),
                                   src.getValue(),
                                   pos.intValue(),
                                   len.intValue(),
                                   type.getValue(),
                                   src.isCaseSensitive(),
                                   getCodePage(src),
                                   src.isJavaSpacifyNull());
       }

       /**
        * Returns the substring of the string at the given 1-based index and
        * of the given length.
        * <p>
        * Case-sensitivity of the returned instance is determined by the
        * <code>src</code> instance case-sensitivity.
        *
        * @param    src
        *           The string to operate upon.
        * @param    pos
        *           The 1-based index at which the substring starts.
        * @param    len
        *           The length of the substring to return. Use -1 for the rest of
        *           the string.
        * @param    type
        *           Specifies whether this substring operates on bytes or
        *           characters.  At this time only "raw" and "character" are
        *           supported.
        *
        * @return   The resulting substring or <code>unknown value</code> if any
        *           input is <code>unknown</code>.
        */
       public static <T extends Text> T substring(T          src,
                                                  int        pos,
                                                  NumberType len,
                                                  String     type)
       {
          if (src.isUnknown() || len.isUnknown())
             return (T) instantiateUnknown(src.getClass());

          return (T) substringImpl(src.getClass(),
                                   src.getValue(),
                                   pos,
                                   len.intValue(),
                                   type,
                                   src.isCaseSensitive(),
                                   getCodePage(src),
                                   src.isJavaSpacifyNull());
       }

       /**
        * Returns the substring of the string at the given 1-based index and
        * of the given length.
        * <p>
        * Case-sensitivity of the returned instance is determined by the
        * <code>src</code> instance case-sensitivity.
        *
        * @param    src
        *           The string to operate upon.
        * @param    pos
        *           The 1-based index at which the substring starts.
        * @param    len
        *           The length of the substring to return. Use -1 for the rest of
        *           the string.
        * @param    type
        *           Specifies whether this substring operates on bytes or
        *           characters.  At this time only "raw" and "character" are
        *           supported.
        *
        * @return   The resulting substring or <code>unknown value</code> if any
        *           input is <code>unknown</code>.
        */
       public static <T extends Text> T substring(T          src,
                                                  int        pos,
                                                  NumberType len,
                                                  character  type)
       {
          if (src.isUnknown() || len.isUnknown() || type.isUnknown())
             return (T) instantiateUnknown(src.getClass());

          return (T) substringImpl(src.getClass(),
                                   src.getValue(),
                                   pos,
                                   len.intValue(),
                                   type.getValue(),
                                   src.isCaseSensitive(),
                                   getCodePage(src),
                                   src.isJavaSpacifyNull());
       }

       /**
        * Returns the substring of the string at the given 1-based index and
        * of the given length.
        * <p>
        * Case-sensitivity of the returned instance is determined by the
        * <code>src</code> instance case-sensitivity.
        *
        * @param    src
        *           The string to operate upon.
        * @param    pos
        *           The 1-based index at which the substring starts.
        * @param    len
        *           The length of the substring to return. Use -1 for the rest of
        *           the string.
        * @param    type
        *           Specifies whether this substring operates on bytes or
        *           characters.  At this time only "raw" and "character" are
        *           supported.
        *
        * @return   The resulting substring or <code>unknown value</code> if any
        *           input is <code>unknown</code>.
        */
       public static <T extends Text> T substring(T          src,
                                                  NumberType pos,
                                                  int        len,
                                                  String     type)
       {
          if (src.isUnknown() || pos.isUnknown())
             return (T) instantiateUnknown(src.getClass());

          return (T) substringImpl(src.getClass(),
                                   src.getValue(),
                                   pos.intValue(),
                                   len,
                                   type,
                                   src.isCaseSensitive(),
                                   getCodePage(src),
                                   src.isJavaSpacifyNull());
       }

       /**
        * Returns the substring of the string at the given 1-based index and
        * of the given length.
        * <p>
        * Case-sensitivity of the returned instance is determined by the
        * <code>src</code> instance case-sensitivity.
        *
        * @param    src
        *           The string to operate upon.
        * @param    pos
        *           The 1-based index at which the substring starts.
        * @param    len
        *           The length of the substring to return. Use -1 for the rest of
        *           the string.
        * @param    type
        *           Specifies whether this substring operates on bytes or
        *           characters.  At this time only "raw" and "character" are
        *           supported.
        *
        * @return   The resulting substring or <code>unknown value</code> if any
        *           input is <code>unknown</code>.
        */
       public static <T extends Text> T substring(T          src,
                                                  NumberType pos,
                                                  int        len,
                                                  character  type)
       {
          if (src.isUnknown() || pos.isUnknown() || type.isUnknown())
             return (T) instantiateUnknown(src.getClass());

          return (T) substringImpl(src.getClass(),
                                   src.getValue(),
                                   pos.intValue(),
                                   len,
                                   type.getValue(),
                                   src.isCaseSensitive(),
                                   getCodePage(src),
                                   src.isJavaSpacifyNull());
       }

       /**
        * Returns the substring of the string at the given 1-based index and
        * of the given length.
        * <p>
        * Case-sensitivity of the returned instance is determined by the
        * <code>src</code> instance case-sensitivity.
        *
        * @param    src
        *           The string to operate upon.
        * @param    pos
        *           The 1-based index at which the substring starts.
        * @param    len
        *           The length of the substring to return. Use -1 for the rest of
        *           the string.
        * @param    type
        *           Specifies whether this substring operates on bytes or
        *           characters.  At this time only "raw" and "character" are
        *           supported.
        *
        * @return   The resulting substring or <code>unknown value</code> if the
        *           <code>src</code> is <code>unknown</code>.
        */
       public static <T extends Text> T substring(T      src,
                                                  int    pos,
                                                  int    len,
                                                  String type)
       {
          if (src.isUnknown())
             return (T) instantiateUnknown(src.getClass());

          return (T) substringImpl(src.getClass(),
                                   src.getValue(),
                                   pos,
                                   len,
                                   type,
                                   src.isCaseSensitive(),
                                   getCodePage(src),
                                   src.isJavaSpacifyNull());
       }

       /**
        * Returns the substring of the string at the given 1-based index and
        * of the given length.
        * <p>
        * Case-sensitivity of the returned instance is determined by the
        * <code>src</code> instance case-sensitivity.
        *
        * @param    src
        *           The string to operate upon.
        * @param    pos
        *           The 1-based index at which the substring starts.
        * @param    len
        *           The length of the substring to return. Use -1 for the rest of
        *           the string.
        * @param    type
        *           Specifies whether this substring operates on bytes or
        *           characters.  At this time only "raw" and "character" are
        *           supported.
        *
        * @return   The resulting substring or <code>unknown value</code> if the
        *           <code>src</code> is <code>unknown</code>.
        */
       public static <T extends Text> T substring(T         src,
                                                  int       pos,
                                                  int       len,
                                                  character type)
       {
          if (src.isUnknown() || type.isUnknown())
             return (T) instantiateUnknown(src.getClass());

          return (T) substringImpl(src.getClass(),
                                   src.getValue(),
                                   pos,
                                   len,
                                   type.getValue(),
                                   src.isCaseSensitive(),
                                   getCodePage(src),
                                   src.isJavaSpacifyNull());
       }

       /**
        * Returns the substring of the string at the given 1-based index and
        * of the given length.  Operates on characters rather than bytes.
        * <p>
        * Case-sensitivity of the returned instance is determined by the
        * <code>src</code> instance case-sensitivity.
        *
        * @param    src
        *           The string to operate upon.
        * @param    pos
        *           The 1-based index at which the substring starts.
        * @param    len
        *           The length of the substring to return. Use -1 for the rest of
        *           the string.
        *
        * @return   The resulting substring or <code>unknown value</code> if any
        *           input is <code>unknown</code>.
        */
       public static <T extends Text> T substring(T          src,
                                                  NumberType pos,
                                                  NumberType len)
       {
          if (src.isUnknown() || pos.isUnknown() || len.isUnknown())
             return (T) instantiateUnknown(src.getClass());

          return (T) substringImpl(src.getClass(),
                                   src.getValue(),
                                   pos.intValue(),
                                   len.intValue(),
                                   null,
                                   src.isCaseSensitive(),
                                   getCodePage(src),
                                   src.isJavaSpacifyNull());
       }

       /**
        * Returns the substring of the string at the given 1-based index and
        * of the given length.  Operates on characters rather than bytes.
        * <p>
        * Case-sensitivity of the returned instance is determined by the
        * <code>src</code> instance case-sensitivity.
        *
        * @param    src
        *           The string to operate upon.
        * @param    pos
        *           The 1-based index at which the substring starts.
        * @param    len
        *           The length of the substring to return. Use -1 for the rest of
        *           the string.
        *
        * @return   The resulting substring or <code>unknown value</code> if any
        *           input is <code>unknown</code>.
        */
       public static <T extends Text> T substring(T          src,
                                                  int        pos,
                                                  NumberType len)
       {
          if (src.isUnknown() || len.isUnknown())
             return (T) instantiateUnknown(src.getClass());

          return (T) substringImpl(src.getClass(),
                                   src.getValue(),
                                   pos,
                                   len.intValue(),
                                   null,
                                   src.isCaseSensitive(),
                                   getCodePage(src),
                                   src.isJavaSpacifyNull());
       }

       /**
        * Returns the substring of the string at the given 1-based index and
        * of the given length.  Operates on characters rather than bytes.
        * <p>
        * Case-sensitivity of the returned instance is determined by the
        * <code>src</code> instance case-sensitivity.
        *
        * @param    src
        *           The string to operate upon.
        * @param    pos
        *           The 1-based index at which the substring starts.
        * @param    len
        *           The length of the substring to return. Use -1 for the rest of
        *           the string.
        *
        * @return   The resulting substring or <code>unknown value</code> if any
        *           input is <code>unknown</code>.
        */
       public static <T extends Text> T substring(T          src,
                                                  NumberType pos,
                                                  int        len)
       {
          if (src.isUnknown() || pos.isUnknown())
             return (T) instantiateUnknown(src.getClass());

          return (T) substringImpl(src.getClass(),
                                   src.getValue(),
                                   pos.intValue(),
                                   len,
                                   null,
                                   src.isCaseSensitive(),
                                   getCodePage(src),
                                   src.isJavaSpacifyNull());
       }

       /**
        * Returns the substring of the string at the given 1-based index and
        * of the given length.  Operates on characters rather than bytes.
        * <p>
        * Case-sensitivity of the returned instance is determined by the
        * <code>src</code> instance case-sensitivity.
        *
        * @param    src
        *           The string to operate upon.
        * @param    pos
        *           The 1-based index at which the substring starts.
        * @param    len
        *           The length of the substring to return. Use -1 for the rest of
        *           the string.
        *
        * @return   The resulting substring or <code>unknown value</code> if the
        *           <code>src</code> is <code>unknown</code>.
        */
       public static <T extends Text> T substring(T src, int pos, int len)
       {
          if (src.isUnknown())
             return (T) instantiateUnknown(src.getClass());

          return (T) substringImpl(src.getClass(),
                                   src.getValue(),
                                   pos,
                                   len,
                                   null,
                                   src.isCaseSensitive(),
                                   getCodePage(src),
                                   src.isJavaSpacifyNull());
       }

       /**
        * Returns the substring of the string at the given 1-based index and
        * extending for the rest of the string.   Operates on characters rather
        * than bytes.
        * <p>
        * Case-sensitivity of the returned instance is determined by the
        * <code>src</code> instance case-sensitivity.
        *
        * @param    src
        *           The string to operate upon.
        * @param    pos
        *           The 1-based index at which the substring starts.
        *
        * @return   The resulting substring or <code>unknown value</code> if any
        *           input is <code>unknown</code>.
        */
       public static <T extends Text> T substring(T src, NumberType pos)
       {
          if (src.isUnknown() || pos.isUnknown())
             return (T) instantiateUnknown(src.getClass());

          return (T) substringImpl(src.getClass(),
                                   src.getValue(),
                                   pos.intValue(),
                                   -1,
                                   null,
                                   src.isCaseSensitive(),
                                   getCodePage(src),
                                   src.isJavaSpacifyNull());
       }

       /**
        * Returns the substring of the string at the given 1-based index and
        * extending for the rest of the string.  Operates on characters rather
        * than bytes.
        * <p>
        * Case-sensitivity of the returned instance is determined by the
        * <code>src</code> instance case-sensitivity.
        *
        * @param    src
        *           The string to operate upon.
        * @param    pos
        *           The 1-based index at which the substring starts.
        *
        * @return   The resulting substring or <code>unknown value</code> if the
        *           <code>src</code> is <code>unknown</code>.
        */
       public static <T extends Text> T substring(T src, int pos)
       {
          if (src.isUnknown())
             return (T) instantiateUnknown(src.getClass());

          return (T) substringImpl(src.getClass(),
                                   src.getValue(),
                                   pos,
                                   -1,
                                   null,
                                   src.isCaseSensitive(),
                                   getCodePage(src),
                                   src.isJavaSpacifyNull());
       }

       /**
        * Returns the substring of the string at the given 1-based index  and
        * extending for the rest of the string. Operates on characters rather than
        * bytes.
        * <p>
        * The returned instance is case-insensitive.
        *
        * @param    src
        *           The string to operate upon.
        * @param    pos
        *           The 1-based index at which the substring starts.
        *
        * @return   The resulting substring.
        */
       public static character substring(String src, int pos)
       {
          return substringImpl(character.class, src, pos, -1, null, false, null, true);
       }

       /**
        * Returns the substring of the string at the given 1-based index  and
        * extending for the rest of the string. Operates on characters rather than
        * bytes.
        * <p>
        * The returned instance is case-insensitive.
        *
        * @param    src
        *           The string to operate upon.
        * @param    pos
        *           The 1-based index at which the substring starts.
        *
        * @return   The resulting substring.
        */
       public static character substring(String src, NumberType pos)
       {
          if (src != null || pos == null || pos.isUnknown())
          {
             return new character();
          }

          return substringImpl(character.class, src, pos.intValue(), -1, null, false, null, true);
       }

       /**
        * Returns the substring of the string at the given 1-based index and
        * of the given length.  Operates on characters rather than bytes.
        * <p>
        * Case-sensitivity of the returned instance is determined by the
        * <code>src</code> instance case-sensitivity.
        *
        * @param    src
        *           The string to operate upon.
        * @param    pos
        *           The 1-based index at which the substring starts.
        * @param    len
        *           The length of the substring to return. Use -1 for the rest of
        *           the string.
        *
        * @return   The resulting substring or <code>unknown value</code> if any
        *           input is <code>unknown</code>.
        */
       public static character substring(String     src,
                                         NumberType pos,
                                         NumberType len)
       {
          if (pos.isUnknown() || len.isUnknown())
             return new character();

          return substringImpl(character.class,
                               src,
                               pos.intValue(),
                               len.intValue(),
                               null,
                               false,
                               null,
                               true);
       }

       /**
        * Returns the substring of the string at the given 1-based index and
        * of the given length.  Operates on characters rather than bytes.
        * <p>
        * Case-sensitivity of the returned instance is determined by the
        * <code>src</code> instance case-sensitivity.
        *
        * @param    src
        *           The string to operate upon.
        * @param    pos
        *           The 1-based index at which the substring starts.
        * @param    len
        *           The length of the substring to return. Use -1 for the rest of
        *           the string.
        *
        * @return   The resulting substring or <code>unknown value</code> if any
        *           input is <code>unknown</code>.
        */
       public static character substring(String     src,
                                         NumberType pos,
                                         int        len)
       {
          if (pos.isUnknown())
             return new character();

          return substringImpl(character.class,
                               src,
                               pos.intValue(),
                               len,
                               null,
                               false,
                               null,
                               true);
       }

       /**
        * Returns the substring of the string at the given 1-based index and
        * of the given length.  Operates on characters rather than bytes.
        * <p>
        * Case-sensitivity of the returned instance is determined by the
        * <code>src</code> instance case-sensitivity.
        *
        * @param    src
        *           The string to operate upon.
        * @param    pos
        *           The 1-based index at which the substring starts.
        * @param    len
        *           The length of the substring to return. Use -1 for the rest of
        *           the string.
        *
        * @return   The resulting substring or <code>unknown value</code> if any
        *           input is <code>unknown</code>.
        */
       public static character substring(String     src,
                                         int        pos,
                                         NumberType len)
       {
          if (len.isUnknown())
             return new character();

          return substringImpl(character.class,
                               src,
                               pos,
                               len.intValue(),
                               null,
                               false,
                               null,
                               true);
       }

       /**
        * Returns the substring of the string at the given 1-based index and
        * of the given length.  Operates on characters rather than bytes.
        * <p>
        * The returned instance is case-insensitive.
        *
        * @param    src
        *           The string to operate upon.
        * @param    pos
        *           The 1-based index at which the substring starts.
        * @param    len
        *           The length of the substring to return. Use -1 for the rest of
        *           the string.
        *
        * @return   The resulting substring.
        */
       public static character substring(String src, int pos, int len)
       {
          return substringImpl(character.class, src, pos, len, null, false, null, true);
       }

       /**
        * Returns the substring of the string at the given 1-based index and
        * of the given length.
        * <p>
        * The returned instance is case-insensitive.
        *
        * @param    src
        *           The string to operate upon.
        * @param    pos
        *           The 1-based index at which the substring starts.
        * @param    len
        *           The length of the substring to return. Use -1 for the rest of
        *           the string.
        * @param    type
        *           Specifies whether this substring operates on bytes or
        *           characters.  At this time only "raw" and "character" are
        *           supported.
        *
        * @return   The resulting substring.
        */
       public static character substring(String  src,
                                         integer pos,
                                         int     len,
                                         String  type)
       {
          if (pos.isUnknown())
             return instantiateUnknown(character.class);

          return substringImpl(character.class, src, pos.intValue(), len, type, false, null, true);
       }

       /**
        * Returns the substring of the string at the given 1-based index and
        * of the given length.
        * <p>
        * The returned instance is case-insensitive.
        *
        * @param    src
        *           The string to operate upon.
        * @param    pos
        *           The 1-based index at which the substring starts.
        * @param    len
        *           The length of the substring to return. Use -1 for the rest of
        *           the string.
        * @param    type
        *           Specifies whether this substring operates on bytes or
        *           characters.  At this time only "raw" and "character" are
        *           supported.
        *
        * @return   The resulting substring.
        */
       public static character substring(String src,
                                         int    pos,
                                         int    len,
                                         String type)
       {
          return substringImpl(character.class, src, pos, len, type, false, null, true);
       }

       /**
        * Returns the substring of the string at the given 1-based index and
        * of the given length.
        *
        * @param    src
        *           The string to operate upon.
        * @param    pos
        *           The 1-based index at which the substring starts.
        * @param    len
        *           The length of the substring to return. Use -1 for the rest of
        *           the string.
        * @param    type
        *           Specifies whether this substring operates on bytes or
        *           characters.  At this time only "raw" and "character" are
        *           supported.
        * @param    caseSens
        *           Determines the case-sensitivity of the returned instance.
        *
        * @return   The resulting substring.
        */
       public static character substring(String  src,
                                         int     pos,
                                         int     len,
                                         String  type,
                                         boolean caseSens)
       {
          return substringImpl(character.class, src, pos, len, type, caseSens, null, true);
       }

       /**
        * Returns a character instance that contains the lowercased version of
        * the specified string. This may generate incorrect results in a
        * DBCS environment since it will use UNICODE as the basis for case
        * conversion. If this returned value is different than would be returned
        * under Progress AND if that Progress-specific value is used in
        * comparison with this returned value, then a difference in logic will be
        * seen.
        * <p>
        * This method does not handle the case of user-specified source and/or
        * target codepages as is possible in Progress.
        * <p>
        * If the operand is case-sensitive, the result will be case-sensitive.
        *
        * @param    c
        *           The character instance to convert (cannot be
        *           <code>null</code>).
        *
        * @return   The lowercased character instance (which should match up
        *           with the Progress equivalents in the ASCII range) or
        *           <code>unknown value</code> if the input string is
        *           <code>unknown value</code>.
        */
       public static <T extends Text> T toLowerCase(T c)
       {
          if (c.isUnknown())
             return (T) instantiateUnknown(c.getClass());

          T res = (T) toLowerCaseImpl(c.getClass(), c.getValue(), getCodePage(c), c.isJavaSpacifyNull());

          // honor case-sensitivity
          res.setCaseSensitive(c.isCaseSensitive());

          return res;
       }

       /**
        * Returns a character instance that contains the lowercased version of
        * the specified string. This may generate incorrect results in a
        * DBCS environment since it will use UNICODE as the basis for case
        * conversion. If this returned value is different than would be returned
        * under Progress AND if that Progress-specific value is used in
        * comparison with this returned value, then a difference in logic will be
        * seen.
        * <p>
        * This method does not handle the case of user-specified source and/or
        * target codepages as is possible in Progress.
        * <p>
        * The result will be case-insensitive.
        *
        * @param    str
        *           The string to convert (cannot be <code>null</code>).
        *
        * @return   The lowercased character instance which should match up
        *           with the Progress equivalents in the ASCII range.
        */
       public static character toLowerCase(String str)
       {
          return toLowerCaseImpl(character.class, str, null, true);
       }

       /**
        * Returns a character instance that contains the lowercased version of
        * the specified string. This may generate incorrect results in a
        * DBCS environment since it will use UNICODE as the basis for case
        * conversion. If this returned value is different than would be returned
        * under Progress AND if that Progress-specific value is used in
        * comparison with this returned value, then a difference in logic will be
        * seen.
        * <p>
        * This method does not handle the case of user-specified source and/or
        * target codepages as is possible in Progress.
        * <p>
        * The result will be case-insensitive.
        *
        * @param    c
        *           The text to convert (cannot be <code>null</code>).
        *
        * @return   The lowercased character instance which should match up
        *           with the Progress equivalents in the ASCII range.
        */
       public static Text toLowerCase(BaseDataType c)
       {
          if (c instanceof Text)
          {
             return toLowerCase((Text) c);
          }

          return toLowerCase(c.toStringMessage());
       }

       /**
        * Returns a character instance that contains the uppercased version of
        * the specified string. This may generate incorrect results in a
        * DBCS environment since it will use UNICODE as the basis for case
        * conversion. If this returned value is different than would be returned
        * under Progress AND if that Progress-specific value is used in
        * comparison with this returned value, then a difference in logic will be
        * seen.
        * <p>
        * This method does not handle the case of user-specified source and/or
        * target codepages as is possible in Progress.
        * <p>
        * If the operand is case-sensitive, the result will be case-sensitive.
        *
        * @param    c
        *           The character instance to convert (cannot be
        *           <code>null</code>).
        *
        * @return   The uppercased character instance (which should match up
        *           with the Progress equivalents in the ASCII range) or
        *           <code>unknown value</code> if the input string is
        *           <code>unknown value</code>.
        */
       public static Text toUpperCase(BaseDataType c)
       {
          if (c instanceof Text)
          {
             return toUpperCase((Text) c);
          }

          return toUpperCase(c.toStringMessage());
       }

       /**
        * Returns a character instance that contains the uppercased version of
        * the specified string. This may generate incorrect results in a
        * DBCS environment since it will use UNICODE as the basis for case
        * conversion. If this returned value is different than would be returned
        * under Progress AND if that Progress-specific value is used in
        * comparison with this returned value, then a difference in logic will be
        * seen.
        * <p>
        * This method does not handle the case of user-specified source and/or
        * target codepages as is possible in Progress.
        * <p>
        * If the operand is case-sensitive, the result will be case-sensitive.
        *
        * @param    c
        *           The character instance to convert (cannot be
        *           <code>null</code>).
        *
        * @return   The uppercased character instance (which should match up
        *           with the Progress equivalents in the ASCII range) or
        *           <code>unknown value</code> if the input string is
        *           <code>unknown value</code>.
        */
       public static <T extends Text> T toUpperCase(T c)
       {
          if (c.isUnknown())
             return (T) instantiateUnknown(c.getClass());

          T res = (T) toUpperCaseImpl(c.getClass(), c.getValue(), getCodePage(c), c.isJavaSpacifyNull());

          // honor case-sensitivity
          res.setCaseSensitive(c.isCaseSensitive());

          return res;
       }

       /**
        * Returns a character instance that contains the uppercased version of
        * the specified string. This may generate incorrect results in a
        * DBCS environment since it will use UNICODE as the basis for case
        * conversion. If this returned value is different than would be returned
        * under Progress AND if that Progress-specific value is used in
        * comparison with this returned value, then a difference in logic will be
        * seen.
        * <p>
        * This method does not handle the case of user-specified source and/or
        * target codepages as is possible in Progress.
        * <p>
        *The result will be case-insensitive.
        *
        * @param    str
        *           The string to convert (cannot be <code>null</code>).
        *
        * @return   The uppercased character instance which should match up
        *           with the Progress equivalents in the ASCII range.
        */
       public static character toUpperCase(String str)
       {
          return toUpperCaseImpl(character.class, str, null, true);
       }

       /**
        * Returns a string instance that contains the uppercased version of
        * the specified string. This may generate incorrect results in a
        * DBCS environment since it will use UNICODE as the basis for case
        * conversion. If this returned value is different than would be returned
        * under Progress AND if that Progress-specific value is used in
        * comparison with this returned value, then a difference in logic will be
        * seen.
        * <p>
        * This method does not handle the case of user-specified source and/or
        * target codepages as is possible in Progress.
        * <p>
        *The result will be case-insensitive.
        *
        * @param    str
        *           The string to convert (cannot be <code>null</code>).
        * @param    runtimeString
        *           <code>true</code> if the target string is a runtime string,
        *           <code>false</code> if it is a hardcoded string into a source
        *           file.
        *
        * @return   The uppercased character instance which should match up
        *           with the Progress equivalents in the ASCII range.
        */
       public static String toUpperCase(String str, boolean runtimeString)
       {
          str = str.toUpperCase();

          if (!runtimeString)
          {
             StringBuilder sb = new StringBuilder();
             int end = str.length();

             for (int i = 0; i < end; i++)
             {
                char current = str.charAt(i);
                char next;

                if (current == '\\')
                {
                   i++;
                   next = str.charAt(i);
                   sb.append('\\');

                   // change back mistakenly uppercased special characters \t \r \n \b \f into a hardcoded string
                   // NOTE: this seems dangerous, what if the original text contained these uppercased letters,
                   //       prefixed by backslash?
                   switch (next)
                   {
                      case 'T':
                         sb.append('t');
                         break;
                      case 'R':
                         sb.append('r');
                         break;
                      case 'N':
                         sb.append('n');
                         break;
                      case 'B':
                         sb.append('b');
                         break;
                      case 'F':
                         sb.append('f');
                         break;
                      default:
                         sb.append(next);
                   }
                }
                else
                {
                   sb.append(current);
                }
             }

             str = sb.toString();
          }

          return str;
       }

       /**
        * Trims the list of contiguous trim characters from the left and right
        * of an input string.
        * <p>
        * Trim operations are performed based on the case-sensitivity of only
        * the input parameter, the trim character list case-sensitivity is
        * ignored.  Likewise, if the input parameter is case-sensitive, then
        * the returned instance will be case-sensitive.
        *
        * @param    input
        *           The string to trim.
        * @param    list
        *           The list of characters to trim.
        *
        * @return   The trimmed result.
        */
       public static <T extends Text> T trim(T input, character list)
       {
          if (input.isUnknown() || list.isUnknown())
          {
             return (T) instantiateUnknown(input.getClass());
          }

          return (T) trimImpl(input.getClass(),
                              input.getValue(),
                              list.getValue(),
                              input.isCaseSensitive(),
                              getCodePage(input),
                              input.isJavaSpacifyNull());
       }

       /**
        * Trims the list of contiguous trim characters from the left and right
        * of an input string.
        * <p>
        * Trim operations are performed based on the case-sensitivity of the
        * input parameter. Likewise, if the input parameter is case-sensitive,
        * then the returned instance will be case-sensitive.
        *
        * @param    input
        *           The string to trim.
        * @param    list
        *           The list of characters to trim.
        *
        * @return   The trimmed result.
        */
       public static <T extends Text> T trim(T input, String list)
       {
          if (input.isUnknown())
          {
             return (T) instantiateUnknown(input.getClass());
          }

          return (T) trimImpl(input.getClass(),
                              input.getValue(),
                              list,
                              input.isCaseSensitive(),
                              getCodePage(input),
                              input.isJavaSpacifyNull());
       }

       /**
        * Trims any contiguous space, tab, line feed and carriage return from the
        * left and right of an input string.
        * <p>
        * Trim operations are performed based on the case-sensitivity of the
        * input parameter.  Likewise, if the input parameter is case-sensitive,
        * then the returned instance will be case-sensitive.
        *
        * @param    input
        *           The string to trim.
        *
        * @return   The trimmed result.
        */
       public static <T extends Text> T trim(T input)
       {
          if (input.isUnknown())
          {
             return (T) instantiateUnknown(input.getClass());
          }

          return (T) trimImpl(input.getClass(),
                              input.getValue(),
                              null,
                              input.isCaseSensitive(),
                              getCodePage(input),
                              input.isJavaSpacifyNull());
       }

       /**
        * Trims the list of contiguous trim characters from the left and right
        * of an input string.
        * <p>
        * Trim operations are performed case-insensitively and the returned
        * result will be case-insensitive.
        *
        * @param    input
        *           The string to trim.
        * @param    list
        *           The list of characters to trim.
        *
        * @return   The trimmed result.
        */
       public static character trim(String input, character list)
       {
          if (list.isUnknown())
          {
             return new character();
          }

          return trimImpl(character.class, input, list.getValue(), false, null, true);
       }

       /**
        * Trims the list of contiguous trim characters from the left and right
        * of an input string. If the list of trim characters is <code>null</code>
        * the method defaults to space, tab, line feed and carriage return.
        * <p>
        * Trim operations and the returned instance will be case-insensitive.
        *
        * @param    input
        *           The string to trim.
        * @param    list
        *           The list of characters to trim or <code>null</code> to
        *           use the default list.
        *
        * @return   The trimmed result.
        */
       public static character trim(String input, String list)
       {
          return trimImpl(character.class, input, list, false, null, true);
       }

       /**
        * Trims any contiguous space, tab, line feed and carriage return from the
        * left and right of an input string.  Trim operations are performed
        * case-insensitively.
        * <p>
        * Trim operations and the returned instance will be case-insensitive.
        *
        * @param    input
        *           The string to trim.
        *
        * @return   The trimmed result.
        */
       public static character trim(String input)
       {
          return trimImpl(character.class, input, (String) null, false, null, true);
       }

       /**
        * Trims the list of contiguous trim characters from the left and right
        * of an input string. If the list of trim characters is <code>null</code>
        * the method defaults to space, tab, line feed and carriage return.
        *
        * @param    input
        *           The string to trim.
        * @param    list
        *           The list of characters to trim or <code>null</code> to
        *           use the default list.
        * @param    caseSens
        *           Specifies the case-sensitivity of the trim operation AND of
        *           the returned instance.
        *
        * @return   The trimmed result.
        */
       public static character trim(String input, String list, boolean caseSens)
       {
          // trim left
          String result = trim(input, list, true, caseSens);

          // trim right
          return new character(trim(result, list, false, caseSens), caseSens);
       }

       /**
        * Trims the list of contiguous trim characters from the left or right of an input string. If
        * the list of trim characters is {@code null} the method defaults to space.
        *
        * @param    input
        *           The string to trim.
        * @param    list
        *           The list of characters to trim or {@code null} to use the default list.
        * @param    left
        *           Specifies if the trim should occur on the left or right side of the string.
        * @param    caseSens
        *           If {@code true}, a case-sensitive trim is performed.
        *
        * @return   The trimmed result.
        */
       public static String trim(String input, String list, boolean left, boolean caseSens)
       {
          if (input == null)
          {
             return null;
          }

          // implement our default list
          if (list == null)
          {
             list = " \t\n\r";
             // using white-space doesn't require case insensitivity
             caseSens = true;
          }
          else if (!StringHelper.hasAlphabet(list))
          {
             caseSens = true;
          }

          int len  = input.length();
          int llen = list.length();
          int i;

          for (i = left ? 0 : len - 1; left ? i < len : i >= 0;)
          {
             boolean found = false;
             char    c     = input.charAt(i);
             char    cu    = caseSens ? c : Character.toUpperCase(c); // in case sensitive cu is ignored, anyway

             for (int j = 0; j < llen; j++)
             {
                char ch  = list.charAt(j);

                if (caseSens && ch == c)
                {
                   found = true;
                   break;
                }
                else if (!caseSens)
                {
                   char chu = Character.toUpperCase(ch);
                   if (chu == cu)
                   {
                      found = true;
                      break;
                   }
                }
             }

             if (!found)
             {
                break;
             }

             if (left)
             {
                i++;
             }
             else
             {
                i--;
             }
          }

          return left ? input.substring(i) : input.substring(0, i + 1);
       }

       /**
        * Check if a text is empty or contains only spaces.
        *
        * In 4GL a string containing only empty spaces is equal with an empty string.
        *
        * @param value The text to check.
        *
        * @return True if the text is unknown, empty or contains only spaces.
        */
       public static boolean isEmpty(Text value) {
          return value == null || isEmpty(value.getValue());
       }

       /**
        * Check if a text is empty or contains only spaces.
        *
        * In 4GL a string containing only empty spaces is equal with an empty string.
        *
        * @param value The string to check.
        *
        * @return True if the string is null, empty or contains only spaces.
        */
       public static boolean isEmpty(String value) {
          // only real spaces are considered (not tab, cr, lf)
          if (value == null)
          {
             return true;
          }

          int idx = 0;
          while (idx < value.length())
          {
             if (value.charAt(idx) != ' ')
             {
                return false;
             }
             idx = idx + 1;
          }

          return true;
       }

       /**
        * Generate an error for a substring or overlay index that is negative.
        */
       static void genNegativeIndexError()
       {
          String err = "Starting position for SUBSTRING, OVERLAY, etc. must" +
                       " be 1 or greater";
          ErrorManager.recordOrThrowError(82, err);
       }

       /**
        * Generate an error for a value out of range.
        *
        * @param    idx
        *           The index that is out of range.
        * @param    list
        *           The list being indexed.
        */
       static void genOutsideRangeError(int idx, String list)
       {
          String spec = "Entry %d is outside the range of list %s";
          String err  = String.format(spec, idx, list);

          ErrorManager.recordOrThrowError(560, err, false);
       }

       /**
        * Fixup the given character to be used in regular expression.
        *
        * @param    ch
        *           The character to test.
        *
        * @return   The resulting regular expression.
        */
       static String fixupRegexChar(char ch)
       {
          String result = null;

          if (isFixupRegexChar(ch))
          {
             result = new String(new char[] { '\\', ch });
          }
          else
          {
             result = new String(new char[] { ch });
          }

          return result;
       }

       /**
        * Check if the given character is need to be fixed while using in regular expression.
        *
        * @param    ch
        *           The character to test.
        *
        * @return   <code>true</code> if the fuxup is required, <code>false</code> otherwise.
        */
       static boolean isFixupRegexChar(char ch)
       {
          boolean result = false;

          switch (ch)
          {
             case '(':
             case ')':
             case '.':
             case '*':
             case '[':
             case ']':
             case '^':
             case '{':
             case '}':
             case '?':
             case '+':
             case '$':
             case '|':
             case '\\':
                result = true;
                break;
             default:
                result = false;
                break;
          }

          return result;
       }

       /**
        * Given a delimiter string, extract the first character and retrun it.  If
        * the string is empty, return a space character.
        *
        * @param   delimiter
        *          Delimiter string
        *
        * @return  Delimiter character as described above.
        */
       static char safeDelimiter(String delimiter)
       {
          return (delimiter.length() == 0 ? ' ' : delimiter.charAt(0));
       }

       /**
        * Convert the compare operator's string representation to its numeric representation, to be used by the
        * {@link #compare(Text, int, Text, int)} method.
        *
        * @param    compare
        *           The operator's string representation.
        *
        * @return   The numeric representation.
        */
       private static int getCompareOperator(character compare)
       {
          String op = compare.toJavaType();

          if (op != null)
          {
             if ("=".equals(op) || "EQ".equalsIgnoreCase(op))
                return EQ;

             if ("<>".equals(op) || "NE".equalsIgnoreCase(op))
                return NE;

             if ("<".equals(op) || "LT".equalsIgnoreCase(op))
                return LT;

             if ("<=".equals(op) || "LE".equalsIgnoreCase(op))
                return LE;

             if (">".equals(op) || "GT".equalsIgnoreCase(op))
                return GT;

             if (">=".equals(op) || "GE".equalsIgnoreCase(op))
                return GE;

             if ("MATCHES".equalsIgnoreCase(op))
                return MATCHES;

             if ("BEGINS".equalsIgnoreCase(op))
                return BEGINS;
          }

          ErrorManager.recordOrThrowError(9092, op == null ? "?" : op.toUpperCase(), "");
          // Invalid COMPARE operator: <COMPARE operator> (9092)
          return -1;
       }

       /**
        * Convert the strength's string representation to its numeric
        * representation, to be used by the {@link #compare(Text, int, Text, int)}
        * method.
        *
        * @param    strength
        *           The strength's string representation.
        *
        * @return   The numeric representation.
        */
       private static int getStrength(character strength)
       {
          String value = strength.toJavaType();

          if (value != null)
          {
             if ("CASE-SENSITIVE".equalsIgnoreCase(value))
                return CASE_SENSITIVE;

             if ("CASE-INSENSITIVE".equalsIgnoreCase(value))
                return CASE_INSENSITIVE;

             if ("RAW".equalsIgnoreCase(value))
                return RAW;

             if ("CAPS".equalsIgnoreCase(value))
                return CAPS;
          }

          ErrorManager.recordOrThrowError(9093, value == null ? "?" : value.toUpperCase(), "");
          // Invalid COMPARE strength: <Compare strength> (9093)
          return -1;
       }

       /**
        * Returns the specified 1-based indexed entry in the passed list.
        *
        * @param    ii
        *           The 1-based index into the list.
        * @param    list
        *           The delimited list of entries.
        * @param    delimit
        *           The delimiter character.
        *
        * @return   The entry in the list or the <code>unknown value</code> if
        *           any of the inputs are <code>unknown value</code>.
        */
       private static <T extends Text> T entryImpl(Class<T> cls,
                                                   double ii,
                                                   String list,
                                                   char delimit)
       throws ErrorConditionException
       {
          if (list == null)
          {
             return instantiateUnknown(cls);
          }

          // convert 1 based index to 0 based
          int i   = (int) ii;
          int idx = i - 1;

          // split up the list (make sure all possible entries are included,
          // even empty trailing entries --> use -1 as the limit to force this)
          // using our own implementation of the split, which returns a 3-element array, with:
          // - prefix (everything before the found element) is not returned
          // - the element at the given position
          // - suffix (everything after the found element) is not returned
          String[] array = splitInt(list, delimit, idx, true);

          if (array == null)
          {
             genOutsideRangeError(i, list);

             // this is the safest thing to return (it should be undone anyway)
             return instantiate(cls, "");
          }

          return instantiate(cls, array[1]);
       }

       /**
        * Returns the list of entries in the passed list.
        *
        * @param    list
        *           The delimited list of entries.
        * @param    delimit
        *           The delimiter character.
        *
        * @return   All entries in the list using the delimiter provided.
        */
       public static String[] entries(String list, String delimit)
       {
         return entries(list, delimit, 0);
       }

       /**
        * Returns the list of entries in the passed list.
        *
        * @param    list
        *           The delimited list of entries.
        * @param    delimit
        *           The delimiter character.
        * @param    numEntries
        *           The number of entries to return, if less out of range error is thrown.
        *
        * @return   All entries in the list using the delimiter provided.
        */
       public static String[] entries(String list, String delimit, int numEntries)
       {
          // split up the list (make sure all possible entries are included,
          // even empty trailing entries --> use -1 as the limit to force this)
          // using our own implementation of the split
          String[] entries = splitInt(list, safeDelimiter(delimit));

         if (numEntries > 0 && numEntries > entries.length) {
            genOutsideRangeError(numEntries, list);

            return new String[0];
         }

         return entries;
       }

       /**
        * Returns the list of entries in the passed list.
        *
        * @param    cls
        *           The class used to instantiate the list entries.
        * @param    list
        *           The delimited list of entries.
        * @param    delimit
        *           The delimiter character.
        *
        * @return   All entries in the list using the delimiter provided.
        */
       public static <T extends Text> T[] entries(Class<T> cls,
                                                   String list,
                                                   String delimit)
       throws ErrorConditionException {
          return entries(cls, list, delimit, 0);
       }

       /**
        * Returns the list of entries in the passed list.
        *
        * @param    cls
        *           The class used to instantiate the list entries.
        * @param    list
        *           The delimited list of entries.
        * @param    delimit
        *           The delimiter character.
        * @param    numEntries
        *           The number of entries to return, if less out of range error is thrown.
        *
        * @return   All entries in the list using the delimiter provided.
        */
       public static <T extends Text> T[] entries(Class<T> cls,
                                                   String list,
                                                   String delimit,
                                                   int numEntries)
       throws ErrorConditionException {
          List<T> entries = Arrays.stream(entries(list, delimit, numEntries)).map(e -> instantiate(cls, e)).collect(Collectors.toList());

          return entries.toArray((T[]) Array.newInstance(cls, entries.size()));
       }

       /**
        * Create a new instance of the given {@link Text} subclass and set it to
        * unknown.
        *
        * @param    cls
        *           A {@link Text} subclass for which an unknown instance is
        *           needed.
        *
        * @return   See above.
        */
       private static <T extends Text> T instantiateUnknown(Class<T> cls)
       {
          try
          {
             return BaseDataTypeFactory.instantiate(cls);
          }
          catch (Exception e)
          {
             // TODO: should this be ignored ?
             ErrorManager.handleStopException(e.getMessage());
          }

          return (T) Text.generateUnknown(cls);
       }

       /**
        * Optimized replacement for JavaSE split function.  Considering the count option as <code>-1</code>.
        *
        * @param    src
        *           The source string to split.
        * @param    delim
        *           The delimiter char value to be used in source string splitting.
        *
        * @return   The array of strings split from source.
        */
       public static String[] splitInt(String src, char delim)
       {
          String[] result = null;
          // convert string to array of chars
          char[] srcChars = src.toCharArray();

          // protection from some inexpected cases
          if (srcChars == null || srcChars.length == 0 || delim == '\0')
          {
             result = new String[1];
             result[0] = src;
             return result;
          }

          int srcLen = src.length();                // length of src string
          int arraySize = 0;                        // number of substrings
          int[] subLengths = new int[srcLen + 1];   // lengths of substrings
          int i = 0;                                // used to loop inside chars array
          int start = 0;                            // starting point for next substring
          // first find out how much elements should be in array
          for (; i < srcLen; i++)
          {
             if (srcChars[i] == delim)
             {
                // record the length of the substring
                subLengths[arraySize] = i - start;
                arraySize++;
                // starting point for next substring just after delimiter
                start = i + 1;
             }
          }
          // last section
          if (i == srcLen && start <= i)
          {
             subLengths[arraySize] = i - start;
             arraySize++;
          }

          // allocate new array and fill it with respective substrings
          result = new String[arraySize];
          if (arraySize == 1)
          {
             // this is the simplest case we can do in a fastest way
             result[0] = src;
          }
          else
          {
             for (i = 0, start = 0; i < arraySize; i++)
             {
                result[i] = new String(srcChars, start, subLengths[i]);
                start += subLengths[i] + 1;
             }
          }

          return result;
       }

       /**
        * Finds the entry at the position given by 'element', using the specified delimiter.  This does not return
        * all entries after splitting the string using the delimiter - instead, a 3-size array is returned, where:
        * <ul>
        *    <li>
        *       First entry (position zero) is everything before the found element (may be null for first element).
        *       This will end with the delimiter.
        *    </li>
        *    <li>Second entry (position one) is the found element.</li>
        *    <li>
        *       Third entry (position two) is everything after the found element (may be null if the element is
        *       the last entry).
        *    </li>
        * </ul>
        *
        * @param    src
        *           The source string to split.
        * @param    delim
        *           The delimiter to use.
        * @param    element
        *           The zero-based element to find.
        * @param    onlyEntry
        *           Flag indicating just the entry needs to be returned on index 1 in the result.
        *
        * @return   See above.  {@code null} will be returned if the element can't be resolved.
        */
       public static String[] splitInt(String src, char delim, int element, boolean onlyEntry)
       {
          if (element < 0)
          {
             return null;
          }

          if (src.isEmpty())
          {
             return element == 0 ? new String[] { null, "", null } : null;
          }

          if (delim == '\0')
          {
             if (element != 0)
             {
                return null;
             }

             int idx = src.indexOf(delim);
             return new String[] { null, idx == -1 ? src : src.substring(0, idx), null };
          }

          // convert string to array of chars
          char[] srcChars = src.toCharArray();

          int srcLen = src.length();                // length of src string
          int arraySize = 0;                        // number of substrings
          int[] subLengths = new int[srcLen + 1];   // lengths of substrings
          int i = 0;                                // used to loop inside chars array
          int start = 0;                            // starting point for next substring
          // first find out how much elements should be in array
          for (; i < srcLen; i++)
          {
             if (srcChars[i] == delim)
             {
                // record the length of the substring
                subLengths[arraySize] = i - start;
                arraySize++;
                // starting point for next substring just after delimiter
                start = i + 1;

                if (arraySize - 1 == element)
                {
                   // we found the element, the suffix no longer needs to be split
                   i = srcLen;
                   break;
                }
             }
          }
          // last section
          if (i == srcLen && start <= i)
          {
             subLengths[arraySize] = i - start;
             arraySize++;
          }

          if (element >= arraySize)
          {
             return null;
          }

          int prefixLength = 0;
          for (i = 0; i < element; i++)
          {
             prefixLength += subLengths[i] + 1;
          }
          String[] result = new String[3];
          start = prefixLength;
          if (onlyEntry)
          {
             result[0] = null;
             result[1] = new String(srcChars, start, subLengths[element]);
             result[2] = null;
          }
          else
          {
             result[0] = start == 0 ? null : new String(srcChars, 0, start);
             result[1] = new String(srcChars, start, subLengths[element]);
             start += subLengths[element] + 1;
             result[2] = start >= srcChars.length
                              // be aware of an empty element as the last in the list
                            ? (element < arraySize - 1 && srcChars[srcChars.length - 1] == delim ? "" : null)
                            : new String(srcChars, start, srcChars.length - start);
          }

          return result;
       }

       /**
        * Create a new instance of the given {@link Text} subclass and set its
        * value.  If the <code>val</code> is null, the new instance will be set
        * to unknown.
        *
        * @param    cls
        *           A {@link Text} subclass for which a new instance is needed.
        * @param    val
        *           The value to set for the new instance.
        *
        * @return   See above.
        */
       private static <T extends Text> T instantiate(Class<T> cls, String val)
       {
          return instantiate(cls, val, null, true);
       }

       /**
        * Create a new instance of the given {@link Text} subclass and set its
        * value.  If the <code>val</code> is null, the new instance will be set
        * to unknown.
        *
        * @param    cls
        *           A {@link Text} subclass for which a new instance is needed.
        * @param    val
        *           The value to set for the new instance.
        * @param    codepage
        *           The codepage to apply to the result; non-null only for {@link longchar} values.
        * @param    javaSpacifyNull
        *           Flag indicating if {@link Text#javaSpacifyNull(String)} needs to be executed on the value.
        *
        * @return   See above.
        */
       private static <T extends Text> T instantiate(Class<T> cls,
                                                     String   val,
                                                     String   codepage,
                                                     boolean  javaSpacifyNull)
       {
          if (val == null)
          {
             return instantiateUnknown(cls);
          }

          try
          {
             T t;
             if (cls == character.class)
             {
                t = (T) new character(val, false, javaSpacifyNull);
             }
             else
             {
                t = BaseDataTypeFactory.instantiate(cls);
                if (codepage != null)
                {
                   ((longchar) t).fixCodePage(codepage);
                }
                t.assign(val);
             }

             return t;
          }
          catch (Exception e)
          {
             // TODO: should this be ignored ?
             ErrorManager.handleStopException(e.getMessage());
          }
          return (T) Text.generateUnknown(cls);
       }

       /**
        * Trims the list of contiguous trim characters from the left of an input
        * string.  If the list of trim characters is <code>null</code>
        * the method defaults to space, tab, line feed and carriage return.
        *
        * @param    input
        *           The string to trim.
        * @param    list
        *           The list of characters to trim or <code>null</code> to
        *           use the default list.
        * @param    caseSens
        *           Specifies the case-sensitivity of the trim operation AND of
        *           the returned instance.
        * @param    codepage
        *           The codepage to apply to the result; non-null only for {@link longchar} values.
        * @param    javaSpacifyNull
        *           Flag indicating if {@link Text#javaSpacifyNull(String)} needs to be executed on the value.
        *
        * @return   The trimmed result.
        */
       private static <T extends Text> T leftTrimImpl(Class<T> cls,
                                                      String  input,
                                                      String  list,
                                                      boolean caseSens,
                                                      String  codepage,
                                                      boolean javaSpacifyNull)
       {
          T res = (T) instantiate(cls, trim(input, list, true, caseSens), codepage, javaSpacifyNull);
          res.setCaseSensitive(caseSens);

          return res;
       }

       /**
        * Searches for all matches to the <code>from</code> string in the
        * <code>src</code> string and replaces these with the <code>to</code>
        * string.
        *
        * @param    src
        *           The string to operate upon.
        * @param    from
        *           The substring to search for.
        * @param    to
        *           The substring to replace when a match is found.
        * @param    cs
        *           Determines if a case-sensitive match should be performed AND
        *           the case-sensitivity of the returned instance.
        * @param    codepage
        *           The codepage to apply to the result; non-null only for {@link longchar} values.
        * @param    srcJavaSpacifcyNull
        *           Flag indicating if {@link Text#javaSpacifyNull(String)} needs to be executed on the src value.
        * @param    fromJavaSpacifcyNull
        *           Flag indicating if {@link Text#javaSpacifyNull(String)} needs to be executed on the from value.
        * @param    toJavaSpacifcyNull
        *           Flag indicating if {@link Text#javaSpacifyNull(String)} needs to be executed on the to value.
        *
        * @return   The resulting string.
        */
       private static <T extends Text> T replaceAllImpl(Class<T> cls,
                                                        String   src,
                                                        String   from,
                                                        String   to,
                                                        boolean  cs,
                                                        String   codepage,
                                                        boolean  srcJavaSpacifcyNull,
                                                        boolean  fromJavaSpacifcyNull,
                                                        boolean  toJavaSpacifcyNull)
       {
          int flen = from.length();
          boolean csOrig = cs;
          if (flen == 0)
          {
             ErrorManager.recordOrThrowError(2918, "Second parameter of REPLACE must have non-zero length", false);
             return null;
          }

          int len  = src.length();
          if (flen > len)
          {
             T res = instantiate(cls, src, codepage, srcJavaSpacifcyNull);
             res.setCaseSensitive(csOrig);
             return res;
          }

          if (from.length() == 1 && to.length() == 1)
          {
             int cfrom = from.codePointAt(0);
             int cto = to.codePointAt(0);
             if (!(Character.isAlphabetic(cfrom) || Character.isAlphabetic(cto)))
             {
                src = src.replace(from.charAt(0), to.charAt(0));

                T res = instantiate(cls, src, codepage, srcJavaSpacifcyNull);
                res.setCaseSensitive(csOrig);

                return res;
             }
          }

          if (src.length() == from.length() && TextOps.getUpper(src).equals(TextOps.getUpper(from)))
          {
             T res = instantiate(cls, to, codepage, toJavaSpacifcyNull);
             res.setCaseSensitive(csOrig);

             return res;
          }

          if (!cs)
          {
             boolean allNonAlpha = true;
             for (int i = 0; i < from.length(); i++)
             {
                if (Character.isAlphabetic(from.charAt(i)))
                {
                   allNonAlpha = false;
                   break;
                }
             }

             // if there is nothing to look for which would be case-insensitive, assume case-sensitive
             cs = allNonAlpha;
          }

          StringBuilder sb = null;

          // find the indexes on which the pattern appears in the source, and use these indexes for the result
          List<Integer> indexes = kmpSearch(src, from, cs);
          if (indexes != null)
          {
             sb = new StringBuilder(src.length() + (indexes.size() * to.length()));
             int i1 = 0;
             int i2 = indexes.get(0);
             if (i2 != 0)
             {
                sb.append(src.subSequence(0,  i2));
             }
             for (int i = 0; i < indexes.size(); i++)
             {
                sb.append(to);
                i1 = indexes.get(i) + flen;
                i2 = i == indexes.size() - 1 ? src.length() : indexes.get(i + 1);
                sb.append(src.substring(i1, i2));
             }
          }

          String resVal = sb == null ? src : sb.toString();

          T res = instantiate(cls,
                              resVal,
                              codepage,
                              srcJavaSpacifcyNull || fromJavaSpacifcyNull || toJavaSpacifcyNull);
          res.setCaseSensitive(csOrig);

          return res;
       }

       /**
        * Perform a KMP search of all occurrences of the pattern in the given source string, without the pattern
        * overlapping.
        *
        * @param    source
        *           The source string.
        * @param    pattern
        *           The pattern to search.
        * @param    cs
        *           Determines if a case-sensitive match should be performed.
        *
        * @return   The list of all indexes where this pattern appears, or {@code null} if none found.
        */
       private static List<Integer> kmpSearch(String source, String pattern, boolean cs)
       {
          if (pattern.isEmpty() || source.isEmpty())
          {
             return null;
          }

          int m = pattern.length();

          // calculate the longest prefix which is also a suffix, for each substring
          int len = 0;
          int[] lps = new int[m];
          int i = 1;
          lps[0] = 0;
          while (i < m)
          {
             if (StringHelper.charEquals(pattern.charAt(i), pattern.charAt(len), cs))
             {
                len = len + 1;
                lps[i] = len;
                i = i + 1;
              }
              else
              {
                 if (len == 0)
                 {
                    lps[i++] = 0;
                 }
                 else
                 {
                    len = lps[len - 1];
                 }
              }
          }

          List<Integer> found = null;
          i = 0;
          int j = 0;
          int n = source.length();
          while (i < n)
          {
             if (StringHelper.charEquals(pattern.charAt(j), source.charAt(i), cs))
             {
                j++;
                i++;
              }

              if (j == m)
              {
                 if (found == null)
                 {
                    found = new ArrayList<>(3);
                 }
                 found.add(i - j);
                 j = 0; // start over, as occurrences must not overlap
              }
              else if (i < n && !StringHelper.charEquals(pattern.charAt(j), source.charAt(i), cs))
              {
                 if (j == 0)
                 {
                    i = i + 1;
                 }
                 else
                 {
                    j = lps[j - 1];
                 }
              }
          }

          return found;
       }

       /**
        * Trims the list of contiguous trim characters from the right of an input
        * string.  If the list of trim characters is <code>null</code>
        * the method defaults to space, tab, line feed and carriage return.
        *
        * @param    input
        *           The string to trim.
        * @param    list
        *           The list of characters to trim or <code>null</code> to
        *           use the default list.
        * @param    caseSens
        *           Specifies the case-sensitivity of the trim operation AND of
        *           the returned instance.
        * @param    codepage
        *           The codepage to apply to the result; non-null only for {@link longchar} values.
        * @param    javaSpacifyNull
        *           Flag indicating if {@link Text#javaSpacifyNull(String)} needs to be executed on the value.
        *
        * @return   The trimmed result.
        */
       private static <T extends Text> T rightTrimImpl(Class<T> cls,
                                                       String   input,
                                                       String   list,
                                                       boolean  caseSens,
                                                       String   codepage,
                                                       boolean  javaSpacifyNull)
       {
          T res = instantiate(cls, trim(input, list, false, caseSens), codepage, javaSpacifyNull);
          res.setCaseSensitive(caseSens);

          return res;
       }

       /**
        * Perform positional replacement of '&amp;n' specifiers in the format string
        * <code>fmt</code>, using the array of <code>character</code> and/or
        * <code>String</code> objects as the list of replacements.  The 1-based
        * index position of the '&amp;n' specifiers (where n and range from 1 to
        * 9) is converted to a 0-based index into the <code>replacements</code>
        * array.  Instances of '&amp;&amp;' yield a single '&amp;' in the result.
        * Any specifiers that do not correspond to an object in the array are
        * replaced with an empty string.
        *
        * @param    fmt
        *           The string specifying the text and format specifiers.
        * @param    replacements
        *           The list of replacements.
        *
        * @return   The resulting string.
        */
       private static <T extends Text> T substituteImpl(Class<T> cls,
                                                        String fmt,
                                                        Object... replacements)
       throws ErrorConditionException
       {
          StringBuilder sb = null;

          int len  = fmt.length();

          for (int i = 0; i < len; i++)
          {
             char c = fmt.charAt(i);

             if (c == '&')
             {
                if (sb == null)
                {
                   sb = new StringBuilder(len);
                   sb.append(fmt.subSequence(0, i));
                }

                // ignore this char and look at the next one
                i++;
                c = fmt.charAt(i);

                if (c >= '0' && c <= '9')
                {
                   // convert this character into a 0-based index into the array
                   int idx = Character.digit(c, 10) - 1;

                   // check if this is a valid replacement index (note that 0 is
                   // always ignored/invalid), if not the result is an empty
                   // string since we don't make any replacement in that case
                   if (idx >= 0 && idx < replacements.length)
                   {
                      Object obj = replacements[idx];

                      if (obj != null)
                      {
                         // make a replacement if it is an object we recognize
                         if (obj instanceof String)
                         {
                            sb.append((String) obj);
                         }
                         else if (obj instanceof Boolean)
                         {
                            // this is required because P4GL uses yes/no instead of true/false
                            sb.append(logical.of(((Boolean) obj)));
                         }
                         else if (obj instanceof Text)
                         {
                            Text ch = (Text) obj;
                            sb.append(ch.isUnknown() ? "?" : ch.getValue());
                         }
                         else if (obj instanceof object)
                         {
                            object o = (object) obj;
                            if (o.isUnknown())
                            {
                               sb.append("?");
                            }
                            else if (o.ref instanceof LegacyEnum)
                            {
                               sb.append(((LegacyEnum) o.ref)._getValue());
                            }
                            else
                            {
                               sb.append(object.resourceId(o));
                            }
                         }
                         else if (obj instanceof BaseDataType)
                         {
                            BaseDataType bdt = (BaseDataType) obj;
                            sb.append(bdt.toStringMessage());
                         }
                         else
                         {
                            sb.append(obj.toString());
                         }
                      }
                   }
                }
                else if (c == '&')
                {
                   sb.append(c);
                }
                else
                {
                   String spec = "Invalid character \'%c\' following \'&\'" +
                                 " in SUBSTITUTE";
                   ErrorManager.recordOrThrowError(2920, String.format(spec, c));

                   // this is the safest thing to return (it should be undone anyway)
                   return instantiate(cls, "");
                }
             }
             else
             {
                if (sb != null)
                {
                   // no match, put the char into the output
                   sb.append(c);
                }
             }
          }

          String res = sb == null ? fmt : sb.toString();

          return instantiate(cls, res);
       }

       /**
        * Returns the substring of the string at the given 1-based index and
        * of the given length.
        *
        * @param    src
        *           The string to operate upon.
        * @param    pos
        *           The 1-based index at which the substring starts.
        * @param    len
        *           The length of the substring to return. Use -1 for the rest of
        *           the string.
        * @param    type
        *           Specifies whether this substring operates on bytes or
        *           characters.  At this time only "raw" and "character" are
        *           supported.
        * @param    caseSens
        *           Determines the case-sensitivity of the returned instance.
        * @param    codepage
        *           The codepage to apply to the result; non-null only for {@link longchar} values.
        * @param    javaSpacifyNull
        *           Flag indicating if {@link Text#javaSpacifyNull(String)} needs to be executed on the value.
        *
        * @return   The resulting substring.
        */
       private static <T extends Text> T substringImpl(Class<T> cls,
                                                       String   src,
                                                       int      pos,
                                                       int      len,
                                                       String   type,
                                                       boolean  caseSens,
                                                       String   codepage,
                                                       boolean  javaSpacifyNull)
       {
          String result = null;

          // convert the Progress 1-based index into Java's 0-based index
          int start = pos - 1;

          if (pos < 1)
          {
             genNegativeIndexError();
             return instantiate(cls, "", codepage, false);
          }

          if (type != null && "raw".equalsIgnoreCase(type))
          {
             // raw mode (operates on bytes)
             byte[] buffer = src.getBytes();

             if (start >= buffer.length)
             {
                // requesting data past the end of the string yields the empty string
                result = "";
             }
             else
             {
                int end = start + len;

                if (len == -1 || end >= buffer.length)
                {
                   len = buffer.length - start;
                }

                result = new String(buffer, start, len);
             }
          }
          else
          {
             // character mode

             if (start >= src.length())
             {
                // requesting data past the end of the string yields the empty
                // string
                result = "";
             }
             else
             {
                int end = start + len;

                if (len == -1 || end >= src.length())
                {
                   // if the requested data is more than the rest of the string
                   // or if the rest of the string was explicitly requested via
                   // -1
                   result = src.substring(start);
                }
                else
                {
                   // only return a portion
                   result = src.substring(start, end);
                }
             }
          }

          T res = instantiate(cls, result, codepage, javaSpacifyNull);
          res.setCaseSensitive(caseSens);

          return res;
       }

       /**
        * Returns a character instance that contains the uppercased version of the specified string.
        * This may generate incorrect results in a DBCS environment since it will use UNICODE as the
        * basis for case conversion. If this returned value is different than would be returned under
        * Progress AND if that Progress-specific value is used in comparison with this returned value,
        * then a difference in logic will be seen.
        * <p>
        * This method does not handle the case of user-specified source and/or target codepages as is
        * possible in Progress.
        * <p>
        * The result will be case-insensitive.
        *
        * @param    str
        *           The string to convert (cannot be {@code null}).
        * @param    codepage
        *           The codepage to apply to the result; non-null only for {@link longchar} values.
        * @param    javaSpacifyNull
        *           Flag indicating if {@link Text#javaSpacifyNull(String)} needs to be executed on the value.
        *
        * @return  The uppercased character instance which should match up with the Progress
        *          equivalents in the ASCII range.
        */
       private static <T extends Text> T toUpperCaseImpl(Class<T> cls,
                                                         String   str,
                                                         String   codepage,
                                                         boolean  javaSpacifyNull)
       {
          // TODO: use the correct locale for switching cases
          String upper = getUpper(str);

          return instantiate(cls, upper, codepage, javaSpacifyNull);
       }

       /**
        * Returns a character instance that contains the lowercased version of
        * the specified string. This may generate incorrect results in a
        * DBCS environment since it will use UNICODE as the basis for case
        * conversion. If this returned value is different than would be returned
        * under Progress AND if that Progress-specific value is used in
        * comparison with this returned value, then a difference in logic will be
        * seen.
        * <p>
        * This method does not handle the case of user-specified source and/or
        * target codepages as is possible in Progress.
        * <p>
        * The result will be case-insensitive.
        *
        * @param    str
        *           The string to convert (cannot be <code>null</code>).
        * @param    codepage
        *           The codepage to apply to the result; non-null only for {@link longchar} values.
        * @param    javaSpacifyNull
        *           Flag indicating if {@link Text#javaSpacifyNull(String)} needs to be executed on the value.
        *
        * @return   The lowercased character instance which should match up
        *           with the Progress equivalents in the ASCII range.
        */
       private static <T extends Text> T toLowerCaseImpl(Class<T> cls,
                                                         String   str,
                                                         String   codepage,
                                                         boolean  javaSpacifyNull)
       {
          // TODO: use the correct locale for switching cases
          return instantiate(cls, str.toLowerCase(), codepage, javaSpacifyNull);
       }

       /**
        * Trims the list of contiguous trim characters from the left and right of an input string. If
        * the list of trim characters is {@code null} the method defaults to space.
        *
        * @param    input
        *           The string to trim.
        * @param    list
        *           The list of characters to trim or {@code null} to use the default list.
        * @param    caseSens
        *           Specifies the case-sensitivity of the trim operation AND of the returned instance.
        * @param    codepage
        *           The codepage to apply to the result; non-null only for {@link longchar} values.
        * @param    javaSpacifyNull
        *           Flag indicating if {@link Text#javaSpacifyNull(String)} needs to be executed on the value.
        *
        * @return   The trimmed result.
        */
       private static <T extends Text> T trimImpl(Class<T> cls,
                                                  String   input,
                                                  String   list,
                                                  boolean  caseSens,
                                                  String   codepage,
                                                  boolean  javaSpacifyNull)
       {
          // trim left
          String result = trim(input, list, true, caseSens);

          // trim right
          T res = instantiate(cls, trim(result, list, false, caseSens), codepage, javaSpacifyNull);
          res.setCaseSensitive(caseSens);

          return res;
       }

       /**
        * Returns the length of the given <code>character</code> instance, using the type parameter
        * to determine the measurement.
        * This method checks the <code>type</code> argument and decide to what method to delegate
        * the actual implementation depending on the 2nd parameter.
        *
        * @param    c
        *           The instance to test.
        * @param    type
        *           The measurement mode.
        *
        * @return   The length of the given text
        */
       private static integer length(String c, String type)
       {
          if (type == null)
          {
             return integer.UNKNOWN;
          }

          switch (type.toLowerCase())
          {
             case "raw":
                return TextOps.byteLength(c);

             case "character":
             case "char":
                return TextOps.length(c);

             case "column":
                return TextOps.columnLength(c);

             default:
    // the code below is correct, but causes dependency on [com.goldencode.p2j.ui] package
    //            if (LogicalTerminal.isChui())
    //            {
    //               // in ChUI mode returning the same value as length(..., 'CHAR')
    //               return TextOps.length(c);
    //            }

                // in GUI mode this is an error:
                ErrorManager.recordOrThrowError(
                      1186, "The data type argument value must be \"raw\", \"character\" or \"column\"", true);
                return integer.UNKNOWN;
          }
       }

       /**
        * Check if the given text matches the pattern.  This enforces the DOT to match anything.
        *
        * @param    c
        *           The text to match.
        * @param    p
        *           The pattern to match.
        *
        * @return   <code>true</code> if the text matches the pattern.
        */
       private static boolean matchImpl(String c, String p)
       {
          p = "^" + p + "$"; // in 4GL, matches must always satisfy the entire text

          Pattern p2;

          // check the pattern cache first
          synchronized (matchPatternCache)
          {
             p2 = matchPatternCache.get(p);
          }

          if (p2 == null)
          {
             // compile regex as case-insensitive and apply it to the item
             p2 = Pattern.compile(p, Pattern.DOTALL);
             synchronized (matchPatternCache)
             {
                matchPatternCache.put(p, p2);
             }
          }

          return p2.matcher(c).find();
       }

       /**
        * If the input is a {@link longchar}, get the codepage.
        *
        * @param    input
        *           The source.
        *
        * @return   The source codepage if this is a {@link longchar}, otherwise <code>null</code>.
        */
       private static <T> String getCodePage(T input)
       {
          if (input instanceof longchar)
          {
             return ((longchar) input)._getCodePage();
          }

          return null;
       }
    }