longchar.java

/*
** Module   : longchar.java
** Abstract : Progress 4GL compatible longchar object
**
** Copyright (c) 2012-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
** 001 AIL 20121206 Created initial version to support for Progress LONGCHAR type.
** 002 GES 20130322 Added the BDT c'tor.
** 003 VIG 20131003 Overridden instantiateUnknown method to return longchar
** 004 MAG 20140602 Fixed the BDT c'tor and modified the assign(BDT,boolean) on _POLY morphing.
**                  Override compareTo() method for _POLY morphing.
** 005 GES 20141003 Added overridden method to disable assignments of values with embedded
**                  null characters.
** 006 CA  20151222 Added instantiateDefaultExtent.
** 007 GES 20171207 Removed forced version of assign().
** 008 ECF 20181214 Implemented LargeObject interface.
**     CA  20181221 Added GET-CODEPAGE variants.
** 009 EVL 20190412 Added runtime support for GET-CODEPAGE, FIX-CODEPAGE, IS-CODEPAGE-FIXED.
** 010 ECF 20190628 Implemented COPY-LOB runtime.
** 011 HC  20190805 Added method instantiateDefault.
** 012 CA  20191119 Fixed write(...) - if the data is empty, then assign it.
** 013 CA  20191203 Fixed overlay and unknown COPY-LOB related bugs. 
** 014 ME  20200401 Fixed codepage set on assign, fix codepage validation.
** 015 ME  20200810 Cleanup for toStringExport().
**     IAS 20201007 Added Type enum
**     ME  20201009 Allow codepage to be reset back to unknown in fix-codepage.
**     OM  20210203 Added API to accept unknown parameter for fixCodePage() method.
**     OM  20210312 FIX-CODE-PAGE statement is converted to a static method to accomodate the cases when
**                  the object is obtained by dereference. Improved COPY-LOB engine and validations.
**     OM  20210404 Reworked codepage management for CLOB fields.
**     CA  20210428 Fixed conversion when the lvalue for an assign-styme stmt is a OO property (chained or 
**                  not). In this case, a setter must wrap the assign-style stmt, to change the property's 
**                  value.
**     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.
**     CA  20210713 Fixed COPY-LOB when source is unknown (must set the longchar target to unknown).
**     AL2 20220328 Added proxy checks for BDT.
**     EVL 20220406 Fix for pverlay mode in write method when we need to replace the text considering offset
**                  in target object.
**     EVL 20220411 User regular overlay method when using offset with overlay mode.  Adding trim option
**                  support for writing to longchar.  Reworked overlay trim because of different meaning
**                  the word 'trim' has here.
**     CA  20221024 'asByteArray' must use the actual codepage set at the longchar instance, regardless if 
**                  this was set via FIX-CODEPAGE or via an assignment from another longchar instance.
** 016 HC  20230118 Eliminated some of the uses of String.toUpperCase and/or String.toLowerCase
**                  for performance.
** 017 CA  20230215 'duplicate()' method returns the real type instead of BDT.
** 018 EVL 20230317 Adding supprt for longchar variable length override in some conditions.  This value can
**                  differ from backend String value length.
**     EVL 20230322 Changed approach for setting length override value.  If was already set the new value
**                  accepted if it exceeds the current one.
** 019 CA  20230526 Added 'forceCharacterType', emitted for a 'ELSE' longchar expr in a ternary IF which is
**                  used for a builtin function call.
** 020 EVL 20240607 Fix for unwanted message for assignment to unknown value. If codepage was already fixed
**                  no need to do this again.
** 021 SP  20240805 Fix for unwanted 11683 error for assignment to unknown value. Moved the fixCodepage call
**                  after the assignment.
** 022 CA  20241030 Avoid 'Text.javaSpacifyNull' if the string is known to already have been processed.
** 023 AP  20250318 Improved toStringMessage method to take into account unknown values.
** 024 TG  20250519 Removed longchar.getcodePage(BaseDataType) because it is implemented in I18nOps
**         20250520 Replaced new character() with character.UNKNOWN in getCodePage() for better performance
*/

/*
** 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 com.goldencode.util.*;
import java.io.*;

/**
 * A class that represents a Progress 4GL compatible longchar object whose data is mutable.
 * <p>
 * Although Progress documentation states that the default value of any <code>longchar</code>
 * variable is the unknown value, the real behavior is that it gets initialized to the empty
 * string, same as character variables get initialized.
 */
