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