# Memptr.java

    /*
    ** Module   : Memptr.java
    ** Abstract : Implementation of the builtin class OpenEdge.Core.Memptr.
    **
    ** Copyright (c) 2019-2023, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- ---------------------------------------Description----------------------------------------
    ** 001 CA  20190526 First version, stubs taken by converting the skeleton using FWD.
    ** 002 CA  20190710 First pass at runtime implementation; _Debug and GetHash are still stubs.
    **                  Codepage support is not implemented.
    ** 003 CA  20191024 Added method support levels and updated the class support level.
    ** 004 ME  20200331 Fix constructors, add setters and validate input parameters for get methods.
    **     GES 20200526 Change to match legacy enum implementation.
    ** 005 CA  20210113 Renamed clear() to clear_(), to follow NameConverter's rules.
    **     ME  20210204 The class implements ImemptrHolder.
    ** 006 CA  20210221 Fixed 'qualified', 'extent' and 'returns' annotations at the legacy
    **                  signature.
    **     CA  20220923 Variable definitions (including associated with parameters) must be done always outside of
    **                  the BlockManager API
    ** 007 CA  20231019 All BlockManager calls must have a non-null referent.
    ** 008 CA  20231113 The 'execute' method must be annotated with LegacySignature Type.Execute, and also can be
    **                  dropped if is a no-op.
    */

    /*
    ** 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.oo.core;

    import com.goldencode.p2j.util.*;
    import com.goldencode.p2j.util.BlockManager.Action;
    import com.goldencode.p2j.util.BlockManager.Condition;
    import com.goldencode.p2j.util.InternalEntry.Type;
    import com.goldencode.p2j.oo.common.support.ImemptrHolder;
    import com.goldencode.p2j.oo.lang.*;
    import java.io.IOException;

    import static com.goldencode.p2j.util.MathOps.*;
    import static com.goldencode.p2j.util.BlockManager.*;
    import static com.goldencode.p2j.report.ReportConstants.*;

    /**
     * Business logic (converted to Java from the 4GL source code
     * in OpenEdge/Core/Memptr.cls).
     */
    @LegacyResource(resource = "OpenEdge.Core.Memptr")
    @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_PARTIAL)
    public class Memptr extends BaseObject implements ImemptrHolder
    {
       private logical autoDestroy = TypeFactory.logical();

       private logical externalAllocation = TypeFactory.logical();

       private memptr value = TypeFactory.memptr();

       @LegacySignature(type = Type.EXECUTE)
       public void __core_Memptr_execute__()
       {
          onBlockLevel(Condition.ERROR, Action.THROW);
       }

       @LegacySignature(returns = "LOGICAL", type = Type.GETTER, name = "AutoDestroy")
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
       public logical getAutoDestroy()
       {
          return function(this, "AutoDestroy", logical.class, new Block((Body) () ->
          {
             returnNormal(autoDestroy);
          }));
       }

       /**
        * Set auto destroy.
        *
        * @param    _autodestroy
        *           destroy flag
        */
       @LegacySignature(type = Type.SETTER, name = "AutoDestroy", parameters = {
                @LegacyParameter(name = "autodestroy", type = "LOGICAL", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
       public void setAutoDestroy(logical _autodestroy)
       {
          logical autodestroy = TypeFactory.initInput(_autodestroy);
          internalProcedure(this, "AutoDestroy", new Block((Body) () ->
          {
             this.autoDestroy.assign(autodestroy);
          }));
       }

       @LegacySignature(returns = "OBJECT", qualified = "OpenEdge.Core.Memptr", type = Type.GETTER, name = "Empty")
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
       public static object<? extends Memptr> getEmpty()
       {
          ObjectOps.load(Memptr.class);

          return function(Memptr.class, "Empty", object.class, new Block((Body) () ->
          {
             returnNormal(ObjectOps.newInstance(Memptr.class, "I", new int64(0)));
          }));
       }

       @LegacySignature(returns = "LOGICAL", type = Type.GETTER, name = "ExternalAllocation")
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
       public logical getExternalAllocation()
       {
          return function(this, "ExternalAllocation", logical.class, new Block((Body) () ->
          {
             returnNormal(externalAllocation);
          }));
       }

       @LegacySignature(returns = "INT64", type = Type.GETTER, name = "Size")
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
       public int64 getSize()
       {
          return function(this, "Size", int64.class, new Block((Body) () ->
          {
             returnNormal(BinaryData.length(value));
          }));
       }

       /**
        * Set size.
        *
        * @param    _size
        *           The new size.
        */
       @LegacySignature(type = Type.SETTER, name = "Size", parameters = {
                @LegacyParameter(name = "size", type = "INT64", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
       public void setSize(int64 _size)
       {
          int64 size = TypeFactory.initInput(_size);
          internalProcedure(this, "Size", new Block((Body) () ->
          {
             if (this.getSize().longValue() == 0)
                this.value.setLength(size);
          }));
       }

       @LegacySignature(returns = "MEMPTR", type = Type.GETTER, name = "Value")
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
       public memptr getValue()
       {
          // return the actual memptr reference, returnNormal is making a deep copy
          return value;
       }

       @LegacySignature(type = Type.CONSTRUCTOR)
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
       public static void __core_Memptr_constructor__static__()
       {
          externalProcedure(Memptr.class, new Block((Body) () ->
          {
             onBlockLevel(Condition.ERROR, Action.THROW);
          }));
       }

       @LegacySignature(type = Type.CONSTRUCTOR)
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
       public void __core_Memptr_constructor__()
       {
          internalProcedure(this, "__core_Memptr_constructor__", new Block((Body) () ->
          {
             __lang_BaseObject_constructor__();

             this.externalAllocation.assign(false);
             this.autoDestroy.assign(true);
          }));
       }

       @LegacySignature(type = Type.CONSTRUCTOR, parameters = {
                @LegacyParameter(name = "p1", type = "INT64", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
       public void __core_Memptr_constructor__(final int64 _p1)
       {
          int64 p1 = TypeFactory.initInput(_p1);

          internalProcedure(this, "__core_Memptr_constructor__", new Block((Body) () ->
          {
             Assert.isZeroOrPositive(p1, new character("Size"));

             __core_Memptr_constructor__();

             this.value.setLength(p1);
          }));
       }

       @LegacySignature(type = Type.CONSTRUCTOR, parameters = {
                @LegacyParameter(name = "p1", type = "INT64", mode = "INPUT"),
                @LegacyParameter(name = "p2", type = "INT64", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
       public void __core_Memptr_constructor__(final int64 _p1, final int64 _p2)
       {
          int64 p1 = TypeFactory.initInput(_p1);
          int64 p2 = TypeFactory.initInput(_p2);

          internalProcedure(this, "__core_Memptr_constructor__", new Block((Body) () ->
          {
             Assert.isZeroOrPositive(p2, new character("Size"));

             __core_Memptr_constructor__();

             this.value.setPointerValue(p1);
             this.value.setLength(p2);

             this.externalAllocation.assign(true);
          }));
       }

       @LegacySignature(type = Type.CONSTRUCTOR, parameters = {
                @LegacyParameter(name = "p1", type = "MEMPTR", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
       public void __core_Memptr_constructor__(final memptr _p1)
       {
          memptr p1 = TypeFactory.initInput(_p1);

          internalProcedure(this, "__core_Memptr_constructor__", new Block((Body) () ->
          {
             __core_Memptr_constructor__(BinaryData.length(p1));
             this.value.assign(p1);
          }));
       }

       @LegacySignature(type = Type.CONSTRUCTOR, parameters = {
                @LegacyParameter(name = "p1", type = "RAW", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
       public void __core_Memptr_constructor__(final raw _p1)
       {
          raw p1 = TypeFactory.initInput(_p1);

          internalProcedure(this, "__core_Memptr_constructor__", new Block((Body) () ->
          {
             __core_Memptr_constructor__(BinaryData.length(p1));
             this.value.assign(p1);
          }));
       }

       /**
        * Destructor for this class.  Cleanup/delete any created resources.
        */
       @LegacySignature(type = Type.DESTRUCTOR)
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
       public void __core_Memptr_destructor__()
       {
          internalProcedure(this, "__core_Memptr_destructor__", new Block((Body) () ->
          {
             if (!value.isUnknown() && autoDestroy.booleanValue())
             {
                if (externalAllocation.booleanValue())
                   value.setPointerValue(0);

                if (BinaryData.length(value).longValue() > 0)
                   value.setLength(0);
             }
          }));
       }

       @LegacySignature(type = Type.METHOD, name = "_Debug")
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_STUB)
       public void debug_1()
       {
          internalProcedure(this, "_Debug", new Block((Body) () ->
          {
             String debugFile = EnvironmentOps.getTempDirectory().getValue()
                      + toLegacyString().getValue() + ".bin";

             FileStream fs = null;

             try
             {
                fs = new FileStream(debugFile, true, false);

                for (long i = 1; i <= this.value.length().longValue(); i++)
                {
                   long bit = this.value.getByte(i).longValue();

                   String line = String.valueOf(bit) + "\t";

                   if (bit != 10 && bit != 13)
                      line += "\t\t" + character.chr(bit).getValue();

                   fs.write(line + Stream.NEWLINE);
                }

             } catch (IOException e) {
                // TODO: this should probably be handled in FileStream anyway
             } finally {
                fs.close();
             }

          }));
       }

       @LegacySignature(type = Type.METHOD, name = "Clear")
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
       public void clear_()
       {
          internalProcedure(this, "Clear", new Block((Body) () ->
          {
             if (!externalAllocation.booleanValue())
             {
                int64 size = this.value.length();

                if (size.longValue() > 0)
                {
                   this.value.setLength(0);
                   this.value.setLength(size);
                }
             }
          }));
       }

       @LegacySignature(returns = "INTEGER", type = Type.METHOD, name = "GetByte", parameters = {
                @LegacyParameter(name = "p1", type = "INT64", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
       public integer getByte(final int64 _p1)
       {
          int64 p1 = TypeFactory.initInput(_p1);

          return function(this, "GetByte", integer.class, new Block((Body) () ->
          {
             Assert.isPositive(p1, new character("Position"));

             returnNormal(this.value.getByte(p1));
          }));
       }

       @LegacySignature(returns = "OBJECT", type = Type.METHOD, name = "GetBytes", qualified = "openedge.core.memptr", parameters = {
                @LegacyParameter(name = "p1", type = "INT64", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
       public object<? extends com.goldencode.p2j.oo.core.Memptr> getBytes(final int64 _p1)
       {
          int64 p1 = TypeFactory.initInput(_p1);

          return function(this, "GetBytes", object.class, new Block((Body) () ->
          {
             Assert.isPositive(p1, new character("Start position"));
             returnNormal(getBytes(p1, minus(getSize(), p1)));
          }));
       }

       @LegacySignature(returns = "OBJECT", type = Type.METHOD, name = "GetBytes", qualified = "openedge.core.memptr", parameters = {
                @LegacyParameter(name = "p1", type = "INT64", mode = "INPUT"),
                @LegacyParameter(name = "p2", type = "INT64", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
       public object<? extends com.goldencode.p2j.oo.core.Memptr> getBytes(final int64 _p1,
                final int64 _p2)
       {
          int64 p1 = TypeFactory.initInput(_p1);
          int64 p2 = TypeFactory.initInput(_p2);

          memptr mpData = TypeFactory.memptr();

          return function(this, "GetBytes", object.class, new Block((Body) () ->
          {
             Assert.isPositive(p1, new character("Start position"));
             Assert.isPositive(p2, new character("Slice size"));

             if (getSize().intValue() == 0)
                returnNormal(getEmpty());
             else
             {
                mpData.assign(this.value.getBytes(p1, p2));
                returnNormal(ObjectOps.newInstance(Memptr.class, "I", mpData));
             }
          }, (Fini) () ->
          {
             mpData.setLength(0);
          }));
       }

       @LegacySignature(returns = "RAW", type = Type.METHOD, name = "GetHash")
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_STUB)
       public raw getHash()
       {
          return function(this, "GetHash", raw.class, new Block((Body) () ->
          {
             returnNormal(getHash(HashAlgorithmEnum.md5));
          }));
       }

       @LegacySignature(returns = "RAW", type = Type.METHOD, name = "GetHash", parameters = {
                @LegacyParameter(name = "p1", type = "OBJECT", qualified = "openedge.core.hashalgorithmenum", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_STUB)
       public raw getHash(final object<? extends HashAlgorithmEnum> _p1)
       {
          Assert.notNull(_p1, new character("Algorithm"));

          object<? extends HashAlgorithmEnum> p1 = TypeFactory
                   .initInput(_p1);

          return function(this, "GetHash", raw.class, new Block((Body) () ->
          {
             returnNormal(SecurityOps.messageDigest(p1.ref().toLegacyString(), value));
          }));
       }

       @LegacySignature(returns = "INT64", type = Type.METHOD, name = "GetPointerValue")
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
       public int64 getPointerValue()
       {
          return function(this, "GetPointerValue", int64.class, new Block((Body) () ->
          {
             returnNormal(this.value.getPointerValue());
          }));
       }

       @LegacySignature(returns = "LONGCHAR", type = Type.METHOD, name = "GetString", parameters = {
                @LegacyParameter(name = "p1", type = "INT64", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
       public longchar getString(final int64 _p1)
       {
          return getString(_p1, plus(minus(getSize(), _p1), 1));
       }

       @LegacySignature(returns = "LONGCHAR", type = Type.METHOD, name = "GetString", parameters = {
                @LegacyParameter(name = "p1", type = "INT64", mode = "INPUT"),
                @LegacyParameter(name = "p2", type = "INT64", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
       public longchar getString(final int64 _p1, final int64 _p2)
       {
          return getString(_p1, _p2, I18nOps.getCPInternal(), new character("UTF-8"));
       }

       @LegacySignature(returns = "LONGCHAR", type = Type.METHOD, name = "GetString", parameters = {
                @LegacyParameter(name = "p1", type = "INT64", mode = "INPUT"),
                @LegacyParameter(name = "p2", type = "INT64", mode = "INPUT"),
                @LegacyParameter(name = "p3", type = "CHARACTER", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_BASIC)
       public longchar getString(final int64 _p1, final int64 _p2, final character _p3)
       {
          return getString(_p1, _p2, I18nOps.getCPInternal(), _p3);
       }

       @LegacySignature(returns = "LONGCHAR", type = Type.METHOD, name = "GetString", parameters = {
                @LegacyParameter(name = "p1", type = "INT64", mode = "INPUT"),
                @LegacyParameter(name = "p2", type = "INT64", mode = "INPUT"),
                @LegacyParameter(name = "p3", type = "CHARACTER", mode = "INPUT"),
                @LegacyParameter(name = "p4", type = "CHARACTER", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_BASIC)
       public longchar getString(final int64 _p1, final int64 _p2, final character _p3,
                final character _p4)
       {
          int64 startPos = TypeFactory.initInput(_p1);
          int64 sliceSize = TypeFactory.initInput(_p2);
          character sourceCodepage = TypeFactory.initInput(_p3);
          character targetCodepage = TypeFactory.initInput(_p4);
          longchar ch = TypeFactory.longchar();

          return function(this, "GetString", longchar.class, new Block((Body) () ->
          {
             Assert.isPositive(startPos, new character("Start position"));
             Assert.isZeroOrPositive(sliceSize, new character("Slice size"));
             Assert.notNullOrEmpty(sourceCodepage, new character("Source codepage"));
             Assert.notNullOrEmpty(targetCodepage, new character("Target codepage"));

             // if length is zero the code page is not set - empty string but still copy-lob validation take place
             if (sliceSize.longValue() > 0)
                ch.fixCodePage(targetCodepage);

             new LobCopy(new SourceLob(this.value).length(sliceSize).offset(startPos),
                      new TargetLob(ch)).targetCodePage(targetCodepage).sourceCodePage(sourceCodepage)
                               .run();

             returnNormal(ch);
          }));
       }

       @LegacySignature(type = Type.METHOD, name = "PutBytes", parameters = {
                @LegacyParameter(name = "p1", type = "INT64", mode = "INPUT"),
                @LegacyParameter(name = "p2", type = "INT64", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
       public void putBytes(final int64 _p1, final int64 _p2)
       {
          int64 p1 = TypeFactory.initInput(_p1);
          int64 p2 = TypeFactory.initInput(_p2);
          memptr mtemp = TypeFactory.memptr();

          internalProcedure(this, "PutBytes", new Block((Body) () ->
          {
             Assert.isZeroOrPositive(p1, new character("Pointer"));
             Assert.isZeroOrPositive(p2, new character("Size"));

             long size = getSize().longValue();

             if (size != 0 && p2.longValue() > size)
             {
                undoThrowTopLevel(AppError.newInstance("Input memptr larger than available size", 0));
             }
             else
             {
                try
                {
                   if (size == 0)
                   {
                      setSize(p2);
                   }

                   mtemp.setPointerValue(p1.longValue());
                   mtemp.setLength(p2);

                   this.value.setBytes(mtemp, 1);
                }
                finally
                {
                   mtemp.setPointerValue(0);
                   if (mtemp.length().longValue() > 0)
                      mtemp.setLength(0);
                }

             }
          }));
       }

       @LegacySignature(type = Type.METHOD, name = "PutBytes", parameters = {
                @LegacyParameter(name = "p1", type = "MEMPTR", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
       public void putBytes(final memptr _p1)
       {
          putBytes(_p1.getPointerValue(), _p1.length());
       }

       @LegacySignature(type = Type.METHOD, name = "PutBytes", parameters = {
                @LegacyParameter(name = "p1", type = "RAW", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
       public void putBytes(final raw _p1)
       {
          raw p1 = TypeFactory.initInput(_p1);
          memptr mptemp = TypeFactory.memptr();

          internalProcedure(this, "PutBytes", new Block((Body) () ->
          {
             try
             {
                mptemp.setLength(p1.length());
                mptemp.setBytes(p1, 1);
                putBytes(mptemp.getPointerValue(), mptemp.length());
             }
             finally
             {
                if (mptemp.length().longValue() > 0)
                   mptemp.setLength(0);
             }
          }));
       }

       @LegacySignature(returns = "CHARACTER", type = Type.METHOD, name = "ToString")
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
       @Override
       public character toLegacyString()
       {
          return TextOps.substitute("OpenEdge.Core.Memptr_&1", this.value.getPointerValue());
       }
    }