# TargetLob.java

    /*
    ** Module   : TargetLob.java
    ** Abstract : A target parameter to a LobCopy operation.
    **
    ** Copyright (c) 2019-2025, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
    ** 001 ECF 20190510 Created initial version.
    ** 002 ECF 20190706 Implemented COPY-LOB runtime.
    ** 003 CA  20190717 Automatically resize a memptr target if the source doesn't fit.
    ** 004 CA  20191203 Fixed overlay and unknown COPY-LOB related bugs.
    ** 005 OM  20210128 Reset the size of memptr to 0 before reallocating.
    **     OM  20210312 Fixed support for LOBs obtained by dereference operator.
    **     OM  20210328 Improved copy lob engine and validations.
    **     OM  20210404 Fine-tuned errors generated by edge-case in copy-lob statements.
    **     EVL 20220405 Added validateOffset implementation.
    **     EVL 20220408 Refined error messages emission for different source types.
    **     EVL 20220411 Removed TODO for error generation.
    ** 006 EVL 20230317 Adding support for longchar internal length override.
    ** 007 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
    ** 008 CA  20250321 The TARGET of COPY-LOB can also be a class property.
    */

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

    /**
     * Concrete implementation of a converted COPY-LOB target parameter, corresponding with the
     * {@code TO [ OBJECT ] target-lob} phrase.
     */
    public class TargetLob
    extends LobCopyOutput
    {
       /** Large object which is the target of the copy operation */
       private final LargeObject lob;

       /** Field or property reference to LOB, if LOB is a record buffer field or a class property */
       private final Accessor field;

       /** 1-based offset at which data will be written into the target large object */
       private NumberType offset = null;

       /** If {@code true}, truncate any data remaining in the target, which was not overwritten */
       private boolean trim = false;

       /**
        * Constructor.
        *
        * @param   lob
        *          Large object which is the target of the copy operation.
        */
       public TargetLob(LargeObject lob)
       {
          FieldReference tmpField = null;
          if (lob != null)
          {
             tmpField = lob.getFieldReference();
             if (tmpField != null)
             {
                lob.setFieldReference(null); // reset at first use
             }
          }

          this.field = tmpField;
          this.lob = lob;
       }

       /**
        * Constructor which takes an object that is expected to be a LOB. Intended to handle the
        * converted POLY case.
        *
        * @param   lob
        *          Large object which is the target of the copy operation.
        *
        * @throws  ConditionException
        *          if {@code lob} is not an instance of @LargeObject@.
        */
       public TargetLob(Object lob)
       {
          this(LobCopy.assertParameterType(lob));
       }

       /**
        * Constructor which takes a field reference which must be a wrapper for a {@code LargeObject}.
        *
        * @param   field
        *          Field reference to a large object which is the target of the copy operation.
        */
       public TargetLob(FieldReference field)
       {
          this.field = field;
          this.lob = (LargeObject) field.get();
       }

       /**
        * Constructor which takes a property reference which must be a wrapper for a {@code LargeObject}.
        *
        * @param   property
        *          Property reference to a large object which is the target of the copy operation.
        */
       public TargetLob(PropertyReference property)
       {
          this.field = property;
          this.lob = (LargeObject) property.get();
       }

       /**
        * Set the starting offset at which to write content; corresponds with the OVERLAY AT option.
        *
        * @param   offset
        *          1-based starting offset.
        *
        * @return  This object instance, to allow method chaining.
        */
       public TargetLob offset(NumberType offset)
       {
          this.offset = offset;

          return this;
       }

       /**
        * Set the starting offset at which to write content; corresponds with the OVERLAY AT option.
        *
        * @param   offset
        *          1-based starting offset.
        *
        * @return  This object instance, to allow method chaining.
        */
       public TargetLob offset(int offset)
       {
          this.offset = new integer(offset);

          return this;
       }

       /**
        * Configure the copy operation to trim any data remaining in the target object which was not
        * overwritten by the copy.
        *
        * @return  This object instance, to allow method chaining.
        */
       public TargetLob trim()
       {
          this.trim = true;

          return this;
       }

       /**
        * Indicate whether this object is a valid large object data type. It is possible to pass a
        * POLY which is not a valid LOB type as a COPY-LOB parameter, and this error must be
        * determined at runtime.
        *
        * @return  {@code true} if valid, else {@code false}. The default implementation assumes
        *          validity; implementors which must handle runtime type verification must override
        *          this method.
        */
       @Override
       public boolean isLargeObject()
       {
          return lob != null;
       }

       /**
        * Obtain the type of the object wrapped by this parameter
        *
        * @return  The numerical id of a type declared as one of the TYPE static constants from
        *          {@code LobCopyParameter} interface.
        */
       @Override
       public int getObjectType()
       {
          return (lob instanceof clob) ? TYPE_CLOB : // must be checked first
                 (lob instanceof blob) ? TYPE_BLOB :
                 (lob instanceof memptr) ? TYPE_MEMPTR :
                 (lob instanceof longchar) ? TYPE_LONGCHAR : TYPE_UNKNOWN;
       }

       /**
        * Indicate whether the large object represented by this parameter natively represents
        * encoded character data, as opposed to binary data.
        *
        * @return  {@code true} if character data; {@code false} if binary data.
        */
       @Override
       public boolean isCharacterData()
       {
          return lob != null && lob.isCharacterData();
       }

       /**
        * Get the code page of this parameter, if any.
        *
        * @return  4GL code page name or {@code null} if parameter represents non-character data.
        */
       @Override
       public String getCodePage()
       {
          return LobCopy.getLobDefaultCodePage(lob);
       }

       /**
        * Checks if the optional offset is valid.
        *
        * @param   sourceType
        *          The type of the source object. The error messages are different in some cases.
        *
        * @return  {@code true} if the optional offset is valid.
        */
       @Override
       public boolean validateOffset(int sourceType)
       {
          if (offset != null)
          {
             int objectType = getObjectType();
             if ((offset.isUnknown() || offset.intValue() <= 0) &&
                 objectType == TYPE_MEMPTR && sourceType == TYPE_CLOB)
             {
                ErrorManager.recordOrThrowError(11332);
                // Invalid offset specified in COPY-LOB statement. (11332)
                return false;
             }
          }

          return true;
       }

       /**
        * Validate this parameter. The following parameters are checked: offset,
        *
        * @param   sourceType
        *          The type of the source object. The error messages are different in some cases.
        *
        * @return  {@code true} if validation did not encounter any issue and {@code false} otherwise, when in
        *          NO-ERROR mode and the {@code ErrorConditionException} is not thrown.
        *
        * @throws  ErrorConditionException
        *          if the parameter fails validation and silent error mode is not active.
        */
       @Override
       public boolean validate(int sourceType)
       {
          int objectType = getObjectType();
          if (offset != null)
          {
             if (offset.isUnknown() || offset.intValue() <= 0)
             {
                ErrorManager.recordOrThrowError(11332);
                // Invalid offset specified in COPY-LOB statement. (11332)
                return false;
             }

             if (sourceType == TYPE_MEMPTR && objectType == TYPE_MEMPTR)
             {
                if (((memptr)lob).isUninitialized())
                {
                   ErrorManager.recordOrThrowError(11393);
                   // MEMPTR target of COPY-LOB must be initialized. (11393)
                   return false;
                }
             }
          }

          int off = getOffset();
          long size = lob.lengthOf();
          if (off > size)
          {
             if (objectType == TYPE_LONGCHAR)
             {
                // this is fine: this type of variable will 'expand' as needed to accommodate the larger offset
                return true;
             }
             else if (objectType == TYPE_MEMPTR)
             {
                // different errors depending on different conditions
                if (sourceType == TYPE_FILE)
                {
                   ErrorManager.recordOrThrowError(11335);
                }
                else if (sourceType == TYPE_LONGCHAR)
                {
                   ErrorManager.recordOrThrowError(11393);
                }
                else
                {
                   ErrorManager.recordOrThrowError(11394);
                }
                // MEMPTR target of COPY-LOB is not big enough. (11394)
                return false;
             }
             else if (objectType == TYPE_BLOB || objectType == TYPE_CLOB)
             {
                ErrorManager.recordOrThrowError(11335);
                // Offset supplied for target large object is greater than object size. (11335)
                return false;
             }
          }

          // everything seems fine now
          return true;
       }

       /**
        * Write the given array of bytes into the large object.
        *
        * @param   data
        *          Data to be assigned to this object.
        */
       @Override
       public void write(byte[] data)
       {
          int off = getOffset();

          if (!checkTargetSize(off, data == null ? 0 : data.length))
          {
             return;
          }

          lob.write(isOverlay(), data, off, trim);

          if (field != null)
          {
             field.set((BaseDataType) lob);
          }
       }

       /**
        * Write the given character data into the large object.
        *
        * @param   data
        *          Data to be assigned to this object.
        * @param   oecp
        *          Target OE codepage to be used in write operation. Ignored.
        */
       @Override
       public void write(String data, String oecp)
       {
          int off = getOffset();

          lob.write(isOverlay(), data, off, trim);

          if (field != null)
          {
             field.set((BaseDataType) lob);
          }
       }

       /**
        * Get the offset at which to overlay data in the target. If no explicit offset has been
        * specified, return a value of 0.
        *
        * @return  Zero-based target offset.
        */
       private int getOffset()
       {
          return offset != null ? offset.intValue() - 1 : 0;
       }

       /**
        * Check if the OVERLAY option is used.
        *
        * @return   <code>true</code> of {@link #offset} is non-null.
        */
       protected boolean isOverlay()
       {
          return offset != null;
       }

       /**
        * 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 (lob instanceof longchar)
          {
             ((longchar)lob).setOverrideLength(newOverrideLength >= 0 ? newOverrideLength + getOffset() : -1);
          }
       }

       /**
        * Check the target size, and resize if it doesn't fit.
        *
        * @param    offset
        *           The offset where to write in the target.
        * @param    srcLength
        *           The source length.
        *
        * @return  {@code true} if the target passed validation.
        */
       private boolean checkTargetSize(int offset, int srcLength)
       {
          if (lob instanceof memptr)
          {
             memptr m = (memptr) lob;

             if (m.isUninitialized() || m.isUnknown())
             {
                m.setLength(offset + srcLength);
             }
             else
             {
                long lobSize = m.lengthOf();
                if (lobSize < offset + srcLength)
                {
                   if (!isOverlay())
                   {
                      m.setLength(0); // reset first, otherwise resize won't work
                      m.setLength(offset + srcLength);
                   }
                   else
                   {
                      ErrorManager.recordOrThrowError(11394);
                      // MEMPTR target of COPY-LOB is not big enough. (11394)
                      return false;
                   }
                }
             }
          }

          return true;
       }
    }