# clob.java

    /*
    ** Module   : clob.java
    ** Abstract : Data wrapper for SQL CLOB data.
    **
    ** Copyright (c) 2018-2025, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
    ** 001 ECF 20181214 Created initial version.
    ** 002 ECF 20190215 Added binaryEquals.
    ** 003 CA  20190514 Added clob(character) ctor.
    ** 004 CA  20190530 Added clob(BDT) ctor.
    ** 005 ECF 20190628 Implemented COPY-LOB runtime.
    ** 006 CA  20191119 clob initial state is unknown.
    ** 007 CA  20191203 Fixed overlay and unknown COPY-LOB related bugs.
    ** 008 OM  20210225 Pushed up binaryEquals() to LargeObject.
    **     OM  20210312 Added parent FieldReference accessors in case the LOB was obtained by dereferenciation
    **                  of a buffer field.
    **     OM  20210328 Improved COPY-LOB engine and validations.
    **     OM  20210404 Reworked codepage management for CLOB fields. These fields should know their CP from the
    **                  moment they are created (before first assign).
    **     AL2 20220328 Added proxy checks for BDT.
    **     EVL 20221222 Adding runtime support for LOB fields export.
    **     CA  20220613 Do not process empty codepage.
    **     CA  20230207 Field name can be null when transferred via OpenClient calls.
    ** 009 CA  20230215 'instantiateDefault' now is implemented by each sub-class.
    **     CA  20230215 'duplicate()' method returns the real type instead of BDT.
    ** 010 AL2 20250530 Added getIndependentFromContext.
    ** 011 TG  20250519 Added clob.getCodePage(clob).
    */

    /*
    ** This program is free software: you can redistribute it and/or modify
    ** it under the terms of the GNU Affero General Public License as
    ** published by the Free Software Foundation, either version 3 of the
    ** License, or (at your option) any later version.
    **
    ** This program is distributed in the hope that it will be useful,
    ** but WITHOUT ANY WARRANTY; without even the implied warranty of
    ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    ** GNU Affero General Public License for more details.
    **
    ** You may find a copy of the GNU Affero GPL version 3 at the following
    ** location: https://www.gnu.org/licenses/agpl-3.0.en.html
    **
    ** Additional terms under GNU Affero GPL version 3 section 7:
    **
    **   Under Section 7 of the GNU Affero GPL version 3, the following additional
    **   terms apply to the works covered under the License.  These additional terms
    **   are non-permissive additional terms allowed under Section 7 of the GNU
    **   Affero GPL version 3 and may not be removed by you.
    **
    **   0. Attribution Requirement.
    **
    **     You must preserve all legal notices or author attributions in the covered
    **     work or Appropriate Legal Notices displayed by works containing the covered
    **     work.  You may not remove from the covered work any author or developer
    **     credit already included within the covered work.
    **
    **   1. No License To Use Trademarks.
    **
    **     This license does not grant any license or rights to use the trademarks
    **     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
    **     of Golden Code Development Corporation. You are not authorized to use the
    **     name Golden Code, FWD, or the names of any author or contributor, for
    **     publicity purposes without written authorization.
    **
    **   2. No Misrepresentation of Affiliation.
    **
    **     You may not represent yourself as Golden Code Development Corporation or FWD.
    **
    **     You may not represent yourself for publicity purposes as associated with
    **     Golden Code Development Corporation, FWD, or any author or contributor to
    **     the covered work, without written authorization.
    **
    **   3. No Misrepresentation of Source or Origin.
    **
    **     You may not represent the covered work as solely your work.  All modified
    **     versions of the covered work must be marked in a reasonable way to make it
    **     clear that the modified work is not originating from Golden Code Development
    **     Corporation or FWD.  All modified versions must contain the notices of
    **     attribution required in this license.
    */

    package com.goldencode.p2j.util;

    import java.io.*;
    import java.sql.*;
    import com.goldencode.p2j.persist.*;
    import com.goldencode.util.*;

    /**
     * Data wrapper type for SQL CLOB data.
     */
    public class clob
    extends longchar
    {
       /** SQL CLOB object */
       private Clob sqlClob = null;

       /** The parent buffer field where this object was obtained by dereferenciation, if this is the case. */
       private FieldReference fRef;

       /** Flags this field if it was constructed with a custom CODEPAGE specified in the field definition. */
       private boolean customCP = false;

       /** The field name used with current field reference. */
       private String fieldName = null;

       /**
        * Default constructor.
        */
       public clob()
       {
          setUnknown();
       }

       /**
        * Copy constructor.
        *
        * @param   value
        *          Clob to be copied.
        */
       public clob(clob 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 clob)
             {
                value = (clob) value.val();
             }
             else
             {
                assign(value.val());
                return;
             }
          }
          if (value.unknown)
          {
             this.unknown = true;
             return;
          }

          this.unknown = false;
          this.value = value.value; // codePageConvert(value.value, value._getCodePage(), _getCodePage());
          this.sqlClob = value.sqlClob;
          this.fRef = null; //value.fRef

          // copy customCP/CP ?
       }

       /**
        * Constructor which is initialized from a database record.
        *
        * @param   sqlClob
        *          SQL CLOB from which data is read.
        *
        * @throws  SQLException
        *          if there is an error reading data.
        */
       public clob(Clob sqlClob)
       throws SQLException
       {
          this.sqlClob = sqlClob;

          // TODO: enable optimization to skip this step in certain cases (e.g., query field list exclusion)
          readData();
       }

       /**
        * Constructor which initializes an instance from the given data.
        *
        * @param   value
        *          Data.
        */
       public clob(String value)
       {
          assign(value);
       }

       /**
        * Constructor which initializes an instance from the given data.
        *
        * @param   value
        *          Data.
        * @param   codepage
        *          The codepage.
        */
       public clob(String value, String codepage)
       {
          customCP = (codepage != null && !codepage.isEmpty());
          longchar.fixCodePage(this, customCP ? codepage : I18nOps._getCPInternal(), true);

          assign(value);
       }

       /**
        * Constructor which initializes an instance from the given data.
        *
        * @param   value
        *          Data.
        */
       public clob(character value)
       {
          assign(value);
       }

       /**
        * Constructor which initializes an instance from the given data.
        *
        * @param   value
        *          Data.
        */
       public clob(longchar value)
       {
          assign(value);
       }

       /**
        * Constructor which initializes an instance from the given data.
        *
        * @param   value
        *          Data.
        */
       public clob(BaseDataType value)
       {
          assign(value);
       }

       /**
        * Get the type.
        *
        * @return type.
        */
       public Type getType()
       {
          return Type.CLOB;
       }

       /**
        * Read data from the SQL CLOB used to initialize this object.
        *
        * @throws  SQLException
        *          if there is an error reading data.
        */
       public void readData()
       throws SQLException
       {
          if (this.value != null)
          {
             // already initialized
             return;
          }

          String s = null;

          if (sqlClob != null)
          {
             int len = (int) sqlClob.length();
             if (len > 0)
             {
                s = sqlClob.getSubString(1, len);
             }
             else if (len == 0)
             {
                s = "";
             }

             sqlClob.free();
             sqlClob = null;
          }

          assign(s);
       }

       /**
        * Return the value codepage fixed status.
        *
        * @return  Always {@code true}. The codepage for {@code clob}-s cannot be changed.
        */
       public boolean isCodePageFixed()
       {
          return true;
       }

       /**
        * Implementation of {@code IS-COLUMN-CODEPAGE} ABL function.
        *
        * @param   value
        *          The data to be tested.
        *
        * @return  {@code YES} only when {@code value} is a {@code CLOB} field defined with
        *          {@code IS-COLUMN-CODEPAGE} attribute.
        */
       public static logical isColumnCodepage(BaseDataType value)
       {
          if (value == null)
          {
             return new logical(); // or err 5729 ?
          }

          if (value instanceof clob)
          {
             return new logical(((clob) value).customCP);
          }

          ErrorManager.recordOrThrowError(5729);
          // Incompatible datatypes found during runtime conversion. (5729)
          return new logical();
       }

       /**
        * Returns the code page of a the specified clob variable.
        *
        * @param   value
        *          The value for which the codepage will be retrieved.
        *
        * @return  See above
        */
       public static character getCodePage(clob value)
       {
          return value.getCodePage();
       }

       /**
        * @see com.goldencode.p2j.util.BaseDataType#duplicate()
        *
        * This class is not undoable, so return a reference to this object.
        *
        * @return  This object.
        */
       @Override
       public clob duplicate()
       {
          return this;
       }

       /**
        * Creates a new instance of the same type that represents the {@code unknown value}.
        *
        * @return  An instance that represents the {@code unknown value}.
        */
       @Override
       public BaseDataType instantiateUnknown()
       {
          clob clb = new clob();
          clb.setUnknown();

          return clb;
       }

       /**
        * Creates a new instance of the same type that represents the default initialized value.
        *
        * @return   An instance that represents the default value.
        */
       @Override
       public BaseDataType instantiateDefault()
       {
          return instantiateUnknown();
       }

       /**
        * 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>honors the {@code trim} option (i.e., any existing characters beyond the end of the
        *     write are left in place if {@code trim} is {@code false} or truncated if {@code trim}
        *     is {@code true});</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
          if (overlay)
          {
             /*
             TODO: this error is not thrown if the source is longchar, only if is clob
             if (!isUnknown() && data != null && data.length() > 0 && lengthOf() == 0)
             {
                ErrorManager.recordOrThrowError(11298, "COPY-LOB could not copy the source large object to the target", false);
                return;
             }
             */
             if (data != null && (value == null || offset >= value.length()))
             {
                int rightFill = offset - (value == null ? 0 : value.length());
                assign(StringHelper.rightAlignText("", rightFill));
             }
          }

          int dataLen;

          if (data == null || (dataLen = data.length()) == 0 && !trim || offset == 0 && value == data)
          {
             // 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)
             {
                if (data != null)
                {
                   assign(data);
                }
                else
                {
                   setUnknown();
                }
             }

             return;
          }

          int currentLen = value != null ? value.length() : 0;

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

       /**
        * Sets the {@code FieldReference} reference. This is only used when the object is obtained by a
        * dereferenciation ({@code ::}) operation.
        *
        * @param   fr
        *          The source of this LOB.
        */
       @Override
       public void setFieldReference(FieldReference fr)
       {
          this.fRef = fr;

          // save current field name
          if (fr != null && (fieldName == null || fieldName.isEmpty()))
          {
             fieldName = fr.getLegacyFieldName();
          }
       }

       /**
        * Obtain the {@code FieldReference} reference, if any.
        *
        * @return  The parent {@code FieldReference} from which this LOB was obtained by dereferenciation.
        */
       @Override
       public FieldReference getFieldReference()
       {
          return fRef;
       }

       /**
        * Gets the filed name associated with current {@code FieldReference} reference, if any.
        *
        * @return  The field name to be used in LOB file name creation.
        */
       public String getFieldName()
       {
          return fieldName;
       }

       /**
        * 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.
        */
       @Override
       public void writeExternal(ObjectOutput out)
       throws IOException
       {
          super.writeExternal(out);
          out.writeObject(fieldName);
       }

       /**
        * 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.
        */
       @Override
       public void readExternal(ObjectInput in)
       throws IOException,
              ClassNotFoundException
       {
          super.readExternal(in);
          fieldName = (String) in.readObject();
       }

       /**
        * Assigns the value represented by {@code value} to this object. If needed (the source {@code value} has
        * a different codepage), a codepage conversion is performed.
        *
        * @param   value
        *          The source {@code Text} to be assigned.
        */
       @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;
          }
          super.assign(TextOps.codePageConvert(value, _getCodePage()));
       }

       /**
        * Assigns the value represented by {@code value} to this object. Since all {@code clob} fields have a
        * fixed codepage, a codepage conversion will occur.
        *
        * @param   value
        *          The source {@code longchar} / {@code clob} to be assigned.
        */
       @Override
       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())
          {
             setUnknown();
          }
          else
          {
             super.assign(TextOps.codePageConvert(value, _getCodePage()));
          }
       }

       /**
        * 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.
        */
       @Override
       public String toStringExport()
       {
          return isUnknown() ? "?" : value == null ? "" : value;
       }

       /**
        * Identify if this data is dependent upon the context local. This identifies
        * if the data is safe to be cached in cross-session places or the data should
        * not leak from the context. If is is not safe to be shared cross-session, then
        * this should return {@code null}. If other representation is suitable for caching,
        * then this can return another object that wraps internal values that are not
        * dependent upon the context (e.g. date, date-time, date-time-tz).
        *
        * @return   Always. {@code null}.
        */
       @Override
       public Object getIndependentFromContext()
       {
          return null;
       }
    }