public class longchar
extends Text
implements LargeObject
{
   /** Variable to store code page value set by FIX-CODEPAGE statement. */
   private String codePage;

   /** 
    * Variable to store if code page value was set by FIX-CODEPAGE statement. 
    * IS-CODEPAGE-FIXED will relay on this
    * */
   private boolean fixedCodepage;
   
   /**
    * The override value for variable length.  Can be changed in copy-lob operration if target is longchar
    * and source is memptr.
    */
   private int overrideLength = -1;

   /**
    * Default constructor, creates an instance that represents the unknown value.
    */
   public longchar()
   {
      super();
   }

   /**
    * 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 (this is delegated to the superclass' constructor).
    * <p>
    * Only Text (character or longchar) BDT's can be converted to longchar.
    * 
    * @param    value
    *           The value to be used for this instance.
    */
   public longchar(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
      {
         if (value instanceof Text)
         {
            assign(value);
         }
         else
         {
            incompatibleTypesOnConversion();
         }
      }
   }

   /**
    * 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>.
    * If value is not "" then codepage is cpinternal
    *
    * @param    value
    *           The character value of the resulting instance.
    */
   public longchar(String value)
   {
      super(value);
      
      if (!TextOps.isEmpty(value))
      {
         codePage = I18nOps._getCPInternal();
      }
   }
   
   /**
    * 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>.
    *
    * @param    value
    *           The character value of the resulting instance.
    * @param    caseSens
    *           Specifies whether this instance should use case sensitive
    *           comparisons.
    */
   public longchar(String value, boolean caseSens)
   {
      super(value, caseSens);
      
      if (!TextOps.isEmpty(value))
      {
         codePage = I18nOps._getCPInternal();
      }
   }
   
   /**
    * Constructs an instance that has a <code>char</code> value passed
    * in and which <b>does not</b> represent the <code>unknown value</code>.
    *
    * @param    ch
    *           The character value of the resulting instance.
    */
   public longchar(char ch)
   {
      super(ch);
   }

   /**
    * Create a new instance, by copying the state of the passed {@link Text}.
    * 
    * @param    value
    *           The value which needs to be copied.
    */
   public longchar(Text value)
   {
      super(value);
   }

   /**
    * Force the longchar value as a character type, without limit length enforcement.
    * 
    * @param    val
    *           The value to convert.
    *           
    * @return   The new character value, with {@link character#forceLengthLimit()} disabled.
    */
   public static character forceCharacterType(longchar val)
   {
      return new character(val)
      {
         @Override
         protected boolean forceLengthLimit()
         {
            return false;
         }
      };
   }
   
   /**
    * Get the type
    * @return type
    */
   public Type getType()
   {
      return Type.LONGCHAR;
   }
   
   /**
    * Report if existing instances that have embedded null characters are allowed to be
    * assigned to this instance.
    *
    * @return   Always <code>false</code>.
    */
   public boolean isEmbeddedNullAllowed()
   {
      return false;
   }

   /**
    * 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</code>
    * interface.
    *
    * @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</code>.
    */
   public int compareTo(Object obj)
   {
      if (obj instanceof BaseDataType)
      {
         return super.compareTo(new longchar((BaseDataType) obj));
      }

      return super.compareTo(obj);
   }

   /**
    * Returns the code page of a the specified longchar variable. If the 
    * specified LONGCHAR is empty and the code page was not fixed using the 
    * FIX-CODEPAGE statement, must return the unknown value.
    * 
    * @param    value
    *           The value for which the codepage will be retrieved.
    *
    * @return   See above
    */
   public static character getCodePage(longchar value)
   {
      return value.getCodePage();
   }

   /**
    * Returns the code page of a the current longchar variable. If the LONGCHAR is unknown or empty and the
    * code page was not fixed using the FIX-CODEPAGE statement, the unknown value is returned.
    * 
    * @return   See above
    */
   public character getCodePage()
   {
      String cp = _getCodePage();
      return (cp == null) ? character.UNKNOWN : new character(cp.toUpperCase());
   }
   
   /**
    * Returns the code page of a the current longchar variable. If the LONGCHAR is unknown or empty and the
    * code page was not fixed using the FIX-CODEPAGE statement, the unknown value is returned.
    * <p>
    * This is the String returning value, the unknown value is represented by {@code null}.
    * 
    * @return   See above
    */
   public String _getCodePage()
   {
      if (fixedCodepage)
      {
         return codePage;
      }
      
      if (isUnknown() || value.isEmpty())
      {
         return null;
      }
      
      // at this moment codePage must not be null
      return codePage;
   }
   
   /**
    * Creates a new instance of the same type that represents the {@code unknown value}.
    *
    * @return   An instance that represents the {@code unknown value}.
    */
   public BaseDataType instantiateUnknown()
   {
      return new longchar();
   }
   
   /**
    * Return {@code true} if the codepage of the given longchar variable is fixed.
    * 
    * @param    value
    *           The value for which the codepage will be retrieved.
    *
    * @return   See above.
    */
   public static logical isCodePageFixed(longchar value)
   {
      return new logical(value != null && value.isCodePageFixed());
   }
   
   /**
    * Implements Progress 4GL feature for FIX-CODEPAGE statement.
    *
    * @param   longcharRef
    *          A reference to a {@code longchar} variable.
    * @param   cpToUse
    *          The new value for codepage to be fixed.
    * 
    * @return  The longchar reference.
    */
   public static longchar fixCodePage(longchar longcharRef, String cpToUse)
   {
      return fixCodePage(longcharRef, cpToUse, false);
   }
   
   /**
    * Implements Progress 4GL feature for FIX-CODEPAGE statement.
    * <p>
    * Note: actually this method should be as less visible as possible, preferably private. However, it must
    *       be accessed from {@link com.goldencode.p2j.persist.FieldReference}.
    *
    * @param   longcharRef
    *          A reference to a {@code longchar} variable.
    * @param   cpToUse
    *          The new value for codepage to be fixed. If {@code prepareClob} is true, this parameter must NOT  
    *          be {@code null}.
    * @param   prepareClob
    *          Allow to set a FIXED the CODEPAGE on target {@code longcharRef} only if it is a field (hence
    *          a {@code clob}) only when this parameter is {@code true}. Otherwise the procedure is finished.
    *          At least this is behaviour in 4GL: ERROR-STATUS:ERROR = YES ERROR-STATUS:GET-NUMBER(1) = 0.
    *          ERROR-STATUS:get-message(1) = "".
    * 
    * @return  The longchar reference.
    */
   public static longchar fixCodePage(longchar longcharRef, String cpToUse, boolean prepareClob)
   {
      if (longcharRef instanceof clob)
      {
         if (!prepareClob || longcharRef.fixedCodepage)
         {
            // yes!, isn't it funny when procedure stops without any apparent reason?
            ErrorManager.recordOrThrowError(0, "", false);
            return longcharRef;
         }
      }
      
      // allow to lazily set the CP on CLOB fields 
      if (!TextOps.isEmpty(longcharRef) && !prepareClob)
      {
         ErrorManager.recordOrThrowError(11683);
         // FIX-CODEPAGE can only be run when the LONGCHAR variable is empty.
      }
      else
      {
         if (cpToUse == null)
         {
            longcharRef.codePage = null;
            longcharRef.fixedCodepage = false;
         }
         else if (!I18nOps.isSupported(cpToUse))
         {
            ErrorManager.recordOrThrowError(912, cpToUse.toUpperCase(), I18nOps.CONVERSION_MAP_FILE);
            // Code page attribute table for <code-page> was not found in <filename>.
         }
         else
         {
            if (I18nOps.CP_UNDEFINED.equalsIgnoreCase(cpToUse))
            {
               ErrorManager.recordOrThrowError(12125, cpToUse, "");
               // The codepage "<codepage>" cannot be used for CLOB or LONGCHAR variables.
            }
            else
            {
               longcharRef.codePage = cpToUse;
               longcharRef.fixedCodepage = true;
            }
         }
      }
      
      return longcharRef;
   }
   
   /**
    * Implements Progress 4GL feature for FIX-CODEPAGE statement.
    *
    * @param   longcharRef
    *          A reference to a {@code longchar} variable.
    * @param   cpToUse
    *          The new value for codepage to be fixed.
    */
   public static void fixCodePage(longchar longcharRef, Text cpToUse)
   {
      fixCodePage(longcharRef, (cpToUse == null) ? null : cpToUse.toJavaType());
   }
   
   /**
    * Implements Progress 4GL feature for FIX-CODEPAGE statement. This variant is used when the first
    * argument is obtained by dereferenciation.
    *
    * @param   value
    *          A reference to a BDT, obtained by dereferenciation.
    * @param   cpToUse
    *          The new value for codepage to be fixed.
    */
   public static void fixCodePage(BaseDataType value, String cpToUse)
   {
      if (value instanceof longchar)
      {
          fixCodePage((longchar) value, cpToUse);
      }
      else
      {
         ErrorManager.recordOrThrowError(5729);
         // Incompatible datatypes found during runtime conversion. (5729)
      }
   }
   
   /**
    * Implements Progress 4GL feature for FIX-CODEPAGE statement. This variant is used when either of the
    * arguments or both are obtained by dereferenciation.
    *
    * @param   value
    *          A reference to a BDT, obtained by dereferenciation.
    * @param   cpToUse
    *          The new value for codepage to be fixed.
    */
   public static void fixCodePage(BaseDataType value, BaseDataType cpToUse)
   {
      if (cpToUse == null || cpToUse.isUnknown())
      {
         fixCodePage(value, (String) null);
      }
      else if (cpToUse instanceof Text && !(cpToUse instanceof clob))
      {
         fixCodePage(value, ((Text) cpToUse).toJavaType());
      }
      else if (cpToUse instanceof blob || cpToUse instanceof clob ||
               cpToUse instanceof raw  || cpToUse instanceof memptr)
      {
         ErrorManager.recordOrThrowError(5729);
         // Incompatible datatypes found during runtime conversion. (5729)
      }
      else
      {
         ErrorManager.recordOrThrowError(912, cpToUse.toStringMessage(), I18nOps.CONVERSION_MAP_FILE);
         // Code page attribute table for <code-page> was not found in <filename>. (912)
      }
   }
   
   /**
    * Implements Progress 4GL feature for FIX-CODEPAGE statement.
    * 
    * @param    cpToUse
    *           The new value for codepage to be fixed.
    */
   public void fixCodePage(String cpToUse)
   {
      fixCodePage(this, cpToUse);
   }
   
   /**
    * Implements Progress 4GL feature for FIX-CODEPAGE statement.
    * 
    * @param    cpToUse
    *           The new value for codepage to be fixed.
    */
   public void fixCodePage(character cpToUse)
   {
      fixCodePage(this, (cpToUse != null) ? cpToUse.getValue() : null);
   }
   
   /**
    * Return the value codepage fixed status.
    *
    * @return   <code>TRUE</code> is codepage was fixed for this variable,
    *           <code>FALSE</code> otherwise.
    */
   public boolean isCodePageFixed()
   {
      return fixedCodepage;
   }

   /**
    * Return the default display format string for this type.
    *
    * @return   The default format string.
    */
   public String defaultFormatString()
   {
      return null;
   }

   /**
    * Does the same as standard <code>clone()</code> method but returns an
    * instance of <code>BaseDataType</code> and doesn't throw the
    * <code>CloneNotSupportedException</code>.
    *
    * @return   A clone of this instance.
    */
   public longchar duplicate()
   {
      return new longchar(this);
   }
   
   /**
    * Creates a string representation of the instance data in a form that is compatible with the
    * {@code MESSAGE} language statement.  If the instance represents the {@code unknown value}, a '?' will
    * be returned.
    *
    * @return   The 'message' formatted string.
    */
   public String toStringMessage()
   {
      return toStringExport();
   }
   
   /**
    * Creates a string representation of the instance data using the 'export'
    * format.  For the character data type the entire string is returned
    * enclosed in a pair of double quotes.  If the instance represents the
    * <code>unknown value</code>, a '?' will be returned.  Any instances of
    * double quotes inside the string will be doubled up on output.
    *
    * @return   The 'export' formatted string.
    */
   public String toStringExport()
   {
      return isUnknown() ? "?" : getValue();
   }
   
   /**
    * Creates a string representation of the instance data using the user- specified Progress 4GL format
    * string. The algorithm to format a {@code longchar} is basically the same as for any {@code character}.
    *
    * @param    fmt
    *           The Progress 4GL format string for a character data type.
    *
    * @return   The formatted string.
    */
   public String toString(String fmt)
   {
      if (isUnknown())
      {
         return "?";
      }
      
      String clean = null;
      
      // implement 'default' behavior 
      if (fmt == null)
      {
         fmt = character.defaultFormat;
         
         // default case doesn't specify
         clean = value;
      }
      else
      {
         // for some reason, when there is an explicit format string, the value is specified
         clean = javaSpacifyNull ? javaSpacifyNull(value) : value;
      }
      
      // make the format string easier to process
      String format = character.preprocessFormatString(fmt);
      
      // trim trailing whitespace off the value
      String trimmed = StringHelper.safeTrimTrailing(clean);
      
      // build the output string and return it
      return character.processDataString(trimmed, format);
   }

   /**
    * Indicate whether this object manages character data (as opposed to binary data).
    * 
    * @return  Always {@code true}.
    */
   @Override
   public boolean isCharacterData()
   {
      return true;
   }

   /**
    * The length of the data managed by this object.
    *
    * @return  The length, or 0 if uninitialized/unknown.
    */
   @Override
   public long lengthOf()
   {
      if (isUnknown())
      {
         return 0;
      }
      
      return value.length();
   }
   
   /**
    * Return the entire contents of the current buffer as an array. This may be a copy of the data, depending
    * on the subclass' implementation. The byte array is the binary representation of this LOB using the
    * configured codepage. If none was configured the default CPInternal is used. 
    * 
    * @param   pos
    *          Starting offset position.
    * @param   len
    *          Length of range to return.
    * 
    * @return  The contents of the instance. If the instance is uninitialized the returned
    *          value will be a 0 length array. If the instance is {@code unknown} or has an
    *          undefined size, then {@code null} will be returned.
    */
   @Override
   public byte[] asByteArray(long pos, long len)
   {
      if (isUnknown())
      {
         return null;
      }
      
      int offset = (int) Math.max(0, pos);
      int length = (int) Math.min(len, value.length());
      String s = (pos == 0 && len == length) ? value : value.substring(offset, length);
      
      String codePage = this.codePage != null ? this.codePage : I18nOps._getCPInternal();
      String charset = I18nOps.convmap2Java.get(codePage);
      
      try
      {
         return s.getBytes(charset);
      }
      catch (UnsupportedEncodingException e)
      {
         ErrorManager.recordOrThrowError(912, codePage, I18nOps.CONVERSION_MAP_FILE);
         // Code page attribute table for <code-page> was not found in <filename>. 
         return null;
      }
   }

   /**
    * Write the given character data into the data managed by this object, overwriting the data
    * that are at the given location, if any.
    * <p>
    * This implementation:
    * <ul>
    * <li>ignores the {@code trim} option (i.e., any existing characters beyond the end of the
    *     write are left in place, regardless of the value of {@code trim});</li>
    * <li>extends the target size if the write goes past the end of the existing value.</li>
    * </ul>
    * 
    * @param   overlay
    *          Flag indicating if the OVERLAY option is used.
    * @param   data
    *          Data to be assigned to this object.
    * @param   offset
    *          Offset position (in characters) in the target large object.
    * @param   trim
    *          {@code true} to truncate any remaining data in the target large object; {@code
    *          false} to leave remaining data alone. Ignored.
    */
   @Override
   public void write(boolean overlay, String data, int offset, boolean trim)
   {
      // if we are in overlay mode, fill with 'offset - 1' spaces to the right
      // TODO: if value is null and source is clob, then this remains unknown....
      if (overlay && (value == null || offset >= value.length()))
      {
         int rightFill = offset - (value == null ? 0 : value.length());
         assign(value + StringHelper.rightAlignText("", rightFill));
      }
      
      int dataLen;
      
      if (data == null || (dataLen = data.length()) == 0 || (offset == 0 && data.equals(value)))
      {
         // nothing to write or data already is stored in this object (we don't do a deep equals
         // check because it could be expensive and the common case is likely a full assignment
         // anyway, so don't take the hit here, just quickly check for object reference equality)
         if (overlay)
         {
            // do the trimming is requested
            if (trim && value != null && offset >= 0 && offset < value.length())
            {
               // do not using regular trim function here
               assign(value.substring(0, offset) + ((data != null) ? data : ""));
            }
         }
         else
         {
            if (data != null)
            {
               assign(data);
            }
            else
            {
               setUnknown();
               // TODO: CA - this was commented out because 'COPY-LOB tt1.clobField to lc' sets the target
               // to unknown if tt1.clobField is unknown.
               // assign(""); 
            }
         }
         
         return;
      }
      
      int currentLen = (value != null) ? value.length() : 0;
      
      if (offset > currentLen)
      {
         ErrorManager.recordOrThrowError(11316);
         // Could not obtain LONGCHAR. (11316)
         return;
      }
      
      // simple case: just replace existing value with data
      if (!overlay || isUnknown() || (offset == 0 && (currentLen <= dataLen)))
      {
         assign(data);
         return;
      }
      
      // complex case: overlay data over existing data, possibly extending the length
      int endData = offset + dataLen;
      int newLen = trim ? endData : Math.max(currentLen, endData);
      char[] buf = new char[newLen];
      
      if (offset > 0)
      {
         // copy first portion from existing value into new buffer
         value.getChars(0, offset, buf, 0);
      }
      
      // copy new data to its target offset in new buffer
      data.getChars(0, dataLen, buf, offset);
      
      if (newLen > endData)
      {
         // copy last portion from existing value into new buffer
         value.getChars(endData, currentLen, buf, endData);
      }
      
      assign(new String(buf));
   }
   
   /**
    * Creates a new instance of the same type that represents the
    * default initialized value.
    *
    * @return   An instance that represents the default value.
    */
   public BaseDataType instantiateDefault()
   {
      return new longchar("");
   }

   /**
    * Get the default initialization for an extent variable of this type.
    * 
    * @return   See above.
    */
   @Override
   protected BaseDataType instantiateDefaultExtent()
   {
      return new longchar("");
   }
   
   /**
    * Assigns the value represented by {@code value} to this object. If this {@code longchar} does not have a
    * fixed codepage, the default codepage (SESSION:CPINTERNAL) is used. Otherwise a codepage conversion
    * occurs.
    *
    * @param   value
    *          The source {@code Text} to be assigned.
    */
   @Override
   protected boolean isIncompatibleTypesOnConversion(BaseDataType value)
   {
      // invalid conversion is being thrown even if unknown
      if (value instanceof blob ||
      // if not unknown only longchar/character allowed
               (value != null && !value.isUnknown() && !(value instanceof longchar)
                        && !(value instanceof character)))
      {
         incompatibleTypesOnConversion();
      }

      return super.isIncompatibleTypesOnConversion(value);
   }
   
   /**
    * Sets new value for longchar overridden length.  Can be more than backend String variable and sets up
    * in copy-lob operation in some conditions.
    *
    * @param    newOverrideLength
    *           The new value of the internal longchar length.
    */
   protected void setOverrideLength(int newOverrideLength)
   {
      if(newOverrideLength == -1 || newOverrideLength > overrideLength)
      {
         overrideLength = newOverrideLength;
      }
   }

   /**
    * Returns the length of the current Text object instance in chars.  Internal version.
    *
    * @return  Current text variable char length.
    */
   @Override
   protected int lengthInt()
   {
      return overrideLength != -1 ? overrideLength : super.lengthInt();
   }

   @Override
   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;
      }
      if (value instanceof longchar)
      {
         assign((longchar) value);
      }
      else
      {
         if (value.isUnknown())
         {
            // the fixed codepage is kept
            setUnknown();
         }
         else
         {
            if (!isCodePageFixed() && !TextOps.isEmpty(value))
            {
               codePage = I18nOps._getCPInternal();
            }
            
            super.assign(value);
         }
      }
   }
   
   /**
    * Assigns the value represented by {@code value} to this object. If this {@code longchar} does not have a
    * fixed codepage, the codepage from the source is copied too. Otherwise a codepage conversion occurs.
    *
    * @param   value
    *          The source {@code longchar} to be assigned.
    */
   public void assign(longchar 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())
      {
         if (!isCodePageFixed())
         { 
            codePage = value._getCodePage();
            super.assign(value);
         }
         else 
         {
            super.assign(TextOps.codePageConvert(value, codePage, value._getCodePage()));
         }
      } 
      else
      {
         super.assign(value);
         // do not fix code page if already done
         if (!isCodePageFixed())
         {
            fixCodePage(this, (String) null);
         }
      }
   }
   
   /**
    * Obtain the length (in bytes) of this BDT will use when serialized. 
    *
    * @return  the length of this BDT will use when serialized.
    */
   @Override
   public int getSize()
   {
      return super.value.length();
   }
}