memptr.java

/*
** Module   : memptr.java
** Abstract : Progress 4GL compatible memptr data type
**
** Copyright (c) 2005-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- -----------------------------------Description-----------------------------------
** 001 GES 20050906   @22564 Created initial version with support for a subset of memptr 
**                           processing.
** 002 GES 20060422   @25654 Added a copy constructor.
** 003 GES 20060724   @28172 Use forced assign in copy constructor.
** 004 GES 20060807   @28476 Moved to externalizable interface to optimize network performance.
** 005 CA  20130123          Added c'tor which builds a new instance from a BaseDataType value,
**                           if possible. This should be used only when constructing an instance
**                           using the value returned by a DYNAMIC-FUNCTION call; 4GL does some
**                           special casting in this case, which is not implemented yet.
** 006 GES 20130322          Fixed the BDT c'tor.
** 007 GES 20130523          Major rewrite to back this using a native memory buffer instead of
**                           a byte[] on the Java heap. Added missing features.  Many deviations
**                           from the 4GL were found and fixed. The actual data access and
**                           storage is now in this class instead of in the parent, and this is
**                           exposed via a set of abstract worker methods that we implement here.
** 008 CA  20130916          On P2J Client side, use the memory daemon directly; this allows 
**                           access to memptr instances from P2J Client side.
** 009 GES 20131212          Made isUninitialized() public.
** 010 MAG 20140602          Fixed the BDT c'tor and modified the assign(BDT,boolean) on _POLY
**                           morphing. Add compareTo(Object).
** 011 CA  20140811          Fixed clearing of unknown flag in assignment/output param.
** 012 ECF 20150824          Implemented context cleanup processing to deallocate memory not
**                           previously freed explicitly.
**     CA  20150827          In memptr c'tors, if a memptr instance is passed, just copy its 
**                           pointer.
** 013 OM  20160705          Replaced deep copy with pointer copy and reference counter. Removed
**                           negative allocation on WIN platform. Fixed toStringMessage() method.
** 014 CA  20160629          memptr vars can never be undoable.
** 015 OM  20160831          Optimized context-local accesses. Javadoc updates.
** 016 GES 20171207          Removed forced version of assign().
** 017 CA  20180214          Replaced workArea with lazy-initialization, as when transferred via
**                           the socket, the Reader thread can't access context-local state
**     GES 20180322          Added compiledWordSize().
** 018 ECF 20181214          Implemented LargeObject interface.
** 019 HC  20190302          Implemented emulation of GetWindowRect and GetClientRect Windows API
**                           functions.
** 020 IAS 20190617          Changed the signature of the writeByteRange method
** 021 ECF 20190628          Implemented COPY-LOB runtime.
** 022 CA  20191203          Fixed overlay and unknown COPY-LOB related bugs.
** 023 IAS 20201007          Added Type enum.
**     OM  20210404          Fine-tuned errors generated by edge-case in copy-lob statements.
**     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.
**     CA  20210818          Fixed memptr parameter runtime - they share the 'pointer structure' with the 
**                           passed argument.
**     CA  20210823          A fix for CA/20210818 - assignment of a parameter to a non-unknown memptr will
**                           clear the 'srcWasUnknown' flag. 
**     CA  20220217          If assign(memptr) is called for a memptr referencing the same address, do nothing.
**     AL2 20220328          Added proxy checks for BDT.
**     CA  20220515          Allow library and memptr calls to be executed on server-side.
**     CA  20220920          A fix for CA/20220217 - assign to an unknown memptr must set this instance to 
**                           unknown, even if both their null address (0) matches.
**     CA  20220922          The address space must always use a positive size (negative sizes are allowed on
**                           linux).
**     CA  20221020          'checkAddressSpace' must ignore 0 address number. 
**     CA  20221025          Added a protection against NegativeArraySizeException in clearAddressSpace.
**     EVL 20221025          Small fix for message construction with two long values summing results.
** 024 CA  20230215          'instantiateDefault' now is implemented by each sub-class.
**     CA  20230215          'duplicate()' method returns the real type instead of BDT.
** 025 GBB 20230512          Logging methods replaced by CentralLogger/ConversionStatus.
** 026 CA  20230918          Cleanup the memory allocations only if on server-side or if the FWD client is 
**                           still running.
** 027 CA  20231017          Fixed 'writeByteRange' to consider the given length (number of bytes) to write.
** 028 OM  20231001          Avoid deprecated APIs.
** 029 GBB 20240826          Moving OSResourceManager to osresource package.
** 030 GBB 20240912          OSResourceManager method isServerSide to use an enum param.
** 031 GBB 20250403          asByteArray made public to be accessed from CoreAppserver.
** 032 ICP 20250328          Made writeByte, readByteRange, and writeByteRange public to enable
**                           external direct memory manipulation (e.g., from MemoryOutputStream).
*/

/*
** 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.util.logging.*;

import com.goldencode.p2j.util.logging.*;
import com.goldencode.p2j.util.osresource.*;
import org.roaringbitmap.longlong.*;

import com.goldencode.p2j.net.*;
import com.goldencode.p2j.security.*;

/**
 * Implements the unique behavior of a Progress 4GL compatible {@code MEMPTR} data type
 * which is backed by a native platform memory buffer (instead of being allocated on the Java
 * heap). The contents of the buffer are mutable.  All Progress 4GL language features are
 * supported up to the 10.2B level.
 * <p>
 * Format string related processing is handled in method {@link #toString(String)}.
 * <p>
 * The following is the mapping of Progress language features to the corresponding feature in
 * this class:
 * <p>
 * <pre>
 * Progress Function               Java Method
 * -----------------------------   -----------------------
 * get-byte-order                  {@link #getByteOrder}
 * get-pointer-value               {@link #getPointerValue}
 * get-size                        {@link #length}
 *
 * Progress Language Statement     Java Method
 * -----------------------------   -----------------------
 * set-byte-order                  {@link #setByteOrder}
 * set-pointer-value               {@link #setPointerValue}
 * set-size                        {@link #setLength}
 * </pre> 
 * <p>
 * All of the above features are specific to the {@code MEMPTR} type (they do not work for
 * {@code RAW} variables in the 4GL, such attempted usage will cause a compiler error).  All
 * other features are shared and are implemented in the common base class.
 */
