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