Text.java

/*
** Module   : Text.java
** Abstract : Base class for P2J's longchar and character implementations.
**
** Copyright (c) 2013-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- --------------------------------Description---------------------------------
** 001 CA  20130130 Created initial version by extracting the internals from the character
**                  class.
** 002 GES 20130322 Fixed the BDT c'tor and modified the assign(BDT,boolean) to be the
**                  common location for the _POLY morphing. This is safe because all current
**                  usage would have generated a ClassCastException if that method was ever used
**                  with an invalid type. Now, instead of the CCE, the code will morph the value
**                  as is possible. The morphing code still needs to be finished.
** 003 CA  20131013 Added no-op deleted() method, required by the changes in Finalizable interface.
** 004 ECF 20140417 Implemented quick fix for missing _POLY support.
** 005 OM  20141014 Improved _POLY support for assign(Undoable).
** 006 GES 20141003 Fixed embedded null character behavior. Construction of a new instance or
**                  assignment from strings that have embedded nulls is disallowed.  Construction
**                  or assignment from existing instances that have null characters is allowed
**                  if the subclass allows it.  A special setter is provided for explicit use
**                  in forcing assignment of string data that has embedded nulls. That is the
**                  only way null data can get into an instance. Some helper methods were moved
**                  here from a subclass.
** 007 ECF 20150715 Replace StringBuffer with StringBuilder.
** 008 OM  20150924 Added missing replace() overloads.
** 009 ECF 20151111 Performance fix: in assign(Text, boolean), defer expensive null character scan
**                  until after we determine whether embedded nulls are allowed; common case
**                  allows them.
** 010 ECF 20160227 Fixed heading numbers.
** 011 ECF 20160430 Optimized compareTo and equalsCaseInsensitively to avoid costly string
**                  comparisons if the internal String objects are the same instance.
** 012 OM  20160603 Fixed rtrimming to space character only.
** 013 GES 20160617 Fixed unknown literal processing in assign() for processing polymorphic
**                  return values.
** 014 ECF 20160614 Implemented faster copy constructor.
** 015 CA  20160627 Reworked undoable support - the undoables register themselves with all blocks 
**                  up the stack, until either 1. the tx block which created it is reached or 
**                  2. the last tx block where it was saved is reached.
** 016 SBI 20160915 Fixed an extra delimiter that was added by replaceEntry. 
** 017 IAS 20160919 Serialization optimization.
** 018 SBI 20160922 Fixed NPE if ENTRY is used for an unknown value.
** 019 CA  20161004 Fixed direct comparison with unknown values.
** 020 EVL 20171108 Changed read and write approach for String value from using (read|write)UTF()
**                  to (read|write)Object() to properly handle Windows Unicode strings.
** 021 GES 20171206 Removed explicit bypass on pending error (new silent error mode).
** 022 CA  20190508 Added support for CASE-SENSITIVE extent var option.
** 023 SVL 20190614 Added assign(clob) to prevent abend during dynamic tables compilation.
** 024 CA  20190927 Added support for direct Java access from 4GL code.
** 025 CA  20191203 Clear the value when setting to unknown.
** 026 CA  20200506 Fixed runtime for character vs longchar OO method calls.
** 027 ME  20200528 Add assign for "legacy" object - toLegacyString.
** 028 IAS 20200908 Rework (de)serialization.
** 029 EVL 20200922 Replace Java split() with our implementation for TextOps.splitInt().
**     VVT 20201001 javaTruncateNull() optimized.
**     ME  20210504 Added isIncompatibleTypesOnConversion override for invalid POLY conversion.
**     CA  20210609 Reworked INPUT/INPUT-OUTPUT parameters to a new approach, where they are explicitly 
**                  initialized at the method's execution, and not at the caller's arguments.
**     AL2 20220319 Added value proxy check in assign.
**     ME  20220404 Right trim string for hashCode value.
** 030 HC  20230118 Eliminated some of the uses of String.toUpperCase and/or String.toLowerCase
**                  for performance.
** 031 EVL 20230317 Adding method to get internal Text object length.  In some conditions for longchar this
**                  can be different than simple length of the backend String variable.
** 032 CA  20240324 Short-circuit compareTo when one or both the operands are empty.
** 033 CA  20240423 Optimized 'replaceEntry' to create a minimal number of Strings.
** 034 SB  20240530 Use the string value of a BinaryData instance for assignment, therefore accounting
**                  for charset. Refs #8783.
** 035 CA  20241030 Avoid 'Text.javaSpacifyNull' if the string is known to already have been processed.
** 036 AS  20241205 Introduced valueEquals method.
** 037 FER 20250139 Modified compareTo() method to return '-1' if this value is null regardless of the type
**                  of the obj to be compared with. Otherwise it would lead to NPE.
*/

/*
** 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 static com.goldencode.util.NativeTypeSerializer.*; 
import java.io.*;
import java.nio.charset.Charset;
import com.goldencode.util.*;

/**
 * Base class for the {@link character} and {@link longchar} data types.
 */