public class memptr
extends BinaryData
implements LargeObject
{
   /** Logger */
   private static final CentralLogger LOG = CentralLogger.get(memptr.class.getName());
   
   /** No byte order has been explicitly requested. This is the default state of a memptr. */
   public static final int NO_BYTE_ORDER = 0;
   
   /** Operations should use the native byte order on the current machine. */
   public static final int HOST_BYTE_ORDER = 1;
   
   /** Operations should use big endian byte order. */
   public static final int BIG_ENDIAN = 2;
   
   /** Operations should use little endian byte order. */
   public static final int LITTLE_ENDIAN = 3;
   
   /** Smallest value accepted in SET-SIZE() without generating an error. */
   public static final int LOWER_BOUND = -2147483648;
   
   /** Largest value accepted in SET-SIZE() without generating an error. */
   public static final int UPPER_BOUND = 2147483647;
   
   /** Stores context local instances of the remote memory manager proxy. */
   private static final ContextLocal<WorkArea> ctxt = new ContextLocal<WorkArea>()
   {
      @Override
      protected WorkArea initialValue()
      {
         WorkArea wa = new WorkArea();
         
         wa.serverSide = OSResourceManager.getInstance().isServerSide(OsResourceType.MEMPTR);
         wa.bufMgr = OSResourceManager.getMemptr();
         
         wa.nativeByteOrder = wa.bufMgr.isLittleEndian() ? LITTLE_ENDIAN : BIG_ENDIAN;
         wa.compiledWordSize = wa.bufMgr.compiledWordSize();
         
         wa.winOS = EnvironmentOps.isUnderWindowsFamily();
         
         return wa;
      }
      
      @Override
      protected void cleanup(WorkArea wa)
      {
         try
         {
            if (wa.serverSide || SessionManager.get().getSession().isRunning())
            {
               // cleanup only if the allocation is done on server-side or if the FWD client is still running
               // (if it has terminated, the heap will be de-allocated along with the JVM process).
               wa.bufMgr.cleanup();
            }
         }
         catch (SilentUnwindException exc)
         {
            // this will occur in cases where the cleanup is triggered from the server due to
            // the client being disconnected; we don't want to unwind and disrupt other cleanup,
            // so just log and eat the exception
            if (LOG.isLoggable(Level.FINE))
            {
               LOG.log(Level.FINE, "Error cleaning up memptr context", exc);
            }
         }
      }
   };
   
   /**
    * The working area where this instance was created. Caches costly access to context local.
    * Lazy-initialized in {@link #locate()} as it can't be initialized by the c'tor - when
    * transported via the network, the Reader thread will not be able to access the context.
    */
   private WorkArea workArea;
   
   /** Pointer structure shared between parameters and arguments. */
   private memptrPointer pointer = new memptrPointer(NO_BYTE_ORDER, 0, 0);

   /** Flag indicating this is a parameter. */
   private boolean parameter = false;
   
   /** Flag indicating if the source argument for this parameter was unknown. */
   private boolean srcWasUnknown = false;
   
   /**
    * Default constructor, creates an instance that represents an uninitialized byte array (a 
    * {@code null} array).
    */
   public memptr()
   {
   }
   
   /**
    * 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 (see {@link #assign(BaseDataType)}).
    * 
    * @param    value
    *           The value to be used for this instance.
    */
   public memptr(BaseDataType value)
   {
      this(); // initialize winOS
      
      if (value == null)
      {
         setUnknown();
         return;
      }
      
      // the value may be a proxy; make sure to unwrap and work with the real value
      value = value.val();
      
      if (value.isUnknown())
      {
         setUnknown();
      }
      else
      {
         if (value instanceof memptr)
         {
            copyPointer((memptr) value);
         }
         else
         {
            assign(value);
         }
      }
   }
   
   /**
    * Constructs an instance after copying the parameter's data into the internal representation
    * of this class.
    * <p>
    * If the parameter is {@code unknown}, this instance will be set to {@code unknown}.
    *
    * @param   value
    *          The value to be used for this instance.
    */
   public memptr(memptr value)
   {
      this(); // init win_os
      // the value may be a proxy; make sure to unwrap and work with the real value
      if (BaseDataType.isProxy(value))
      {
         if (value.val() instanceof memptr)
         {
            value = (memptr) value.val();
         }
         else
         {
            initialize(value);
            return;
         }
      }
      copyPointer(value);
   }
   
   /**
    * Constructs an instance after copying the parameter's data into the internal representation
    * of this class.
    * <p>
    * If the parameter is {@code null}, the instance will be {@code unknown}.
    *
    * @param   value
    *          The value to be used for this instance.
    */
   public memptr(byte[] value)
   {
      this(); // init win_os
      assign(value);
   }
   
   
   /**
    * Creates a new instance of the same type that represents {@code unknown}.
    *
    * @return   An instance that represents {@code unknown}.
    */
   public static memptr instantiateUnknownMemptr()
   {
      memptr m = new memptr();
      m.setUnknown();
      
      return m;
   }
   
   /**
    * Get the type
    * @return type
    */
   @Override
   public Type getType()
   {
      return Type.MEMPTR;
   }

   /**
    * 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} interface.
    * <p>
    * The algorithm will fail to give meaningful results in the case where one tries to sort
    * against other objects that do not represent compatible values.
    *
    * @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}.
    */
   @Override
   public int compareTo(Object obj)
   {
      if (obj instanceof BaseDataType)
      {
         return super.compareTo(new memptr((BaseDataType) obj));
      }
      
      return super.compareTo(obj);
   }
   
   /**
    * Does the same as standard {@code clone()} method but returns an instance of 
    * {@code BaseDataType} and doesn't throw the {@code CloneNotSupportedException}.
    *
    * @return  A clone of this instance.
    */
   @Override
   public memptr duplicate()
   {
      memptr m = new memptr();
      
      // copy reference of the instance data
      m.copyPointer(this);
      
      return m;
   }
   
   /**
    * Creates a new instance of the same type that is {@code unknown}.
    *
    * @return  An instance that represents {@code unknown}.
    */
   @Override
   public BaseDataType instantiateUnknown()
   {
      memptr m = new memptr();
      m.setUnknown();
      
      return m;
   }
   
   /**
    * 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 new memptr();
   }

   /**
    * Reports if this instance represents <code>unknown</code>.
    *
    * @return   <code>true</code> if this instance is set to the
    *           <code>unknown</code>.
    */
   @Override
   public boolean isUnknown()
   {
      return unknown || pointer.unknown;
   }
   
   /**
    * Sets the state of this instance's <code>unknown</code> flag to <code>true</code>.
    * <p>
    * <b>Warning: the data stored in this instance WILL BE deallocated after calling this
    * method.</b>
    */
   @Override
   public void setUnknown()
   {
      super.setUnknown();
      this.pointer.unknown = true;
   }

   /**
    * Method implementation of the GET-POINTER-VALUE 4GL built-in function which returns a 64-bit 
    * integer value representing the pointer used by the instance.  This is a real memory address
    * for the first byte of the buffer that has been allocated.  Of course, this is not only very
    * dangerous, but it is also not portable.  Yet, this is how the 4GL does it.
    * 
    * @return   The 64-bit integer value representing the location in memory of the instance's
    *           buffer.  If {@code unknown}, the pointer will be {@code unknown}. If
    *           uninitialized, this will return 0.
    */
   public int64 getPointerValue()
   {
      // unknown takes precedence over being uninitialized 
      if (isUnknown())
      {
         return new int64();
      }
      
      return new int64(pointer.addr);
   }
   
   /**
    * Sets the binary data to point to a memory address. The current size is left unchanged
    * and any current buffer is NOT deallocated. This is just how the 4GL implements it.
    * <p>
    * If the instance is {@code unknown}, then this call silently fails.  It does NOT change the
    * pointer value, the instance will still be {@code unknown}.  This is how the 4GL does it.
    *
    * @param    ptr
    *           The 64-bit value representing the pointer to the memory address. This may be
    *           any value (negative, 0 or a positive number). Since the 4GL has no unsigned
    *           64-bit data type, it is necessary negative numbers be supported since memory
    *           addresses are typically unsigned values. Even though most systems do not
    *           allocate valid memory at address 0 (so that 0 can be used as a well known
    *           invalid memory address), the 4GL does allow 0 to be used (perhaps it is needed
    *           on some systems).  If this value is {@code unknown}, the method will silently
    *           return with no change to the pointer value.  This is how the 4GL does it.
    *
    * @return   The current instance.
    */
   public memptr setPointerValue(NumberType ptr)
   {
      if (ptr == null || ptr.isUnknown())
      {
         // no error here, just silently return
         return this;
      }
      
      return setPointerValue(ptr.longValue());
   }
   
   /**
    * Sets the binary data to point to a memory address. The current size is left unchanged
    * and any current buffer is NOT deallocated. This is just how the 4GL implements it.
    * <p>
    * If the instance is {@code unknown}, then this call silently fails.  It does NOT change the 
    * pointer value, the instance will still be {@code unknown}.  This is how the 4GL does it.
    *
    * @param    ptr
    *           The 64-bit value representing the pointer to the memory address. This may be
    *           any value (negative, 0 or a positive number). Since the 4GL has no unsigned
    *           64-bit data type, it is necessary negative numbers be supported since memory
    *           addresses are typically unsigned values. Even though most systems do not
    *           allocate valid memory at address 0 (so that 0 can be used as a well known
    *           invalid memory address), the 4GL does allow 0 to be used (perhaps it is needed
    *           on some systems).
    *
    * @return   The current instance.
    */
   public memptr setPointerValue(long ptr)
   {
      if (isUnknown())
      {
         // no error here, no change to the state at all, just silently return
         return this;
      }
      
      // protect this so only this context's memory addresses can be referred by the pointer
      checkAddressSpace(ptr);
      
      // all other values are accepted and saved off
      pointer.addr = ptr;
      
      return this;
   }
   
   /**
    * Reports the byte order mode configured in this instance.  By default (and contrary to the
    * Progress documentation), this is set to 0.  This will remain 0 until {@link #setByteOrder}
    * is called.  Since {@code setByteOrder} can only ever be called with 3 values, those
    * are the only possible values that can be returned from here.  The only exception to this
    * is that when the instance is {@code unknown}, then {@code unknown} will be
    * returned, no matter what has been previously configured.
    * <p>
    * The following are the possible return values:
    * <p>
    * <pre>
    * 4GL Constant        Java Constant       Value
    * ------------------  ------------------  ---------
    * n/a                 NO_BYTE_ORDER       0
    * HOST-BYTE-ORDER     HOST_BYTE_ORDER     1
    * BIG-ENDIAN          BIG_ENDIAN          2
    * LITTLE-ENDIAN       LITTLE_ENDIAN       3
    * </pre>
    * <p>
    * The fact that Progress returns 0 by default allows one can tell when this value has been
    * explicitly configured, since it is never possible to set it back to 0 via
    * {@code setByteOrder}.  When set to 0 or to HOST_BYTE_ORDER, the result is the same.
    * The native byte ordering of the current platform will be used.
    * <p>
    * This value is interesting in another way: it does not change when an instance is
    * de-allocated, re-allocated, set to {@code unknown} or assigned.  The configuration
    * value stays with the instance across all these events and thus it is not really
    * associated with a specific memory buffer, but rather with the variable instance itself.
    * Don't be fooled by the fact that this returns {@code unknown} when the instance is
    * {@code unknown}.  The byte order is still properly set (or defaulted) and when the
    * instance is no longer {@code unknown}, that value will be returned.
    * 
    * @return   The byte-order setting of the instance.
    */
   public integer getByteOrder()
   {
      if (isUnknown())
      {
         return new integer();
      }
      
      return new integer(pointer.byteOrder);
   }

   /**
    * Defines the byte ordering scheme to use when reading/writing certain data types into the
    * memory buffer.  The following data types change behavior depending on byte ordering:
    * <p>
    * <pre>
    * DOUBLE
    * FLOAT
    * INT64
    * LONG
    * SHORT
    * UNSIGNED-LONG
    * UNSIGNED-SHORT
    * </pre>
    * <p>
    * For each of the above listed data types, both the get and the set will honor the byte
    * ordering.  This means that the only data types that do NOT honor byte ordering are
    * BYTE (a single byte being read or written), BYTES (a sequence of bytes) and STRING (a
    * sequence of characters).
    * <p>
    * Any valid value set through this method is retained for the lifetime of the instance,
    * even when it is set to {@code unknown} and/or de-allocated and then re-allocated.
    * Interestingly, the value does not get copied to the target instance during assignment,
    * so it is instance-specific.  This is all compatible with how the 4GL works.
    * <p>
    * The following are the only possible byte order modes that can be specified:
    * <p>
    * <pre>
    * 4GL Constant        Java Constant       Value
    * ------------------  ------------------  ---------
    * HOST-BYTE-ORDER     HOST_BYTE_ORDER     1
    * BIG-ENDIAN          BIG_ENDIAN          2
    * LITTLE-ENDIAN       LITTLE_ENDIAN       3
    * </pre>
    * <p>
    * This method returns without any changes if the instance is {@code unknown}.
    * 
    * @param    order
    *           The new byte order mode.
    *
    * @return   The current instance is returned.
    */
   public memptr setByteOrder(int order)
   {
      if (isUnknown())
      {
         return this;
      }
      
      if (order == HOST_BYTE_ORDER || order == BIG_ENDIAN || order == LITTLE_ENDIAN)
      {
         pointer.byteOrder = order;
      }
      else
      {
         ErrorManager.recordOrThrowError(9259, "Invalid value for SET-BYTE-ORDER.");
      }
      return this;
   }

   /**
    * Defines the byte ordering scheme to use when reading/writing certain data types into the
    * memory buffer.  The following data types change behavior depending on byte ordering:
    * <p>
    * <pre>
    * DOUBLE
    * FLOAT
    * INT64
    * LONG
    * SHORT
    * UNSIGNED-LONG
    * UNSIGNED-SHORT
    * </pre>
    * <p>
    * For each of the above listed data types, both the get and the set will honor the byte
    * ordering.  This means that the only data types that do NOT honor byte ordering are
    * BYTE (a single byte being read or written), BYTES (a sequence of bytes) and STRING (a
    * sequence of characters).
    * <p>
    * Any valid value set through this method is retained for the lifetime of the instance,
    * even when it is set to {@code unknown} and/or de-allocated and then re-allocated.
    * Interestingly, the value does not get copied to the target instance during assignment,
    * so it is instance-specific.  This is all compatible with how the 4GL works.
    * <p>
    * The following are the only possible byte order modes that can be specified:
    * <p>
    * <pre>
    * 4GL Constant        Java Constant       Value
    * ------------------  ------------------  ---------
    * HOST-BYTE-ORDER     HOST_BYTE_ORDER     1
    * BIG-ENDIAN          BIG_ENDIAN          2
    * LITTLE-ENDIAN       LITTLE_ENDIAN       3
    * </pre>
    * <p>
    * If the parameter is {@code unknown}, then the instance itself will be set to
    * {@code unknown} and the byte order will NOT be changed.  Subsequent allocation of
    * a new buffer will retain the same byte order setting.  This is how the 4GL does it.
    * <p>
    * This method returns without any changes if the instance is {@code unknown}.
    * 
    * @param    order
    *           The new byte order mode.
    */
   public void setByteOrder(NumberType order)
   {
      if (order == null || order.isUnknown())
      {
         setUnknown();
      }
      else
      {
         setByteOrder(order.intValue());
      }
   }
   
   /**
    * Sets the array to a given length.  If set to 0, this de-allocates any currently allocated
    * array (if no array is allocated, this does nothing).  If set to a positive number, this will
    * allocate a new array if no array is allocated, otherwise this method will do nothing.
    * <p>
    * This method will try to emulate the allocation based on the current (-ly configured) OS. 
    * See {@link EnvironmentOps#isUnderWindowsFamily()}:
    * <ul>
    *    <li>On {@code WIN32} platform will only accept positive values when allocating a new
    *          native memory buffer. If the buffer is already allocated and a negative value is
    *          passed then this is a no-op;</li>
    *    <li>On {@code UNIX} will accept and apparently allocate {@code abs(len)} bytes.</li>
    * </ul>
    * <p>
    * On 32-bit Linux platforms, the 4GL "real lower bound" is -7 and not 0 as one would expect.
    * Since this is an obscure and platform-specific nonsense behavior which is unlikely to be
    * needed in a real application, it is not honored at this time (which leaves this code
    * simpler).
    *
    * @param    len
    *           New length of the array.
    *
    * @return   The current instance.
    */
   @Override
   public BinaryData setLength(long len)
   throws ErrorConditionException
   {
      if (parameter && srcWasUnknown)
      {
         genUninitializedError();
         return this;
      }
      
      if (isUnknown() && len == 0)
      {
         // if 'unknown' is set and 'len' is zero, do nothing.
         return this;
      }
      
      // generate an error if the size is out of bounds, this must occur before the test for
      // being uninitialized
      RangeCheck.check(len, LOWER_BOUND, UPPER_BOUND);
      
      WorkArea wa = locate();
      
      // are we already initialized?
      if (isUninitialized())
      {
         if (len != 0 && ((wa.winOS && len > 0) || !wa.winOS))
         {
            pointer.addr = wa.bufMgr.allocate(Math.abs(len)); // len may be negative only on linux
            
            // we save the size IF and ONLY IF the allocation succeeded
            if (pointer.addr != 0)
            {
               // remember that size may be negative here, that is OK (yes, the 4GL actually
               // reports the negative size on Linux, while on Windows the reported value is
               // different, see the visibleLength() method)
               pointer.size = len;
               clearUnknown();
               recordAddressSpace(pointer.addr, pointer.size);
            }
         }
         // if len is negative on windows, nothing happens
      }
      else
      {
         // this is a de-allocation request
         if (len == 0)
         {
            deallocate();
         }
         else
         {
            // if we are already initialized but the size was never set (this happens in the case
            // that setPointerValue() was used), then this call is supposed to store the size for
            // subsequent boundary checking purposes; once we set this value, we don't allow it
            // to be changed again without deallocating first
            if (pointer.size == 0)
            {
               // this can be any non-zero value that can fit in an int (we already have range
               // checked this, so we know there is no loss of data);
               pointer.size = len;
            }
         }
      }
      
      return this;
   }
   
   /**
    * Reports if this instance represents an uninitialized buffer. Being uninitialized is not the
    * same as being of zero length.  Rather it means that the buffer was never allocated. Being
    * {@code unknown} is also different from being uninitialized.
    *
    * @return  {@code true} if this instance is uninitialized.
    */
   @Override
   public boolean isUninitialized()
   {
      return (pointer.addr == 0);
   }
   
   /**
    * Creates a string representation of the instance data, since a memptr represents a 'pointer'
    * it doesn't have a real string representation. If the instance represents {@code unknown}, a
    * '?' will be returned, otherwise the empty string will be returned.
    *
    * @return   The empty string or '?' if {@code unknown}.
    */
   @Override
   public String toString()
   {
      return toString(null);
   }
   
   /**
    * Creates a string representation of the instance data using the given format string, since a
    * memptr represents a 'pointer' it doesn't have a real string representation and the format
    * string is meaningless. If the instance represents {@code unknown}, a '?' will be returned,
    * otherwise the empty string will be returned.
    *
    * @param    fmt
    *           The format string to use.
    *
    * @return   The empty string or '?' if {@code unknown}.
    */
   @Override
   public String toString(String fmt)
   {
      if (isUnknown() || isUninitialized())
      {
         return "?";
      }
      
      return getString(1).toStringMessage();
   }
   
   /**
    * Creates a string representation of the instance data in a form that is compatible with the
    * {@code MESSAGE} language statement.  If the instance represents {@code unknown}, a '?' will
    * be returned.
    * <p>
    * This is equivalent to {@link #toString}.
    *
    * @return   The empty string or '?' if {@code unknown}.
    */
   @Override
   public String toStringMessage()
   {
      return toString();
   }
   
   /**
    * Creates a string representation of the instance data using the 'export' format.  If the
    * instance represents {@code unknown}, a '?' will be returned.  Any other data is directly
    * output into the string and no formatting or conversion of binary data is done.
    *
    * @return   The binary data in the instance as a string.
    */
   @Override
   public String toStringExport()
   {
      if (isUnknown())
      {
         return "?";
      }
      
      return new String(readByteRange(0, findNextNull(0), false));
   }   
      
   /**
    * Replacement for the default object reading method. The latest state is read from the input
    * source.  The parent's state is read first.
    * 
    * @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);
      pointer.byteOrder = in.readInt();
      pointer.addr      = in.readLong();
      pointer.size      = in.readLong();
   }

   /**
    * Replacement for the default object writing method. The latest state is written to the output
    * destination.  The parent's state is written first.
    * 
    * @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.writeInt(pointer.byteOrder);
      out.writeLong(pointer.addr);
      out.writeLong(pointer.size);
   }
   
   /**
    * Write the given byte 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 bytes beyond the end of the
    *     write are left in place, regardless of the value of {@code trim});</li>
    * <li>raises an error 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, byte[] data, int offset, boolean trim)
   {
      int dataLen;
      
      if (data == null || (dataLen = data.length) == 0)
      {
         if (!overlay)
         {
            assign(data);
         }
         // else nothing to write; leave existing state alone
         
         return;
      }
      else if (overlay && isUninitialized())
      {
         ErrorManager.recordOrThrowError(11393);
         // MEMPTR target of COPY-LOB must be initialized. (11393)
         
         return;
      }
      
      if (pointer.size < offset + dataLen)
      {
         ErrorManager.recordOrThrowError(11394);
         // MEMPTR target of COPY-LOB is not big enough. (11394)
         
         return;
      }
      
      // simple case: just replace existing value with data
      if (!overlay || isUnknown() || (offset == 0 && pointer.size == dataLen))
      {
         assign(data);
         
         return;
      }
      
      // complex case: overlay data over existing data, leave any remaining, original data intact
      writeByteRange(data, offset, dataLen, false);
   }
   
   /**
    * Indicate whether this object manages character data (as opposed to binary data).
    * 
    * @return  {@code false}.
    */
   @Override
   public boolean isCharacterData()
   {
      return false;
   }
   
   /**
    * Return the entire contents of the current buffer as an array. This may be a copy of the
    * data, depending on the subclass' implementation.  DO NOT MODIFY the data that is returned,
    * since it is undefined as to whether the changes will or will not be reflected in the actual
    * storage.
    * 
    * @param   pos
    *          Starting offset position.
    * @param   len
    *          Length of range to return.
    * 
    * @return  The contents of the instance (possibly a copy). 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)
   {
      byte[] copy = null;
      
      if (!isUnknown())
      {
         if (isUninitialized())
         {
            copy = new byte[0];
         }
         else
         {
            if (pointer.size >= 1)
            {
               copy = locate().bufMgr.read(pointer.addr, pos, Math.min(pointer.size, len));
            }
         }
      }
      
      return copy;
   }
   
   /**
    * Sets the state (data and unknown value) of this instance based on the state of the passed
    * instance.
    * <p>
    * If the given instance is <code>null</code> or <code>unknown</code>, the instance will be
    * set to <code>unknown</code>.  If the length of the given data is 0 OR if the length of the
    * an initialized buffer is indeterminate, then the instance will be reset as uninitialized
    * (including deallocation of any current buffer). If the length of the array is greater than
    * 0, then any current buffer will be deallocated, a new buffer of the proper size will be
    * allocated and the data will be copied in. This is how the 4GL does it.
    *
    * @param    data
    *           The instance from which to copy state.
    */
   @Override
   public void assign(BinaryData data)
   {
      if (BaseDataType.isProxy(data))
      {
         assign(data.val());
         return;
      }
      
      if (data instanceof memptr)
      {
         if (data.isUnknown())
         {
            setUnknown();
            return;
         }

         if (parameter)
         {
            srcWasUnknown = false;
            // allow assignment from here
         }
         
         if (((memptr) data).pointer.addr == pointer.addr)
         {
            // same memory address, nothing to do.
            return;
         }
      }
      
      super.assign(data);
   }
   
   /**
    * Report if the byte ordering mode of the platform is little endian.
    * 
    * @param   workArea
    *          The optional context to obtain endian information. If not provided, the current
    *          context is used.
    *
    * @return  {@code true} if the platform natively uses little endian byte ordering and
    *          {@code false} if it uses big endian.
    */
   protected static boolean isLittleEndianPlatform(WorkArea workArea)
   {
      if (workArea == null)
      {
         workArea = ctxt.get(); 
      }
      return workArea.nativeByteOrder == LITTLE_ENDIAN;
   }
   
   /**
    * Returns the word size (the size of a pointer) for the native code as compiled. This may be
    * different than the word size of the operating system in the case where 32-bit code is run
    * on a 64-bit system.
    *
    * @return   This value will be either 32 or 64 depending on how the native library was
    *           compiled. If there was an error in inspecting this value, -1 will be returned.
    */ 
   protected static int compiledWordSize()
   {
      // can't use locate() in static context
      WorkArea wa = ctxt.get();
      
      return wa.compiledWordSize;
   }
   
   /**
    * Setup this instance to act as a parameter; this shares the {@link #pointer} with the source memptr.
    * 
    * @param    src
    *           The source argument.
    */
   protected void asParameter(memptr src)
   {
      this.unknown = src.unknown;
      this.pointer = src.pointer;
      this.parameter = true;
      this.srcWasUnknown= src.isUnknown();
   }
   
   /**
    * Sets the state of this instance's <code>unknown</code> flag to <code>false</code>.
    */
   @Override
   protected void clearUnknown()
   {
      super.clearUnknown();
      this.pointer.unknown = false;
   }

   /**
    * Release any resources associated with the instance.
    */
   @Override
   protected void deallocate()
   {
      if (pointer.addr != 0)
      {
         locate().bufMgr.deallocate(pointer.addr);
         clearAddressSpace(pointer.addr, pointer.size);
         
         pointer.addr = 0;
         pointer.size = 0;
      }
   }
   
   /**
    * Determines if the array of binary data stored in an instance of this class must be limited
    * in length.
    *
    * @return    {@code true} if the length must be limited.
    */
   @Override
   protected boolean isLengthLimited()
   {
      return false;
   }
   
   /**
    * Determines the maximum length of the array of binary data stored in an instance of this
    * class.
    *
    * @return    Always 0 (this class has no pre-determined limit on size).
    */
   @Override
   protected long lengthLimit()
   {
      return 0;
   }
   
   /**
    * Determines if the array of binary data stored in an instance of this class must be
    * automatically extended in length when operations write past the current length.  Such
    * extensions will pad with {@code null} bytes.
    *
    * @return    {@code true} if the length must automatically extended.
    */
   @Override
   protected boolean isAutoExtend()
   {
      return false;
   }
   
   /**
    * Helper to create a new byte array with the same contents as the given byte array and the
    * specified length.  If the given target length is smaller than the source array length, the
    * output array is truncated to the specified size.  If the target length is larger than the
    * source array length, the output array is extended to the given size and any additional bytes
    * are initialized to '\0'.
    * <p>
    * This is safe to use on 0 length arrays.
    *
    * @param    len
    *           The output array length.
    */
   @Override
   protected void extendBytes(long len)
   {
      // nothing to do, this is only used in auto-extend sub-classes
   }
   
   /**
    * Replace the entire contents of the current buffer with the given array.
    * <p>
    * The {@link BinaryData#unknown} flag is also cleared, only if {@code value} is not 
    * {@code null} and if the length is greater than 0.
    *
    * @param    value
    *           The new contents.
    */
   @Override
   protected void replaceContents(byte[] value)
   {
      if (value == null)
      {
         setUnknown();
      }
      else
      {
         deallocate();
         
         if (value.length > 0)
         {
            // allocates memory, clears unknown and saves size
            setLength(value.length);
            
            // copy the data
            writeByteRange(value, 0, -1, false);
            
            clearUnknown();
         }
      }
   }
   
   /**
    * Return the entire contents of the current buffer as an array. This may be a copy of the
    * data, depending on the subclass' implementation.  DO NOT MODIFY the data that is returned,
    * since it is undefined as to whether the changes will or will not be reflected in the actual
    * storage.
    *
    * @return  The contents of the instance (possibly a copy). 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()
   {
      return asByteArray(0, pointer.size);
   }
   
   /**
    * The actual size of the buffer (in bytes).
    *
    * @return  The length or 0 if uninitialized.
    */
   @Override
   protected long internalLength()
   {
      return pointer.size;
   }
   
   /**
    * The the length of the buffer as reported to P4GL application.
    *
    * @return  The length or 0 if uninitialized.
    */
   @Override
   protected long visibleLength()
   {
      if (pointer.size >= 0)
      {
         return pointer.size;
      }
      
      // when negative, in linux it is reported as is, but on windows the complement is returned
      // (remember that size is limited to a 32-bit signed value in the 4GL, to return it as an
      // unsigned value in a long, we clear the most significant 4 bytes which is equivalent to
      // 0x00000000FFFFFFFF & size)
      return locate().winOS ? 0x1_0000_0000L + pointer.size : pointer.size;
   }
   
   /**
    * Returns the next index position into the array which is the {@code null} byte, starting at a
    * given index position.
    *
    * @param   pos
    *          The 0-based index into the array at which to start searching.
    *
    * @return  The next index into the array which is the {@code null} byte or the length of the
    *          array if no {@code null} byte exists.
    */
   @Override
   protected long findNextNull(long pos)
   {
      // we must pass -1 as the size if we don't know what the real size is, this means the code
      // will read until it finds a null byte or gets an access violation, this is how the 4GL
      // does it too
      long limit = (pointer.size < 1) ? -1 : pointer.size;
      
      return locate().bufMgr.findNextNull(pointer.addr, pos, limit);
   }

   /**
    * Read the byte at the given location.  All boundary checking and error handling must have
    * already been done in the caller such that this is known to be a valid access. Any
    * unexpected failure may cause a platform-specific exception that terminates the process!
    *
    * @param    pos
    *           The 0-based index position at which to read the byte.
    *
    * @return   The byte at that location.
    */
   @Override
   protected byte readByte(long pos)
   {
      byte[] subset = locate().bufMgr.read(pointer.addr, pos, 1);
      return subset[0];
   }
   
   /**
    * Write the byte into the given location. All boundary checking and error handling must have
    * already been done in the caller such that this is known to be a valid access. Any
    * unexpected failure may cause a platform-specific exception that terminates the process!
    *
    * @param    val
    *           The byte to write.
    * @param    pos
    *           The 0-based index position at which to read the byte.
    */
   @Override
   public void writeByte(byte val, long pos)
   {
      byte[] subset = new byte[1];
      subset[0] = val;
      locate().bufMgr.write(pointer.addr, pos, subset);
   }
   
   /**
    * Read the bytes at the given location.  All boundary checking and error handling must have
    * already been done in the caller such that this is known to be a valid access. Any
    * unexpected failure may cause a platform-specific exception that terminates the process!
    *
    * @param    pos
    *           The 0-based index position at which to read the bytes.
    * @param    len
    *           The number of bytes to read.
    * @param    endian
    *           {@code true} to force the returned data to be ordered by the endian-ness of the
    *           instance. 
    *
    * @return   The byte range at that location.
    */
   @Override
   public byte[] readByteRange(long pos, long len, boolean endian)
   {
      byte[] data = locate().bufMgr.read(pointer.addr, pos, len);
      
      // reverse the byte order if needed
      if (endian && !isLittleEndian())
      {
         data = reverseBytes(data);
      }
      
      return data;
   }
   
   /**
    * Write the bytes at the given location.  All boundary checking and error handling must have
    * already been done in the caller such that this is known to be a valid access. Any
    * unexpected failure may cause a platform-specific exception that terminates the process!
    *
    * @param    data
    *           The bytes to write.
    * @param    pos
    *           The 0-based index position at which to start writing the bytes.
    * @param    len
    *           How many bytes should be written. -1 means write everything to the end of the
    *           array. This is the implementation of the {@link BinaryData#writeByteRange}
    *           method. The len parameter was added to fix bug in the {@link raw#writeByteRange}.
    *           For the memptr is is intentionally ignored.
    * @param    endian
    *           {@code true} to honor the endian-ness of the instance. The {@code data}
    *           array MUST be ordered in little-endian order if the endian flag is on.
    */
   @Override
   public void writeByteRange(byte[] data, long pos, long len, boolean endian)
   {
      // reverse the byte order if needed
      if (endian && !isLittleEndian())
      {
         data = reverseBytes(data);
      }
      
      if (len > 0 && len < data.length)
      {
         byte[] aux = new byte[(int) len];
         System.arraycopy(data, 0, aux, 0, (int) len);
         data = aux;
      }
      
      locate().bufMgr.write(pointer.addr, pos, data);
   }
   
   /**
    * Check if this instance is undoable.  For {@link memptr} case, these instances can never be
    * undoable.
    * 
    * @return   Always {@code false}
    */
   @Override
   protected boolean isUndoable()
   {
      return false;
   }
   
   /**
    * Copy the specified number of bytes from the source instance into the current instance at
    * the given location.  All boundary checking and error handling must have already been
    * done in the caller such that this is known to be a valid access. Any unexpected failure
    * may cause a platform-specific exception that terminates the process!
    * <p>
    * This method is implemented completely on the client side to avoid multiple (costly) round
    * trips.
    *
    * @param    data
    *           The source of data to copy.
    * @param    pos
    *           The 0-based index position in the destination at which to write the bytes.
    * @param    len
    *           The number of bytes to write.
    */
   protected void copyByteRange(memptr data, long pos, long len)
   {
      locate().bufMgr.copy(data.pointer.addr, pointer.addr, pos, len);
   }

   /**
    * Returns the low-level memory manager.
    *
    * @return  see above.
    */
   static LowLevelBuffer getMemoryManager()
   {
      return ctxt.get().bufMgr;
   }
   
   /**
    * Report if the effective byte ordering mode is little endian.
    *
    * @return   {@code true} if the instance should use little endian byte ordering and
    *           {@code false} if the instance should use big endian.
    */
   private boolean isLittleEndian()
   {
      if (pointer.byteOrder == LITTLE_ENDIAN || pointer.byteOrder == BIG_ENDIAN)
      {
         // explicit setting should be honored
         return (pointer.byteOrder == LITTLE_ENDIAN);
      }
      else
      {
         // NO_BYTE_ORDER and HOST_BYTE_ORDER both will force the native platform
         // ordering to be used
         return isLittleEndianPlatform(locate());
      }
   }
   
   /**
    * Copy the pointer details from the specified instance.
    * 
    * @param   m
    *          The {@link memptr} instance which needs to be duplicated.
    */
   private void copyPointer(memptr m)
   {
      this.pointer.addr = m.pointer.addr;
      this.pointer.byteOrder = m.pointer.byteOrder;
      this.pointer.size = m.pointer.size;
      this.pointer.unknown = m.pointer.unknown;
      this.unknown = m.unknown;
      if (pointer.addr != 0)
      {
         locate().bufMgr.incrementReference(pointer.addr);
      }
   }
   
   /**
    * Resolve the context-local {@link #workArea} variable (if not yet resolved) and return it.
    * 
    * @return   The {@link #workArea}.
    */
   private WorkArea locate()
   {
      if (workArea == null)
      {
         workArea = ctxt.get();
      }
      
      return workArea;
   }
   
   /**
    * When memptr is on server-side, allocated address spaces are recorded for each context, to prohibit 
    * cross-context usage of pointer addresses.
    * <p>
    * This method raises an ERROR condition if the context tries to {@link #setPointerValue set a pointer} to
    * an address which was not allocated in this context.
    *  
    * @param    ptr
    *           The memory address.
    */
   private void checkAddressSpace(long ptr)
   {
      WorkArea wa = locate();
      
      if (ptr == 0 || !wa.serverSide)
      {
         return;
      }
      
      if (wa.addressSpace.contains(ptr))
      {
         return;
      }
      
      String msg = "Memory violation: trying to SET-POINTER-VALUE to an address unallocated in this context.";
      LOG.log(Level.SEVERE, msg);
      
      ErrorManager.recordOrThrowError(-100, msg, false);
   }

   /**
    * When memptr is on server-side, allocated address spaces are recorded for each context, to prohibit 
    * cross-context usage of pointer addresses.
    * <p>
    * This records the allocated address space in this context.
    *  
    * @param    ptr
    *           The memory address.
    * @param    size
    *           The allocated size.
    */
   private void recordAddressSpace(long ptr, long size)
   {
      WorkArea wa = locate();
      
      if (!wa.serverSide)
      {
         return;
      }
      
      size = Math.abs(size);
      
      wa.addressSpace.addRange(ptr, ptr + size);
   }
   
   /**
    * When memptr is on server-side, allocated address spaces are recorded for each context, to prohibit 
    * cross-context usage of pointer addresses.
    * <p>
    * This clears the allocated address space in this context.
    *  
    * @param    ptr
    *           The memory address.
    * @param    size
    *           The allocated size.
    */
   private void clearAddressSpace(long ptr, long size)
   {
      WorkArea wa = locate();
      
      if (!wa.serverSide)
      {
         return;
      }
      
      size = Math.abs(size);

      try
      {
         // create a bitmap with the address space being cleared
         wa.cleaner.addRange(ptr, ptr + size);
         // remove it from the allowed address space
         wa.addressSpace.andNot(wa.cleaner);
         
         wa.cleaner.clear();
      }
      catch (NegativeArraySizeException ex)
      {
         if (LOG.isLoggable(Level.SEVERE))
         {
            LOG.log(Level.SEVERE, 
                    "Exception while clearing address space [" + ptr + ":" + (ptr + size) + "] at " + ptr + 
                    " with size " + size, 
                    ex);
         }
      }
   }
   
   /**
    * Container for context-local data.
    */
   private static class WorkArea
   {
      /** Flag indicating if the memptr is handled on server-side. */
      private boolean serverSide;

      /** Remote proxy for access and management of memory buffers. */
      private LowLevelBuffer bufMgr = null;
      
      /** Stores the byte order of the native platform being used. */
      private int nativeByteOrder = 0;
      
      /** Stores the word size of the compiled code in the client. */
      private int compiledWordSize = -1;
      
      /** Stores the OS information for this context. */
      private boolean winOS = false;
      
      /** The allocated address space in this context. */
      private final Roaring64Bitmap addressSpace = new Roaring64Bitmap();
      
      /** 
       * A helper instance to clean the {@link #addressSpace}, when a pointer is 
       * {@link memptr#deallocate() deallocated}.
       */
      private final Roaring64Bitmap cleaner = new Roaring64Bitmap();
   }

   /**
    * A data structure with the details about the 'pointer' referenced by this instance.
    */
   private static class memptrPointer
   {
      /** The byte order used for this instance. */
      private int byteOrder;
      
      /** The actual platform-specific pointer to our memory region (on the client). */
      private long addr;
      
      /** Stores the size that was last set for querying and boundary checking. */
      private long size;

      /** Flag indicating if the instance is unknown. */
      private boolean unknown = false;
      
      /**
       * Initialize this structure with the specified details.
       * 
       * @param    byteOrder
       *           The byte order used for this instance.
       * @param    addr
       *           The actual platform-specific pointer to our memory region (on the client).
       * @param    size
       *           Stores the size that was last set for querying and boundary checking.
       */
      public memptrPointer(int byteOrder, long addr, long size)
      {
         this.byteOrder = byteOrder;
         this.addr = addr;
         this.size = size;
      }
   }
}