public abstract class Text
extends BaseDataType
{
   /** Charset name to use for encoding or <code>null</code> for default. */
   protected static final String override = Utils.getCharsetOverride();
   
   /**
    * Stores the state of this character as an instance of class
    * <code>String</code>.  This is independent of the state of whether or
    * not this instance represents the <code>unknown value</code>.
    */
   protected String value = null; 
   
   /**
    * Stores the state of whether or not this instance represents the
    * <code>unknown value</code>, independent of the state of this
    * character's data.
    */
   protected boolean unknown = false;
   
   /**
    * Stores the state of whether or not this instance compares case
    * sensitively.
    */
   protected boolean caseSens = false;
   
   /** Flag indicating if {@link #javaSpacifyNull(String)} should execute. */
   protected boolean javaSpacifyNull = true;
   
   /**
    * Default constructor, creates an instance that represents the unknown
    * value.
    */
   public Text()
   {
      unknown = true;
   }

   /**
    * This is a special c'tor which should be used only when converting the
    * value returned by a function or method with polymorphic return type into the
    * expected type (i.e. DYNAMIC-FUNCTION()).  In such cases, the 4GL does some
    * automatic type conversion (see {@link #assign(BaseDataType)}).
    * 
    * @param    value
    *           The value to be used for this instance.
    */
   public Text(BaseDataType value)
   {
      // the value may be a proxy; make sure to unwrap and work with the real value
      value = value == null ? null : value.val();
      if (value == null || value.isUnknown())
      {
         setUnknown();
      }
      else
      {
         assign(value);
      }
   }

   /**
    * Constructs an instance that exactly matches that of the passed-in instance.
    *
    * @param    value
    *           The value of the resulting instance.
    */
   public Text(Text value)
   {
      // the value may be a proxy; make sure to unwrap and work with the real value
      if (BaseDataType.isProxy(value))
      {
         if (value.val() instanceof Text)
         {
            value = (Text) value.val();
         }
         else
         {
            assign(value.val());
            return;
         }
      }
      deepAssign(value);
   }
   
   /**
    * Constructs an instance that has the <code>String</code> value passed in and which
    * <b>does not</b> represent the <code>unknown value</code>, unless the <code>value</code>
    * argument is <code>null</code>.
    * <p>
    * Strings (especially converted literals) can have embedded null characters, but in the 4GL
    * the resulting variable instances are "spacified".  The given value will be processed by
    * {@link #javaSpacifyNull} before storing it in the new instance.
    *
    * @param    value
    *           The character value of the resulting instance.
    */
   public Text(String value)
   {
      if (value == null)
      {
         setUnknown();
      }
      else
      {
         // protected against embedded null characters 
         this.value = javaSpacifyNull(value);
         this.javaSpacifyNull = false;
      }
   }
   
   /**
    * Constructs an instance that has the <code>String</code> value passed in and which
    * <b>does not</b> represent the <code>unknown value</code>, unless the <code>value</code>
    * argument is <code>null</code>.
    * <p>
    * Strings (especially converted literals) can have embedded null characters, but in the 4GL
    * the resulting variable instances are "spacified".  The given value will be processed by
    * {@link #javaSpacifyNull} before storing it in the new instance.
    *
    * @param    value
    *           The character value of the resulting instance.
    * @param    caseSens
    *           Specifies whether this instance should use case sensitive
    *           comparisons.
    */
   public Text(String value, boolean caseSens)
   {
      this(value, caseSens, true);
   }
   
   
   /**
    * Constructs an instance that has the <code>String</code> value passed in and which
    * <b>does not</b> represent the <code>unknown value</code>, unless the <code>value</code>
    * argument is <code>null</code>.
    * <p>
    * Strings (especially converted literals) can have embedded null characters, but in the 4GL
    * the resulting variable instances are "spacified".  The given value will be processed by
    * {@link #javaSpacifyNull} before storing it in the new instance.
    *
    * @param    value
    *           The character value of the resulting instance.
    * @param    caseSens
    *           Specifies whether this instance should use case sensitive
    *           comparisons.
    * @param    javaSpacifyNull
    *           Flag indicating if {@link #javaSpacifyNull(String)} needs to be executed on the value or not.
    */
   public Text(String value, boolean caseSens, boolean javaSpacifyNull)
   {
      if (value == null)
      {
         setUnknown();
      }
      else
      {
         // protected against embedded null characters 
         this.value    = javaSpacifyNull ? javaSpacifyNull(value) : value;
         this.caseSens = caseSens;
         this.javaSpacifyNull = javaSpacifyNull;
      }
   }
   
   /**
    * Constructs an instance that has a <code>char</code> value passed in and which
    * <b>does not</b> represent the <code>unknown value</code>.
    * <p>
    * If the given value is the null character, an ASCII space character will be assigned. 
    *
    * @param    ch
    *           The character value of the resulting instance.
    */
   public Text(char ch)
   {
      // protected against embedded null characters
      this.value = (ch == '\0') ? " " : new StringBuilder().append(ch).toString();
      javaSpacifyNull = false;
   }

   /**
    * A method that checks the equality of two Text parameters.
    * The comparison of the String values is low level, it ignores
    * case sensitivity and trailing/leading spaces.
    *
    * @param   op1
    *          The first operand of the comparison.
    * @param   op2
    *          The second operand of the comparison
    * 
    * @return  True if the operands have the same String value.
    */
   public static boolean valueEquals(Text op1, Text op2)
   {
      if (op1.unknown == op2.unknown && op1.unknown)
      {
         return true;
      }
      if (op1.unknown != op2.unknown)
      {
         return false;
      }
      return op1.value.equals(op2.value);
   }

   /**
    * Set the CASE-SENSITIVE option for all elements in this array.
    * 
    * @param    list
    *           The character or longchar array.
    * @param    caseSens
    *           Flag indicating if the CASE-SENSITIVE option is on or off.
    */
   public static void setCaseSensitive(Text[] list, boolean caseSens)
   {
      for (Text item : list)
      {
         item.setCaseSensitive(caseSens);
      }
      
      // set the decimal precision, for lengths > 0 the precision is stored 
      // in the array components
      if (list.length == 0)
      {
         ArrayAssigner.setCaseSensitive(list, caseSens);
      }
   }
   
   /**
    * Return a string which contains the contents of the given string up to 
    * (but not including) the first <code>null</code> character (ASCII 0x00)
    * and for that <code>null</code> character and all following characters
    * the resulting string will contain spaces. 
    * <p>
    * The resulting length of the output string will be the same as the
    * length of the input string.
    * <p>
    * This processing is needed by some operators and/or functionality that
    * process <code>character</code> data.
    * <p>
    * This transformation is for runtime strings, NOT for Progress compatible
    * string literals.  If a source code conversion is needed, please use
    * {@link #progressSpacifyNull}.
    * <p>
    * As an example, a string such as:
    * <p>
    * <pre>
    * Input                               Output
    * ----------------------------------  ----------------------------------
    * "This has a \0 (null) char."        "This has a               "
    * "\0"                                " "
    * </pre>
    *
    * @param    data
    *           The input string (not valid for encoding in a source file).
    *
    * @return   The "space-ified" output string (still not valid for
    *           encoding in a source file).           
    */
   public static String javaSpacifyNull(String data)
   {
      if (data.isEmpty())
      {
         return data;
      }
      
      StringBuilder sb = null;
      
      int idx = data.indexOf('\0');
      
      if (idx != -1)
      {
         sb = new StringBuilder(data.substring(0, idx));
         StringHelper.repeatChar(' ', (data.length() - idx), sb);
      }
      
      return sb == null ? data : sb.toString();
   }

   /**
    * Return a string which contains the contents of the given string up to 
    * (but not including) the first <code>null</code> character (ASCII 0x00),
    * the resulting string is truncated at the first <code>null</code>.
    * <p>
    * As an example, a string such as:
    * <p>
    * <pre>
    * Input                               Output
    * ----------------------------------  ----------------------------------
    * "This has a \0 (null) char."        "This has a "
    * "\0"                                ""
    * </pre>
    *
    * @param    data
    *           The input string (not valid for encoding in a source file).
    *
    * @return   The truncated output string (still not valid for
    *           encoding in a source file).
    */
   public static String javaTruncateNull(String data)
   {
      int idx = data.indexOf('\0');

      if (idx == -1)
      {
         return data;
      }

      return data.substring(0, idx);
   }
   
   /**
    * Return a string which contains the contents of the given string up to 
    * (but not including) the first <code>null</code> character (encoded as a
    * Progress escape sequence) and for that <code>null</code> character and 
    * all following characters the resulting string will contain spaces. 
    * <p>
    * The resulting length of the string (in terms of the number of
    * characters that the string will include at runtime) will NOT change,
    * although the length of the string representation (as a source code
    * representation stored in a Java string) may be reduced as the
    * <code>null</code> character's escape sequence (which would require 4
    * characters to represent as in <code>~000</code>) and any following
    * escaped sequences will be converted to a space which only consumes
    * a single character position.
    * <p>
    * The enclosing single or double quote characters are maintained (not
    * overwritten).
    * <p>
    * This processing is needed by the Progress preprocessor and other
    * conversion processing of string literals.
    * <p>
    * This transformation is for Progress compatible string literals, NOT for
    * runtime strings.  If a runtime string conversion is needed, please use
    * {@link #javaSpacifyNull}.  Both forms of the <code>null</code>
    * character escape sequence are handled.
    * <p>
    * As an example, a Progress string such as:
    * <p>
    * <pre>
    * Input                               Output
    * ----------------------------------  ----------------------------------
    * "This has a ~000 (null) char."      "This has a               "
    * 'This has a ~000 (null) char.'      'This has a               '
    * "~000"                              " "
    * '~000'                              ' '
    * "\000"                              " "
    * '\000'                              ' '
    * </pre>
    *
    * @param    data
    *           A Progress source code representation of a string literal
    *           which is the input string to be transformed.
    * @param    unixEsc
    *           <code>true</code> if the backslash character should be
    *           treated as an escape character.
    *
    * @return   The "space-ified" output string (still valid as a Progress
    *           string literal that could be output into source code).
    */
   public static String progressSpacifyNull(String data, boolean unixEsc)
   {
      StringBuilder sb = new StringBuilder();
      
      // put the leading single or double quote char into the output buffer 
      char quote = data.charAt(0);
      sb.append(quote);
      
      // only scan up to the trailing quote char
      int end = data.length() - 1;
      
      boolean eat = false;
      
      // loop through the contents
      for (int i = 1; i < end; i++) 
      {
         char current = data.charAt(i);
         char next;
         
         if (current == '~' || (current == '\\' && unixEsc))
         {
            if (!eat)
            {
               // flow through mode
               
               // check for the null escape sequence
               String esc = ((i + 4) <= end) ? data.substring(i + 1, i + 4) : null;
               
               if (esc != null && esc.equals("000"))
               {
                  // the first null, shift into space-ify mode
                  eat = true;
                  i += 3;
                  sb.append(' ');
               }
               else
               {
                  // some other escape sequence, allow it to flow through
                  sb.append(current);
                  
                  // there must always be a following character, if it is
                  // a doubled escape char, we must handle it here to avoid 
                  // dropping back through this code since it is not the
                  // opening of a new escape sequence (since all following
                  // chars can be handled the same way we just go ahead
                  // no matter which one it is)
                  i++;
                  sb.append(readChar(data, i));
               }
            }
            else
            {
               // space-ify mode (eat the full escape sequence here)
               
               // output the replacement (always 1 char)
               sb.append(' ');
               
               // there must always be at least 1 following character
               i++;
               next = readChar(data, i);
               
               if (next == 'b'  || next == 't'  || next == 'n'  ||
                   next == 'f'  || next == 'r'  || next == '\"' || 
                   next == '\\' || next == '~'  || next == 'E')
               {
                  // these are only 1 char in size, so we are done
               }
               else if (next >= '0' && next <= '7')
               {
                  // eat the next 2 chars too because this is an octal
                  // escape sequence
                  i += 2;
               }
            }
         }
         else
         {
            sb.append(eat ? ' ' : current);
         }
      }
      
      // add the trailing (close) quote char
      sb.append(data.charAt(end));
      
      return sb.toString();
   }
   
   /**
    * Test for certain failure conditions and if OK return the indexed character, otherwise throw
    * an exception with useful debugging information.
    *
    * @param    input
    *           The string to read from.
    * @param    index
    *           The 0-based index into the string, from which to read the character.
    *
    * @return   The character at the specified index.
    *
    * @throws   IllegalArgumentException
    *           If the index is out of bounds.
    */
   public static char readChar(String input, int index)
   throws IllegalArgumentException
   {
      char next;
      
      if (index == input.length())
      {
         throw new IllegalArgumentException("Unexpected end of string.");
      }
      
      try
      {
         next = input.charAt(index); 
      }
      catch (IndexOutOfBoundsException iob)
      {
         throw new IllegalArgumentException("Invalid index (" + index +
                                            " in string \"" + input + "\").");
      }
      
      return next; 
   }
   
   /**
    * Report if existing instances that have embedded null characters are allowed to be
    * assigned to this instance.
    *
    * @return   Always <code>true</code>. Sub-classes can override this to change the
    *           behavior.
    */
   public boolean isEmbeddedNullAllowed()
   {
      return true;
   }

   /**
    * Returns the larger (lexicographically) of the current or passed-in
    * instance.
    *
    * @param    c
    *           The instance to compare against.
    *
    * @return   The largest instance.
    */
   public Text maximum(Text c)   
   {
      return (Text) super.maximum(c);
   }

   /**
    * Returns the smaller (lexicographically) of the current or passed-in 
    * instance.
    *
    * @param    c
    *           The instance to compare against.
    *
    * @return   The smallest instance.
    */
   public Text minimum(Text c)   
   {
      return (Text) super.minimum(c);
   }

   /**
    * Overwrites characters in the current value of this instance with the 
    * passed text at the 1-based index into the string.  The entire
    * replacement text will be placed into the resulting value.
    *
    * @param    text
    *           Replacement text.
    * @param    pos
    *           The 1-based index into the string at which the replacement
    *           is to occur.
    *
    * @return   The current instance is returned.
    * @throws ErrorConditionException 
    */
   public Text overlay(Text text, double pos)
   throws ErrorConditionException
   {
      replaceWorker(text, new integer(pos), new integer(-1), true);
      return this;
   }

   /**
    * Overwrites characters in the current value of this instance with the 
    * passed text at the 1-based index into the string.  The entire
    * replacement text will be placed into the resulting value.
    *
    * @param    text
    *           Replacement text.
    * @param    pos
    *           The 1-based index into the string at which the replacement
    *           is to occur.
    * @param    len
    *           The number of characters in the source string to replace
    *           and the number of characters from the replacement text to 
    *           include in the replacement.
    *
    * @return   The current instance is returned.
    * @throws ErrorConditionException 
    */
   public Text overlay(Text text, double pos, double len)
   throws ErrorConditionException
   {
      replaceWorker(text, new integer(pos), new integer(len), true);
      return this;
   }

   /**
    * Overwrites characters in the current value of this instance with the 
    * passed text at the 1-based index into the string.  The entire
    * replacement text will be placed into the resulting value.
    *
    * @param    text
    *           Replacement text.
    * @param    pos
    *           The 1-based index into the string at which the replacement
    *           is to occur.
    * @param    len
    *           The number of characters in the source string to replace
    *           and the number of characters from the replacement text to 
    *           include in the replacement.
    * @param    type
    *           Description of how to interpret the position and length values.
    *           At this time only "character" is supported.
    *
    * @return   The current instance is returned.
    * @throws ErrorConditionException 
    */
   public Text overlay(Text text, double pos, double len, String type)
   throws ErrorConditionException
   {
      checkOverlayType(type);
      replaceWorker(text, new integer(pos), new integer(len), true);
      return this;
   }

   /**
    * Overwrites characters in the current value of this instance with the 
    * passed text at the 1-based index into the string.  The entire
    * replacement text will be placed into the resulting value.
    *
    * @param    text
    *           Replacement text.
    * @param    pos
    *           The 1-based index into the string at which the replacement
    *           is to occur.
    * @param    len
    *           The number of characters in the source string to replace
    *           and the number of characters from the replacement text to 
    *           include in the replacement.
    *
    * @return   The current instance is returned.
    * @throws ErrorConditionException 
    */
   public Text overlay(Text text, double pos, NumberType len)
   throws ErrorConditionException
   {
      replaceWorker(text, new integer(pos), len, true);
      return this;
   }

   /**
    * Overwrites characters in the current value of this instance with the 
    * passed text at the 1-based index into the string.  The entire
    * replacement text will be placed into the resulting value.
    *
    * @param    text
    *           Replacement text.
    * @param    pos
    *           The 1-based index into the string at which the replacement
    *           is to occur.
    * @param    len
    *           The number of characters in the source string to replace
    *           and the number of characters from the replacement text to 
    *           include in the replacement.
    * @param    type
    *           Description of how to interpret the position and length values.
    *           At this time only "character" is supported.
    *
    * @return   The current instance is returned.
    * @throws ErrorConditionException 
    */
   public Text overlay(Text  text, double pos, NumberType len, String type)
   throws ErrorConditionException
   {
      checkOverlayType(type);
      replaceWorker(text, new integer(pos), len, true);
      return this;
   }

   /**
    * Overwrites characters in the current value of this instance with the 
    * passed text at the 1-based index into the string.  The entire
    * replacement text will be placed into the resulting value.
    *
    * @param    text
    *           Replacement text.
    * @param    pos
    *           The 1-based index into the string at which the replacement
    *           is to occur.
    *
    * @return   The current instance is returned.
    * @throws ErrorConditionException 
    */
   public Text overlay(Text text, NumberType pos)
   throws ErrorConditionException
   {
      replaceWorker(text, pos, new integer(-1), true);
      return this;
   }

   /**
    * Overwrites characters in the current value of this instance with the 
    * passed text at the 1-based index into the string.  The entire
    * replacement text will be placed into the resulting value.
    *
    * @param    text
    *           Replacement text.
    * @param    pos
    *           The 1-based index into the string at which the replacement
    *           is to occur.
    * @param    len
    *           The number of characters in the source string to replace
    *           and the number of characters from the replacement text to 
    *           include in the replacement.
    *
    * @return   The current instance is returned.
    * @throws ErrorConditionException 
    */
   public Text overlay(Text text, NumberType pos, double len)
   throws ErrorConditionException
   {
      replaceWorker(text, pos, new integer(len), true);
      return this;
   }

   /**
    * Overwrites characters in the current value of this instance with the 
    * passed text at the 1-based index into the string.  The entire
    * replacement text will be placed into the resulting value.
    *
    * @param    text
    *           Replacement text.
    * @param    pos
    *           The 1-based index into the string at which the replacement
    *           is to occur.
    * @param    len
    *           The number of characters in the source string to replace
    *           and the number of characters from the replacement text to 
    *           include in the replacement.
    * @param    type
    *           Description of how to interpret the position and length values.
    *           At this time only "character" is supported.
    *
    * @return   The current instance is returned.
    * @throws ErrorConditionException 
    */
   public Text overlay(Text text, NumberType pos, double len, String type)
   throws ErrorConditionException
   {
      checkOverlayType(type);
      replaceWorker(text, pos, new integer(len), true);
      return this;
   }

   /**
    * Overwrites characters in the current value of this instance with the 
    * passed text at the 1-based index into the string.  The entire
    * replacement text will be placed into the resulting value.
    *
    * @param    text
    *           Replacement text.
    * @param    pos
    *           The 1-based index into the string at which the replacement
    *           is to occur.
    * @param    len
    *           The number of characters in the source string to replace
    *           and the number of characters from the replacement text to 
    *           include in the replacement.
    *
    * @return   The current instance is returned.
    * @throws ErrorConditionException 
    */
   public Text overlay(Text text, NumberType pos, NumberType len)
   throws ErrorConditionException
   {
      replaceWorker(text, pos, len, true);
      return this;
   }

   /**
    * Overwrites characters in the current value of this instance with the 
    * passed text at the 1-based index into the string.  The entire
    * replacement text will be placed into the resulting value.
    *
    * @param    text
    *           Replacement text.
    * @param    pos
    *           The 1-based index into the string at which the replacement
    *           is to occur.
    * @param    len
    *           The number of characters in the source string to replace
    *           and the number of characters from the replacement text to 
    *           include in the replacement.
    * @param    type
    *           Description of how to interpret the position and length values.
    *           At this time only "character" is supported.
    *
    * @return   The current instance is returned.
    * @throws ErrorConditionException 
    */
   public Text overlay(Text text, NumberType pos, NumberType len, String type)
   throws ErrorConditionException
   {
      checkOverlayType(type);
      replaceWorker(text, pos, len, true);
      return this;
   }

   /**
    * Overwrites characters in the current value of this instance with the 
    * passed text at the 1-based index into the string.  The entire
    * replacement text will be placed into the resulting value.
    *
    * @param    text
    *           Replacement text.
    * @param    pos
    *           The 1-based index into the string at which the replacement
    *           is to occur.
    *
    * @return   The current instance is returned.
    * @throws ErrorConditionException 
    */
   public Text overlay(String text, double pos)
   throws ErrorConditionException
   {
      replaceWorker(new character(text),
                    new integer(pos),
                    new integer(-1),
                    true);
      return this;
   }

   /**
    * Overwrites characters in the current value of this instance with the 
    * passed text at the 1-based index into the string.  The entire
    * replacement text will be placed into the resulting value.
    *
    * @param    text
    *           Replacement text.
    * @param    pos
    *           The 1-based index into the string at which the replacement
    *           is to occur.
    * @param    len
    *           The number of characters in the source string to replace
    *           and the number of characters from the replacement text to 
    *           include in the replacement.
    *
    * @return   The current instance is returned.
    * @throws ErrorConditionException 
    */
   public Text overlay(String text, double pos, double len)
   throws ErrorConditionException
   {
      replaceWorker(new character(text),
                    new integer(pos),
                    new integer(len),
                    true);
      return this;
   }

   /**
    * Overwrites characters in the current value of this instance with the 
    * passed text at the 1-based index into the string.  The entire
    * replacement text will be placed into the resulting value.
    *
    * @param    text
    *           Replacement text.
    * @param    pos
    *           The 1-based index into the string at which the replacement
    *           is to occur.
    * @param    len
    *           The number of characters in the source string to replace
    *           and the number of characters from the replacement text to 
    *           include in the replacement.
    * @param    type
    *           Description of how to interpret the position and length values.
    *           At this time only "character" is supported.
    *
    * @return   The current instance is returned.
    * @throws ErrorConditionException 
    */
   public Text overlay(String text, double pos, double len, String type)
   throws ErrorConditionException
   {
      checkOverlayType(type);
      replaceWorker(new character(text),
                    new integer(pos),
                    new integer(len),
                    true);
      return this;
   }

   /**
    * Overwrites characters in the current value of this instance with the 
    * passed text at the 1-based index into the string.  The entire
    * replacement text will be placed into the resulting value.
    *
    * @param    text
    *           Replacement text.
    * @param    pos
    *           The 1-based index into the string at which the replacement
    *           is to occur.
    * @param    len
    *           The number of characters in the source string to replace
    *           and the number of characters from the replacement text to 
    *           include in the replacement.
    *
    * @return   The current instance is returned.
    * @throws ErrorConditionException 
    */
   public Text overlay(String text, double pos, NumberType len)
   throws ErrorConditionException
   {
      replaceWorker(new character(text), new integer(pos), len, true);
      return this;
   }

   /**
    * Overwrites characters in the current value of this instance with the 
    * passed text at the 1-based index into the string.  The entire
    * replacement text will be placed into the resulting value.
    *
    * @param    text
    *           Replacement text.
    * @param    pos
    *           The 1-based index into the string at which the replacement
    *           is to occur.
    * @param    len
    *           The number of characters in the source string to replace
    *           and the number of characters from the replacement text to 
    *           include in the replacement.
    * @param    type
    *           Description of how to interpret the position and length values.
    *           At this time only "character" is supported.
    *
    * @return   The current instance is returned.
    * @throws ErrorConditionException 
    */
   public Text overlay(String text, double pos, NumberType len, String type)
   throws ErrorConditionException
   {
      checkOverlayType(type);
      replaceWorker(new character(text), new integer(pos), len, true);
      return this;
   }

   /**
    * Overwrites characters in the current value of this instance with the 
    * passed text at the 1-based index into the string.  The entire
    * replacement text will be placed into the resulting value.
    *
    * @param    text
    *           Replacement text.
    * @param    pos
    *           The 1-based index into the string at which the replacement
    *           is to occur.
    *
    * @return   The current instance is returned.
    * @throws ErrorConditionException 
    */
   public Text overlay(String text, NumberType pos)
   throws ErrorConditionException
   {
      replaceWorker(new character(text), pos, new integer(-1), true);
      return this;
   }

   /**
    * Overwrites characters in the current value of this instance with the 
    * passed text at the 1-based index into the string.  The entire
    * replacement text will be placed into the resulting value.
    *
    * @param    text
    *           Replacement text.
    * @param    pos
    *           The 1-based index into the string at which the replacement
    *           is to occur.
    * @param    len
    *           The number of characters in the source string to replace
    *           and the number of characters from the replacement text to 
    *           include in the replacement.
    *
    * @return   The current instance is returned.
    * @throws ErrorConditionException 
    */
   public Text overlay(String text, NumberType pos, double len)
   throws ErrorConditionException
   {
      replaceWorker(new character(text), pos, new integer(len), true);
      return this;
   }

   /**
    * Overwrites characters in the current value of this instance with the 
    * passed text at the 1-based index into the string.  The entire
    * replacement text will be placed into the resulting value.
    *
    * @param    text
    *           Replacement text.
    * @param    pos
    *           The 1-based index into the string at which the replacement
    *           is to occur.
    * @param    len
    *           The number of characters in the source string to replace
    *           and the number of characters from the replacement text to 
    *           include in the replacement.
    * @param    type
    *           Description of how to interpret the position and length values.
    *           At this time only "character" is supported.
    *
    * @return   The current instance is returned.
    * @throws ErrorConditionException 
    */
   public Text overlay(String text, NumberType pos, double len, String type)
   throws ErrorConditionException
   {
      checkOverlayType(type);
      replaceWorker(new character(text), pos, new integer(len), true);
      return this;
   }

   /**
    * Overwrites characters in the current value of this instance with the 
    * passed text at the 1-based index into the string.  The entire
    * replacement text will be placed into the resulting value.
    *
    * @param    text
    *           Replacement text.
    * @param    pos
    *           The 1-based index into the string at which the replacement
    *           is to occur.
    * @param    len
    *           The number of characters in the source string to replace
    *           and the number of characters from the replacement text to 
    *           include in the replacement.
    *
    * @return   The current instance is returned.
    * @throws ErrorConditionException 
    */
   public Text overlay(String text, NumberType pos, NumberType len)
   throws ErrorConditionException
   {
      replaceWorker(new character(text), pos, len, true);
      return this;
   }

   /**
    * Overwrites characters in the current value of this instance with the 
    * passed text at the 1-based index into the string.  The entire
    * replacement text will be placed into the resulting value.
    *
    * @param    text
    *           Replacement text.
    * @param    pos
    *           The 1-based index into the string at which the replacement
    *           is to occur.
    * @param    len
    *           The number of characters in the source string to replace
    *           and the number of characters from the replacement text to 
    *           include in the replacement.
    * @param    type
    *           Description of how to interpret the position and length values.
    *           At this time only "character" is supported.
    *
    * @return   The current instance is returned.
    * @throws ErrorConditionException 
    */
   public Text overlay(String     text,
                       NumberType pos,
                       NumberType len,
                       String     type)
   throws ErrorConditionException
   {
      checkOverlayType(type);
      replaceWorker(new character(text), pos, len, true);
      return this;
   }
   
   /**
    * Overwrites characters in the current value of this instance with the 
    * passed text at the 1-based index into the string.  The entire
    * replacement text will be placed into the resulting value.
    *
    * @param    text
    *           Replacement text.
    * @param    pos
    *           The 1-based index into the string at which the replacement
    *           is to occur.
    *
    * @return   The current instance is returned.
    * @throws ErrorConditionException 
    */
   public Text replace(Text text, double pos)
   throws ErrorConditionException
   {
      replaceWorker(text, new integer(pos), new integer(-1), false);
      return this;
   }

   /**
    * Overwrites characters in the current value of this instance with the 
    * passed text at the 1-based index into the string.  The entire
    * replacement text will be placed into the resulting value.
    *
    * @param    text
    *           Replacement text.
    * @param    pos
    *           The 1-based index into the string at which the replacement
    *           is to occur.
    * @param    len
    *           The number of characters in the source string to replace.
    *
    * @return   The current instance is returned.
    * @throws ErrorConditionException 
    */
   public Text replace(Text text, double pos, double len)
   throws ErrorConditionException
   {
      replaceWorker(text, new integer(pos), new integer(len), false);
      return this;
   }

   /**
    * Overwrites characters in the current value of this instance with the 
    * passed text at the 1-based index into the string.  The entire
    * replacement text will be placed into the resulting value.
    *
    * @param    text
    *           Replacement text.
    * @param    pos
    *           The 1-based index into the string at which the replacement
    *           is to occur.
    * @param    len
    *           The number of characters in the source string to replace.
    *
    * @return   The current instance is returned.
    * @throws ErrorConditionException 
    */
   public Text replace(Text text, double pos, NumberType len)
   throws ErrorConditionException
   {
      replaceWorker(text, new integer(pos), len, false);
      return this;
   }

   /**
    * Overwrites characters in the current value of this instance with the 
    * passed text at the 1-based index into the string.  The entire
    * replacement text will be placed into the resulting value.
    *
    * @param    text
    *           Replacement text.
    * @param    pos
    *           The 1-based index into the string at which the replacement
    *           is to occur.
    *
    * @return   The current instance is returned.
    * @throws ErrorConditionException 
    */
   public Text replace(Text text, NumberType pos)
   throws ErrorConditionException
   {
      replaceWorker(text, pos, new integer(-1), false);
      return this;
   }

   /**
    * Overwrites characters in the current value of this instance with the 
    * passed text at the 1-based index into the string.  The entire
    * replacement text will be placed into the resulting value.
    *
    * @param    text
    *           Replacement text.
    * @param    pos
    *           The 1-based index into the string at which the replacement
    *           is to occur.
    * @param    len
    *           The number of characters in the source string to replace.
    *
    * @return   The current instance is returned.
    * @throws ErrorConditionException 
    */
   public Text replace(Text text, NumberType pos, double len)
   throws ErrorConditionException
   {
      replaceWorker(text, pos, new integer(len), false);
      return this;
   }

   /**
    * Overwrites characters in the current value of this instance with the 
    * passed text at the 1-based index into the string.  The entire
    * replacement text will be placed into the resulting value.
    *
    * @param    text
    *           Replacement text.
    * @param    pos
    *           The 1-based index into the string at which the replacement
    *           is to occur.
    * @param    len
    *           The number of characters in the source string to replace.
    *
    * @return   The current instance is returned.
    * @throws ErrorConditionException 
    */
   public Text replace(Text text, NumberType pos, NumberType len)
   throws ErrorConditionException
   {
      replaceWorker(text, pos, len, false);
      return this;
   }

   /**
    * Overwrites characters in the current value of this instance with the 
    * passed text at the 1-based index into the string.  The entire
    * replacement text will be placed into the resulting value.
    *
    * @param    text
    *           Replacement text.
    * @param    pos
    *           The 1-based index into the string at which the replacement
    *           is to occur.
    *
    * @return   The current instance is returned.
    * @throws ErrorConditionException 
    */
   public Text replace(String text, double pos)
   throws ErrorConditionException
   {
      replaceWorker(new character(text),
                    new integer(pos),
                    new integer(-1),
                    false);
      return this;
   }

   /**
    * Overwrites characters in the current value of this instance with the 
    * passed text at the 1-based index into the string.  The entire
    * replacement text will be placed into the resulting value.
    *
    * @param    text
    *           Replacement text.
    * @param    pos
    *           The 1-based index into the string at which the replacement
    *           is to occur.
    * @param    len
    *           The number of characters in the source string to replace.
    *
    * @return   The current instance is returned.
    * @throws ErrorConditionException 
    */
   public Text replace(String text, double pos, double len)
   throws ErrorConditionException
   {
      replaceWorker(new character(text),
                    new integer(pos),
                    new integer(len),
                    false);
      return this;
   }

   /**
    * Overwrites characters in the current value of this instance with the 
    * passed text at the 1-based index into the string.  The entire
    * replacement text will be placed into the resulting value.
    *
    * @param    text
    *           Replacement text.
    * @param    pos
    *           The 1-based index into the string at which the replacement
    *           is to occur.
    * @param    len
    *           The number of characters in the source string to replace.
    *
    * @return   The current instance is returned.
    * @throws ErrorConditionException 
    */
   public Text replace(String text, double pos, NumberType len)
   throws ErrorConditionException
   {
      replaceWorker(new character(text), new integer(pos), len, false);
      return this;
   }

   /**
    * Overwrites characters in the current value of this instance with the 
    * passed text at the 1-based index into the string.  The entire
    * replacement text will be placed into the resulting value.
    *
    * @param    text
    *           Replacement text.
    * @param    pos
    *           The 1-based index into the string at which the replacement
    *           is to occur.
    *
    * @return   The current instance is returned.
    * @throws ErrorConditionException 
    */
   public Text replace(String text, NumberType pos)
   throws ErrorConditionException
   {
      replaceWorker(new character(text), pos, new integer(-1), false);
      return this;
   }

   /**
    * Overwrites characters in the current value of this instance with the 
    * passed text at the 1-based index into the string.  The entire
    * replacement text will be placed into the resulting value.
    *
    * @param    text
    *           Replacement text.
    * @param    pos
    *           The 1-based index into the string at which the replacement
    *           is to occur.
    * @param    len
    *           The number of characters in the source string to replace.
    *
    * @return   The current instance is returned.
    * @throws ErrorConditionException 
    */
   public Text replace(String text, NumberType pos, double len)
   throws ErrorConditionException
   {
      replaceWorker(new character(text), pos, new integer(len), false);
      return this;
   }

   /**
    * Overwrites characters in the current value of this instance with the 
    * passed text at the 1-based index into the string.  The entire
    * replacement text will be placed into the resulting value.
    *
    * @param    text
    *           Replacement text.
    * @param    pos
    *           The 1-based index into the string at which the replacement
    *           is to occur.
    * @param    len
    *           The number of characters in the source string to replace.
    *
    * @return   The current instance is returned.
    * @throws ErrorConditionException 
    */
   public Text replace(String text, NumberType pos, NumberType len)
   throws ErrorConditionException
   {
      replaceWorker(new character(text), pos, len, false);
      return this;
   }
   
   /**
    * Overwrites characters in the current value of this instance with the passed text at the 
    * 1-based index into the string. The entire replacement text will be placed into the
    * resulting value.
    *
    * @param    text
    *           Replacement text.
    * @param    pos
    *           The 1-based index into the string at which the replacement is to occur.
    * @param    type
    *           Specifies whether this substring operates on "CHARACTER" (default), "RAW" or 
    *           "COLUMN".  At this time "character" is supported.
    *
    * @return   The current instance is returned.
    * @throws ErrorConditionException 
    */
   public Text replace(Text text, double pos, String type)
   throws ErrorConditionException
   {
      replaceWorker(text, new integer(pos), new integer(-1), false);
      return this;
   }
   
   /**
    * Overwrites characters in the current value of this instance with the passed text at the
    * 1-based index into the string.  The entire replacement text will be placed into the
    * resulting value.
    *
    * @param    text
    *           Replacement text.
    * @param    pos
    *           The 1-based index into the string at which the replacement is to occur.
    * @param    len
    *           The number of characters in the source string to replace.
    * @param    type
    *           Specifies whether this substring operates on "CHARACTER" (default), "RAW" or 
    *           "COLUMN".  At this time "character" is supported.
    *
    * @return   The current instance is returned.
    * @throws ErrorConditionException 
    */
   public Text replace(Text text, double pos, double len, String type)
   throws ErrorConditionException
   {
      replaceWorker(text, new integer(pos), new integer(len), false);
      return this;
   }
   
   /**
    * Overwrites characters in the current value of this instance with the passed text at the
    * 1-based index into the string.  The entire replacement text will be placed into the
    * resulting value.
    *
    * @param    text
    *           Replacement text.
    * @param    pos
    *           The 1-based index into the string at which the replacement is to occur.
    * @param    len
    *           The number of characters in the source string to replace.
    * @param    type
    *           Specifies whether this substring operates on "CHARACTER" (default), "RAW" or 
    *           "COLUMN".  At this time "character" is supported.
    *
    * @return   The current instance is returned.
    * @throws ErrorConditionException 
    */
   public Text replace(Text text, double pos, NumberType len, String type)
   throws ErrorConditionException
   {
      replaceWorker(text, new integer(pos), len, false);
      return this;
   }
   
   /**
    * Overwrites characters in the current value of this instance with the 
    * passed text at the 1-based index into the string.  The entire
    * replacement text will be placed into the resulting value.
    *
    * @param    text
    *           Replacement text.
    * @param    pos
    *           The 1-based index into the string at which the replacement is to occur.
    * @param    type
    *           Specifies whether this substring operates on "CHARACTER" (default), "RAW" or 
    *           "COLUMN".  At this time "character" is supported.
    *
    * @return   The current instance is returned.
    * @throws ErrorConditionException 
    */
   public Text replace(Text text, NumberType pos, String type)
   throws ErrorConditionException
   {
      replaceWorker(text, pos, new integer(-1), false);
      return this;
   }
   
   /**
    * Overwrites characters in the current value of this instance with the 
    * passed text at the 1-based index into the string.  The entire
    * replacement text will be placed into the resulting value.
    *
    * @param    text
    *           Replacement text.
    * @param    pos
    *           The 1-based index into the string at which the replacement is to occur.
    * @param    len
    *           The number of characters in the source string to replace.
    * @param    type
    *           Specifies whether this substring operates on "CHARACTER" (default), "RAW" or 
    *           "COLUMN".  At this time "character" is supported.
    *
    * @return   The current instance is returned.
    * @throws ErrorConditionException 
    */
   public Text replace(Text text, NumberType pos, double len, String type)
   throws ErrorConditionException
   {
      replaceWorker(text, pos, new integer(len), false);
      return this;
   }
   
   /**
    * Overwrites characters in the current value of this instance with the 
    * passed text at the 1-based index into the string.  The entire
    * replacement text will be placed into the resulting value.
    *
    * @param    text
    *           Replacement text.
    * @param    pos
    *           The 1-based index into the string at which the replacement is to occur.
    * @param    len
    *           The number of characters in the source string to replace.
    * @param    type
    *           Specifies whether this substring operates on "CHARACTER" (default), "RAW" or 
    *           "COLUMN".  At this time "character" is supported.
    *
    * @return   The current instance is returned.
    * @throws ErrorConditionException 
    */
   public Text replace(Text text, NumberType pos, NumberType len, String type)
   throws ErrorConditionException
   {
      replaceWorker(text, pos, len, false);
      return this;
   }
   
   /**
    * Overwrites characters in the current value of this instance with the 
    * passed text at the 1-based index into the string.  The entire
    * replacement text will be placed into the resulting value.
    *
    * @param    text
    *           Replacement text.
    * @param    pos
    *           The 1-based index into the string at which the replacement is to occur.
    * @param    type
    *           Specifies whether this substring operates on "CHARACTER" (default), "RAW" or 
    *           "COLUMN".  At this time "character" is supported.
    *
    * @return   The current instance is returned.
    * @throws ErrorConditionException 
    */
   public Text replace(String text, double pos, String type)
   throws ErrorConditionException
   {
      replaceWorker(new character(text),
                    new integer(pos),
                    new integer(-1),
                    false);
      return this;
   }
   
   /**
    * Overwrites characters in the current value of this instance with the 
    * passed text at the 1-based index into the string.  The entire
    * replacement text will be placed into the resulting value.
    *
    * @param    text
    *           Replacement text.
    * @param    pos
    *           The 1-based index into the string at which the replacement is to occur.
    * @param    len
    *           The number of characters in the source string to replace.
    * @param    type
    *           Specifies whether this substring operates on "CHARACTER" (default), "RAW" or 
    *           "COLUMN".  At this time "character" is supported.
    *
    * @return   The current instance is returned.
    * @throws ErrorConditionException 
    */
   public Text replace(String text, double pos, double len, String type)
   throws ErrorConditionException
   {
      replaceWorker(new character(text),
                    new integer(pos),
                    new integer(len),
                    false);
      return this;
   }
   
   /**
    * Overwrites characters in the current value of this instance with the 
    * passed text at the 1-based index into the string.  The entire
    * replacement text will be placed into the resulting value.
    *
    * @param    text
    *           Replacement text.
    * @param    pos
    *           The 1-based index into the string at which the replacement is to occur.
    * @param    len
    *           The number of characters in the source string to replace.
    * @param    type
    *           Specifies whether this substring operates on "CHARACTER" (default), "RAW" or 
    *           "COLUMN".  At this time "character" is supported.
    *
    * @return   The current instance is returned.
    * @throws ErrorConditionException 
    */
   public Text replace(String text, double pos, NumberType len, String type)
   throws ErrorConditionException
   {
      replaceWorker(new character(text), new integer(pos), len, false);
      return this;
   }
   
   /**
    * Overwrites characters in the current value of this instance with the 
    * passed text at the 1-based index into the string.  The entire
    * replacement text will be placed into the resulting value.
    *
    * @param    text
    *           Replacement text.
    * @param    pos
    *           The 1-based index into the string at which the replacement is to occur.
    * @param    type
    *           Specifies whether this substring operates on "CHARACTER" (default), "RAW" or 
    *           "COLUMN".  At this time "character" is supported.
    *
    * @return   The current instance is returned.
    * @throws ErrorConditionException 
    */
   public Text replace(String text, NumberType pos, String type)
   throws ErrorConditionException
   {
      replaceWorker(new character(text), pos, new integer(-1), false);
      return this;
   }
   
   /**
    * Overwrites characters in the current value of this instance with the 
    * passed text at the 1-based index into the string.  The entire
    * replacement text will be placed into the resulting value.
    *
    * @param    text
    *           Replacement text.
    * @param    pos
    *           The 1-based index into the string at which the replacement is to occur.
    * @param    len
    *           The number of characters in the source string to replace.
    * @param    type
    *           Specifies whether this substring operates on "CHARACTER" (default), "RAW" or 
    *           "COLUMN".  At this time "character" is supported.
    *
    * @return   The current instance is returned.
    * @throws ErrorConditionException 
    */
   public Text replace(String text, NumberType pos, double len, String type)
   throws ErrorConditionException
   {
      replaceWorker(new character(text), pos, new integer(len), false);
      return this;
   }
   
   /**
    * Overwrites characters in the current value of this instance with the 
    * passed text at the 1-based index into the string.  The entire
    * replacement text will be placed into the resulting value.
    *
    * @param    text
    *           Replacement text.
    * @param    pos
    *           The 1-based index into the string at which the replacement is to occur.
    * @param    len
    *           The number of characters in the source string to replace.
    * @param    type
    *           Specifies whether this substring operates on "CHARACTER" (default), "RAW" or 
    *           "COLUMN".  At this time "character" is supported.
    *
    * @return   The current instance is returned.
    * @throws ErrorConditionException 
    */
   public Text replace(String text, NumberType pos, NumberType len, String type)
   throws ErrorConditionException
   {
      replaceWorker(new character(text), pos, len, false);
      return this;
   }
   
   /**
    * Replaces the specified 1-based indexed entry in the current instance
    * which must be a delimited list.  
    *
    * @param    ii
    *           The 1-based index into the list.
    * @param    delimit
    *           The delimiter character.
    * @param    replace
    *           The replacment text.
    *
    * @return   The current instance.
    * @throws ErrorConditionException 
    */
   public Text replaceEntry(double ii, char delimit, String replace)
   throws ErrorConditionException
   {
      if (isUnknown())
      {
         return this;
      }
      
      // 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, will end with the delimiter)
      // - the element at the given position
      // - suffix (everything after the found element, will not start with the delimiter)
      String[] array = TextOps.splitInt(value, delimit, idx, false);
      
      if (array == null)
      {
         TextOps.genOutsideRangeError(i, value);
         return this;
      }
      
      StringBuilder sb = new StringBuilder();
      
      if (array[0] != null)
      {
         // this already has the delimiter
         sb.append(array[0]);
      }
      sb.append(replace);
      if (array[2] != null)
      {
         sb.append(delimit).append(array[2]);
      }
      
      String newVal = sb.toString();
      
      checkUndoable(() -> new character(newVal));
      
      // save the result; does not need null character protection since the data is already
      // coming from an existing text instance of the same type
      value = newVal;
      
      return this;
   }

   /**
    * Replaces the specified 1-based indexed comma-delimited entry in the
    * current instance which must be a delimited list.  
    *
    * @param    i
    *           The 1-based index into the list.
    * @param    replace
    *           The replacment text.
    *
    * @return   The current instance.  It may have been set to the 
    *           <code>unknown value</code> if any of the inputs are
    *           <code>unknown value</code>.
    * @throws ErrorConditionException 
    */
   public Text replaceEntry(double i, Text replace)
   throws ErrorConditionException
   {
      if (replace.unknown)
      {
         setUnknown();
         return this;
      }
      
      return replaceEntry((int) i, ',', replace.value);
   }

   /**
    * Replaces the specified 1-based indexed entry in the current instance
    * which must be a delimited list.  
    *
    * @param    i
    *           The 1-based index into the list.
    * @param    delimit
    *           The delimiter character.
    * @param    replace
    *           The replacment text.
    *
    * @return   The current instance.  It may have been set to the 
    *           <code>unknown value</code> if any of the inputs are
    *           <code>unknown value</code>.
    * @throws ErrorConditionException 
    */
   public Text replaceEntry(double i, character delimit, Text replace)
   throws ErrorConditionException
   {
      if (replace.unknown || delimit.unknown)
      {
         setUnknown();
         return this;
      }
      
      return replaceEntry((int) i,
                          TextOps.safeDelimiter(delimit.value),
                          replace.value);
   }

   /**
    * Replaces the specified 1-based indexed entry in the current instance
    * which must be a delimited list.  
    *
    * @param    i
    *           The 1-based index into the list.
    * @param    delimit
    *           The delimiter character.
    * @param    replace
    *           The replacment text.
    *
    * @return   The current instance.  It may have been set to the 
    *           <code>unknown value</code> if any of the inputs are
    *           <code>unknown value</code>.
    * @throws ErrorConditionException 
    */
   public Text replaceEntry(double i, character delimit, String replace)
   throws ErrorConditionException
   {
      if (delimit.unknown)
      {
         setUnknown();
         return this;
      }
      
      return replaceEntry((int) i, 
                          TextOps.safeDelimiter(delimit.value),
                          replace);
   }

   /**
    * Replaces the specified 1-based indexed comma-delimited entry in the
    * current instance which must be a delimited list.  
    *
    * @param    i
    *           The 1-based index into the list.
    * @param    replace
    *           The replacment text.
    *
    * @return   The current instance.
    * @throws ErrorConditionException 
    */
   public Text replaceEntry(double i, String replace)
   throws ErrorConditionException
   {
      return replaceEntry((int) i, ',', replace);
   }

   /**
    * Replaces the specified 1-based indexed entry in the current instance
    * which must be a delimited list.  
    *
    * @param    i
    *           The 1-based index into the list.
    * @param    delimit
    *           The delimiter character.
    * @param    replace
    *           The replacment text.
    *
    * @return   The current instance.  It may have been set to the 
    *           <code>unknown value</code> if any of the inputs are
    *           <code>unknown value</code>.
    * @throws ErrorConditionException 
    */
   public Text replaceEntry(double i, String delimit, Text replace)
   throws ErrorConditionException
   {
      if (replace.unknown)
      {
         setUnknown();
         return this;
      }
      
      return replaceEntry((int) i,
                          TextOps.safeDelimiter(delimit),
                          replace.value);
   }

   /**
    * Replaces the specified 1-based indexed entry in the current instance
    * which must be a delimited list.  
    *
    * @param    i
    *           The 1-based index into the list.
    * @param    delimit
    *           The delimiter character.
    * @param    replace
    *           The replacment text.
    *
    * @return   The current instance.
    * @throws ErrorConditionException 
    */
   public Text replaceEntry(double i, String delimit, String replace)
   throws ErrorConditionException
   {
      return replaceEntry((int) i, TextOps.safeDelimiter(delimit), replace);
   }

   /**
    * Replaces the specified 1-based indexed comma-delimited entry in the
    * current instance which must be a delimited list.  
    *
    * @param    i
    *           The 1-based index into the list.
    * @param    replace
    *           The replacment text.
    *
    * @return   The current instance.  It may have been set to the 
    *           <code>unknown value</code> if any of the inputs are
    *           <code>unknown value</code>.
    * @throws ErrorConditionException 
    */
   public Text replaceEntry(NumberType i, character replace)
   throws ErrorConditionException
   {
      if (i.isUnknown() || replace.unknown)
      {
         setUnknown();
         return this;
      }
      
      return replaceEntry(i.intValue(), ',', replace.value);
   }

   /**
    * Replaces the specified 1-based indexed entry in the current instance
    * which must be a delimited list.  
    *
    * @param    i
    *           The 1-based index into the list.
    * @param    delimit
    *           The delimiter character.
    * @param    replace
    *           The replacment text.
    *
    * @return   The current instance.  It may have been set to the 
    *           <code>unknown value</code> if any of the inputs are
    *           <code>unknown value</code>.
    * @throws ErrorConditionException 
    */
   public Text replaceEntry(NumberType i, character delimit, Text replace)
   throws ErrorConditionException
   {
      if (i.isUnknown() || replace.unknown || delimit.unknown)
      {
         setUnknown();
         return this;
      }
      
      return replaceEntry(i.intValue(),
                          TextOps.safeDelimiter(delimit.value),
                          replace.value);
   }

   /**
    * Replaces the specified 1-based indexed entry in the current instance
    * which must be a delimited list.  
    *
    * @param    i
    *           The 1-based index into the list.
    * @param    delimit
    *           The delimiter character.
    * @param    replace
    *           The replacment text.
    *
    * @return   The current instance.  It may have been set to the 
    *           <code>unknown value</code> if any of the inputs are
    *           <code>unknown value</code>.
    * @throws ErrorConditionException 
    */
   public Text replaceEntry(NumberType i, character delimit, String replace)
   throws ErrorConditionException
   {
      if (i.isUnknown() || delimit.unknown)
      {
         setUnknown();
         return this;
      }
      
      return replaceEntry(i.intValue(),
                          TextOps.safeDelimiter(delimit.value),
                          replace);
   }

   /**
    * Replaces the specified 1-based indexed comma-delimited entry in the
    * current instance which must be a delimited list.  
    *
    * @param    i
    *           The 1-based index into the list.
    * @param    replace
    *           The replacment text.
    *
    * @return   The current instance.  It may have been set to the 
    *           <code>unknown value</code> if any of the inputs are
    *           <code>unknown value</code>.
    * @throws ErrorConditionException 
    */
   public Text replaceEntry(NumberType i, String replace)
   throws ErrorConditionException
   {
      if (i.isUnknown())
      {
         setUnknown();
         return this;
      }
      
      return replaceEntry(i.intValue(), ',', replace);
   }

   /**
    * Replaces the specified 1-based indexed entry in the current instance
    * which must be a delimited list.  
    *
    * @param    i
    *           The 1-based index into the list.
    * @param    delimit
    *           The delimiter character.
    * @param    replace
    *           The replacment text.
    *
    * @return   The current instance.  It may have been set to the 
    *           <code>unknown value</code> if any of the inputs are
    *           <code>unknown value</code>.
    * @throws ErrorConditionException 
    */
   public Text replaceEntry(NumberType i, String delimit, Text replace)
   throws ErrorConditionException
   {
      if (i.isUnknown() || replace.unknown)
      {
         setUnknown();
         return this;
      }
      
      return replaceEntry(i.intValue(),
                          TextOps.safeDelimiter(delimit),
                          replace.value);
   }

   /**
    * Replaces the specified 1-based indexed entry in the current instance
    * which must be a delimited list.  
    *
    * @param    i
    *           The 1-based index into the list.
    * @param    delimit
    *           The delimiter character.
    * @param    replace
    *           The replacment text.
    *
    * @return   The current instance.  It may have been set to the 
    *           <code>unknown value</code> if any of the inputs are
    *           <code>unknown value</code>.
    * @throws ErrorConditionException 
    */
   public Text replaceEntry(NumberType i, String delimit, String replace)
   throws ErrorConditionException
   {
      if (i.isUnknown())
      {
         setUnknown();
         return this;
      }
      
      return replaceEntry(i.intValue(), 
                          TextOps.safeDelimiter(delimit),
                          replace);
   }

   /**
    * Creates a new instance of the same type that represents the 
    * <code>unknown value</code>.
    *
    * @return   An instance that represents the <code>unknown value</code>.
    */
   public BaseDataType instantiateUnknown()   
   {
      return new character();
   }

   /**
    * Sets the state (data and unknown value) of this instance based on the
    * state of the passed instance. The configuration (case-sensitivity) of
    * this instance is not set.  Use {@link #deepAssign} if both state and
    * configuration must be set.
    *
    * TODO this function can be removed after runtime conversion of dynamic
    * tables containing a clob field is fixed
    *
    * @param    value
    *           The instance from which to copy state. If <code>null</code>
    *           is passed, the instance will be set to the unknown value.
    */
   public void assign(clob value)
   {
      assign((BaseDataType) value);
   }
   
   @Override
   protected boolean isIncompatibleTypesOnConversion(BaseDataType value)
   {
      // invalid conversion is being thrown even if unknown
      if (value instanceof memptr || value instanceof blob || (value instanceof raw && !value.isUnknown())) 
      {
         incompatibleTypesOnConversion();
      }
      
      return super.isIncompatibleTypesOnConversion(value);
   }

   /**
    * Sets the state (data and unknown value) of this instance based on the state of the passed
    * instance.
    * <p>
    * If the value is not of type <code>Text</code>, the following automatic type conversions
    * will occur:
    * <p>
    * <pre>
    * unknown         - sets the instance to unknown value
    * Text            - assigns normally
    * all other cases - the result of toStringMessage() is assigned
    * </pre>
    * <p>
    * This variant is meant to handle the cases of built-in functions and methods in the 4GL
    * which have polymorphic return types (e.g. DYNAMIC-FUNCTION()). This should NOT be used for
    * non-polymorphic assignments.
    * <p>
    * It is possible that string data could have embedded null characters , but in the 4GL
    * the resulting variable instances are "spacified".  The given value will be processed by
    * {@link #javaSpacifyNull} before storing it in the new instance.
    *
    * @param    value
    *           The instance from which to copy state.
    */
   public void assign(BaseDataType value)
   {
      if (value == null)
      {
         setUnknown();
         return;
      }

      // the value may be a proxy; make sure to unwrap and work with the real value
      value = value.val();
      if (value.isUnknown())
      {
         setUnknown();
      }
      else if (value instanceof Text)
      {
         assign((Text) value);
      }
      else if (value instanceof object)
      {
         assign((object<?>) value);
      }
      else if (value instanceof jobject)
      {
         assign((jobject<?>) value);
      }
      else if (value instanceof rowid)
      {
         incompatibleTypesOnConversion();
      }
      else if (value instanceof BinaryData)
      {
         byte[] bytes = ((BinaryData) value).getByteArray();
         String strVal = new String(bytes, Charset.forName(I18nOps.getJavaCPInternal()));
         assign(javaSpacifyNull(strVal));
         javaSpacifyNull = false;
      }
      else
      {
         String newVal = javaSpacifyNull(value.toStringMessage());
         checkUndoable(() -> new character(newVal));

         // protected against embedded null characters 
         this.value = newVal;
         unknown    = false;
         javaSpacifyNull = false;
      }
   }

   /**
    * Convert the specified object instance to number using toString, 
    * and assign it to this instance.
    * 
    * @param    value
    *           The instance holding a object type.
    */
   public void assign(object<?> value)
   {
      // the value may be a proxy; make sure to unwrap and work with the real value
      if (BaseDataType.isProxy(value))
      {
         assign(value.val());
         return;
      }
      if (value.isUnknown())
      {
         setUnknown();
      }
      else
      {
         assign(value.ref().toLegacyString());
      }
   }
   
   /**
    * Sets the state (data and unknown value) of this instance based on the state of the passed
    * instance.
    * <p>
    * This form of assignment allows embedded null characters to be assigned from one already
    * existing instance to another if the child class allows it.
    *
    * @param    value
    *           The instance from which to copy state.
    */
   public void assign(Text value)
   {
      // the value may be a proxy; make sure to unwrap and work with the real value
      if (BaseDataType.isProxy(value))
      {
         assign(value.val());
         return;
      }
      String val = value.value;
      
      // allow values with embedded null characters to be assigned only if the subclass allows
      if (val != null && !isEmbeddedNullAllowed() && val.indexOf('\0') >= 0)
      {
         ErrorManager.recordOrThrowError(142, "Unable to update  Field");
         return;
      }
      
      checkUndoable(value);

      this.value = val;
      unknown    = value.unknown;
      javaSpacifyNull = value.javaSpacifyNull;
   }

   /**
    * Sets the state (data and unknown value) of this instance based on the
    * state of the passed instance. The configuration (case-sensitivity) of
    * this instance is not set.  Use {@link #deepAssign} if both state and
    * configuration must be set.
    *
    * @param    value
    *           The instance from which to copy state. If <code>null</code>
    *           is passed, the instance will be set to the unknown value.
    */
   public void assign(String value)
   {
      assign(new character(value));
   }

   /**
    * Sets the state (data and unknown value) of this instance based on the state of the passed
    * instance.
    *
    * @param    value
    *           The instance from which to copy state.
    */
   public void assign(Undoable value)
   {
      if (value instanceof Text)
      {
         assign((Text) value);
      }
      else if (value instanceof BaseDataType)
      {
         assign(character.valueOf((BaseDataType) value));
      }
      else
      {
         // should never reach this point, anyway
         assign(value.toString());
      }
   }

   /**
    * Sets the state (data and unknown value) and configuration 
    * (case-sensitivity) of this instance based on the state and configuration
    * of the passed instance.
    *
    * @param    value
    *           The instance from which to copy state and configuration.
    */
   public void deepAssign(Text value)
   {
      assign(value);
      caseSens = value.caseSens;
   }

   /**
    * Accesses the state of this instance's <code>String</code> value
    * independently of the state of whether this instance represents the
    * <code>unknown value</code>.  If this instance represents the
    * <code>unknown value</code>, the returned <code>String</code> is
    * <b>not valid</b>! 
    *
    * @return   The state of this instance's <code>String</code> value.
    */
   public String getValue()
   {
      return value;
   }

   /**
    * Sets the state of this instance's <code>String</code> value and sets the
    * <code>unknown</code> state for this instance to <code>false</code>.
    * <p>
    * Strings data can have embedded null characters, but in the 4GL the resulting variable
    * instances are "spacified".  The given value will be processed by {@link #javaSpacifyNull}
    * before storing it in the new instance.
    *
    * @param    value
    *           The state to set this instance's <code>String</code> value.
    */
   public void setValue(String value)
   {
      String val = javaSpacifyNull(value);
      
      checkUndoable(() -> new character(val)); 
      
      // protected against embedded null characters 
      this.value = val;
      unknown    = false;
      javaSpacifyNull = false;
   }

   /**
    * Determines if this instance compares case sensitively.
    *
    * @return   <code>true</code> if this instance is set to case sensitive.
    */
   public boolean isCaseSensitive()
   {
      return caseSens;
   }

   /**
    * Sets the state of this instance's compares case sensitive flag.
    * <p>
    * This method is used to set the current value in a manner that does not
    * schedule an automated reset to the prior value later. Thus, this method
    * is suitable for the initial configuration of a new instance.  If a
    * temporary case-sensitivity change is needed, use 
    * {@link #setTemporaryCaseSensitive}.
    *
    * @param    caseSens
    *           <code>true</code> if this instance must be set to case
    *           sensitive.
    */
   public void setCaseSensitive(boolean caseSens)
   {
      if (caseSens != this.caseSens)
      {
         checkUndoable(true);
      }

      this.caseSens = caseSens;
   }

   /**
    * Sets the state of this instance's compares case sensitive flag for the
    * duration of the caller's current scope.  At the end of the caller's
    * scope a notification will trigger a reset of the case sensitive flag
    * to the current value.
    * <p>
    * This method should only be used when importing references via the
    * {@link SharedVariableManager} as a local instance or a new shared
    * instance would not need to be reset, since they would have no prior
    * value to which to return.  To set the initial case-sensitivity, please
    * se {@link #setCaseSensitive}.
    * <p>
    * The {@link TransactionManager#registerFinalizable} facility is used to
    * obtain the callback necessary to reset the sensitivity at end of the
    * caller's scope.
    *
    * @param    caseSens
    *           <code>true</code> if this instance must be set to case
    *           sensitive.
    */
   public void setTemporaryCaseSensitive(boolean caseSens)
   {
      // do nothing if the request precision is the same as the current
      // precision
      if (this.caseSens != caseSens)
      {
         // remember the old value
         boolean old = this.caseSens;
         
         // reset
         setCaseSensitive(caseSens);
         
         // schedule the reset
         SensitivityResetter sr = new SensitivityResetter(old);
         TransactionManager.registerFinalizable(sr, false);
      }
   }

   /**
    * Test equality between the internal values of this object and the given parameter on a case
    * sensitive basis. Before the internal values are checked, the unknown states of both operands
    * are compared. If both are unknown, {@code true} is returned. If only one is unknown,
    * {@code false} is returned. Otherwise, the case sensitive test is performed.
    *
    * @param   c
    *          Another character instance to test for case sensitive equality with this object.
    *
    * @return  {@code true} if this object and the given object test as equal, else {@code false}.
    */
   public boolean equalsCaseSensitively(Text c)
   {
      boolean u1 = isUnknown();
      boolean u2 = c.isUnknown();
      
      if (u1 && u2)
      {
         return true;
      }
      
      if (u1 || u2)
      {
         return false;
      }
      
      // quick out if the values are the same object instance
      if (value == c.value)
      {
         return true;
      }
      
      // prepare the data
      String s1 = StringHelper.safeTrimTrailingSpaces(value);
      String s2 = StringHelper.safeTrimTrailingSpaces(c.value);
      
      return s1.equals(s2);
   }

   /**
    * Hash code implementation which is consistent with {@link #equalsCaseSensitively}.
    *
    * @return  Hash code value for this object instance.
    */
   public int caseSensitiveHashCode()
   {
      int result = 17;
      if (isUnknown())
      {
         return result;
      }
      
      result = 37 * result + value.hashCode();
      
      return result;
   }

   /**
    * Determines if this instance represents the <code>unknown value</code>.
    *
    * @return   <code>true</code> if this instance is set to the
    *           <code>unknown value</code>.
    */
   public boolean isUnknown()
   {
      return unknown;
   }

   /**
    * Sets the state of this instance's <code>unknown value</code> flag to
    * <code>true</code>.
    * <p>
    * <b>Warning: a separate call is needed to ensure that the data of this
    * instance is set to the correct value.</b>
    */
   public void setUnknown()
   {
      if (!unknown)
      {
         checkUndoable(true);
      }

      unknown = true;
      value = null;
      javaSpacifyNull = true;
   }

   /**
    * Replacement for the default object reading method. The latest
    * state is read from the input source.
    * 
    * @param    in
    *           The input source from which fields will be restored.
    *
    * @throws   IOException
    *           In case of I/O errors.
    * @throws   ClassNotFoundException
    *           If payload can't be instantiated.
    */
   public void readExternal(ObjectInput in)
   throws IOException,
          ClassNotFoundException
   {
      byte flags = in.readByte();
      unknown = (flags & 1) != 0;
      caseSens = (flags & 2) != 0;
      if ((flags & 4) != 0)
      {
         value = readString(in);
      }
   }

   /**
    * Replacement for the default object writing method. The latest
    * state is written to the output destination.
    * 
    * @param    out
    *           The output destination to which fields will be saved.
    *
    * @throws   IOException
    *           In case of I/O errors.
    */
   public void writeExternal(ObjectOutput out)
   throws IOException
   {
      int flags = unknown ? 1 : 0;
      if (caseSens)
      {
         flags |= 2;
      }
      
      // the next object can be null so we write an indicator var first to
      // notify reader if there is following data to be read
      
      boolean there = (value != null);
      if (there)
      {
         flags |= 4;
      }
      out.writeByte(flags);
      
      if (there)
      {
         writeString(out, (String)value);
      }
   }

   /**
    * Compares this instance with the specified instance and returns a -1 if this instance is less
    * than the specified, 0 if the two instances are equal and 1 if this instance is greater than
    * the specified instance. This is the implementation of the {@code Comparable} interface.
    * <p>
    * Core worker that handles comparisons between 2 string operands after removing trailing
    * spaces and honoring the case sensitivity operands.
    * <p>
    * Case-insensitive comparisons are obtained by manually uppercasing both operands which is how
    * Progress does it. Lowercase comparisons cannot be used because the results for 
    * {@link CompareOps#isGreaterThan}, {@link CompareOps#isLessThan}, 
    * {@link CompareOps#isGreaterThanOrEqual} and {@link CompareOps#isLessThanOrEqual} would be
    * different in Java than in Progress when the following characters are included in the string:
    * <pre>
    * [
    * \
    * ^
    * _
    * '
    * </pre>
    * <p>
    * The algorithm will fail to give meaningful results in the case where one tries to sort
    * against other objects that do not represent compatible values.
    *
    * @param   obj
    *          The instance to compare against.
    *
    * @return  -1, 0 or 1 depending on if this instance is less than, equal to or greater
    *          (respectively) than the specified instance {@code obj}.
    */
   public int compareTo(Object obj)
   {
      if (unknown)
      {
         return -1;
      }

      Text c = null;
      
      if (obj instanceof Text)
      {
         c = (Text) obj;
         
         // quick out if the String objects are the same instance
         if ((unknown && c.unknown) || value == c.value)
         {
            return 0;
         }
         else if (c.unknown)
         {
            return -1;
         }
      }
      else if (obj instanceof String)
      {
         // quick out if the String objects are the same instance
         if (value == obj)
         {
            return 0;
         }
         
         c = new character((String) obj);
      }
      else
      {
         return -1;
      }
      
      // prepare the data
      String s1 = StringHelper.safeTrimTrailingSpaces(value);
      String s2 = StringHelper.safeTrimTrailingSpaces(c.value);
      
      // if either instance requires case sensitivity, force a case sensitive comparison
      if (caseSens || c.caseSens)
      {
         return s1.compareTo(s2);
      }
      
      // DO NOT use String.compareToIgnoreCase() since this lowercases and yields different
      // results for >, <, >= and <= forms when [ \ ^ _ ' characters are included in the operands
      if (s1.isEmpty() && s2.isEmpty())
      {
         return 0;
      }
      else if (s1.isEmpty()) 
      {
         return -1;
      }
      else if (s2.isEmpty()) 
      {
         return 1;
      }
      
      return TextOps.getUpper(s1).compareTo(TextOps.getUpper(s2));
   }

   /**
    * Hash code implementation which is consistent with {@link
    * BaseDataType#equals}.
    *
    * @return  Hash code value for this object instance.
    */
   public int hashCode()
   {
      int result = 17;
      if (isUnknown())
      {
         return result;
      }

      String s = StringHelper.safeTrimTrailing(value);
      result = 37 * result + (caseSens ? s.hashCode() : StringHelper.hashCodeCaseInsensitive(s));
      
      return result;
   }

   /**
    * Return the current value as a Java wrapper.
    *
    * @return  Current value or <code>null</code> if unknown.
    */
   public String toJavaType()
   {
      return (isUnknown() ? null : getValue());
   }
   
   /**
    * Get the state of the {@link #javaSpacifyNull} flag.
    * 
    * @return   See above.
    */
   public boolean isJavaSpacifyNull()
   {
      return javaSpacifyNull;
   }
   
   /**
    * Returns the length of the current Text object instance in chars.  Internal version.
    *
    * @return  Current text variable char length.
    */
   protected int lengthInt()
   {
      return value.length();
   }

   /**
    * Check the overlay type description and throw an exception if the type
    * is not yet supported. The check is case-insensitive.
    *
    * @param    type
    *           Must be "character" at this time.
    *
    * @throws   IllegalArgumentException
    *           If the type doesn't match "character".
    */
   private void checkOverlayType(String type)
   {
      if (type != null && !type.equalsIgnoreCase("character"))
      {
         String err = String.format("OVERLAY type %s is not supported!", type);
         throw new IllegalArgumentException(err);
      }
   }

   /**
    * Core method to implement overlay and replace support. This method
    * changes the current value of this instance, overwriting characters
    * at the specified index given by <code>pos</code> with the text
    * specified by <code>text</code>.  The number of replaced characters
    * is specified by <code>len</code>, with a negative number indicating
    * that the length of characters in <code>text</code> is used.  If
    * <code>overlay</code> mode is requested, then the <code>len</code> 
    * parameter also limits the number of characters in the <code>text</code>
    * parameter which will be used as the replacement.  If any of the
    * parameters are <code>unknown value</code>, the instance will be set
    * to the <code>unknown value</code>.
    *
    * @param    text
    *           Replacement text.
    * @param    pos
    *           The 1-based index into the string at which the replacement
    *           is to occur.
    * @param    len
    *           The number of characters in the source string to replace.
    * @param    overlay
    *           <code>true</code> if overlay mode is required.
    */
   private void replaceWorker(Text       text,
                              NumberType pos,
                              NumberType len,
                              boolean    overlay)
   throws ErrorConditionException
   {
      // quick out
      if (this.isUnknown())
      {
         // nothing to do, you can't replace chars in the unknown value
         return;
      }
      
      // are any of our inputs unknown?
      if (text.isUnknown() || pos.isUnknown() || len.isUnknown())
      {
         // set our result to unknown and return
         setUnknown();
         return;
      }
      
      String rtxt = text.getValue();
      int    rpos = pos.intValue();
      int    rlen = len.intValue();
      
      // argument check
      if (rpos < 1)
      {
         TextOps.genNegativeIndexError();
         return;
      }
      
      // convert from 1-based to 0-based index
      rpos--;
      
      // -1 is equivalent of the length of the replacement text
      if (rlen == -1)
      {
         rlen = rtxt.length();
      }
      
      // replacement text is exactly as passed-in if not in overlay mode
      if (overlay)
      {
         // overlay mode means that the len parameter also changes the 
         // replacement text
         if (rtxt.length() < rlen)
         {
            // pad to the requested length
            rtxt = StringHelper.leftAlignText(rtxt, rlen);
         }
         else
         {
            // truncate the replacement text
            rtxt = rtxt.substring(0, rlen); 
         } 
      }
      
      StringBuilder sb = new StringBuilder();
      
      // start with the beginning of the string
      if (rpos >= value.length())
      {
         // pad to the requested position
         sb.append(StringHelper.leftAlignText(value, rpos));
      }
      else
      {
         // just substring the first portion
         sb.append(value.substring(0, rpos));
      }
      
      // insert our replacement text
      sb.append(rtxt);
      
      // index of the end of the string
      int elen = rpos + rlen;
      
      // if there is any text at the end of the string, add that
      if (value.length() > elen) 
      {
         sb.append(value.substring(elen));
      }
      
      String newVal = sb.toString();
      
      checkUndoable(() -> new character(newVal));
      
      // save the results into our instance data; does not need null character protection
      // since the data is already coming from two existing text instances of the same type
      value = newVal;
   }

   /**
    * Helper to reset the associated instance's case sensitivity to a prior
    * value based on a notification that a particular scope has exited.  This
    * scope notification support is provided by 
    * {@link TransactionManager#registerFinalizable} and the {@link
    * Finalizable} interface.
    */
   private class SensitivityResetter
   implements Finalizable
   {
      /** The sensitivity to which the containing instance will be reset. */ 
      boolean oldCaseSens;
      
      /**
       * Construct an instance which can reset the containing instance's
       * case sensitivity when a notification is received.  See 
       * {@link character#setTemporaryCaseSensitive}.
       *
       * @param    oldCaseSens
       *           The case sensitivity to which the containing instance must
       *           be reset.
       */
      SensitivityResetter(boolean oldCaseSens)
      {
         this.oldCaseSens = oldCaseSens;
      }
      
      /**
       * Processes the end of scope notification and resets the associated 
       * instance's case sensitivity.  This notification is provided by the
       * {@link TransactionManager}.
       */
      public void finished()
      {
         Text.this.caseSens = oldCaseSens;
      }
      
      /**
       * Provides a notification that the external program scope in which the object is registered is
       * is being deleted and the object's reference will be lost after this method is called.
       * <p>
       * This is a no-op in this case.
       */
      @Override
      public void deleted()
      {
         // no-op
      }
      
      /**
       * Processes the iterate notification provided by the {@link
       * TransactionManager}.  This implementation does nothing.
       */
      public void iterate()
      {
      }
      
      /**
       * Processes the retry notification provided by the {@link
       * TransactionManager}.  This implementation does nothing.
       */
      public void retry()
      {
      }      
   }
}