# comhandle.java

    /*
    ** Module   : comhandle.java
    ** Abstract : Progress 4GL compatible COM handle object
    **
    ** Copyright (c) 2010-2023, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- ---------------------------------Description---------------------------------
    ** 001 GES 20101005 Placeholder version with some stubs for attributes and methods.
    ** 002 SIY 20101014 Changed signatures.
    ** 003 OM  20170523 Added new methods and completed implementation of existing ones.
    ** 004 CA  20171026 Changes for native COM automation support.
    ** 005 EVL 20171101 Fix for COM object handle reading with client-server exchange.
    ** 006 GES 20171207 Removed explicit bypass on pending error (new silent error mode).
    ** 007 CA  20180517 Fixed comhandle FWD-specific resource management.
    ** 008 OM  20120223 Added support for indexed properties. Added ref-counter.
    ** 009 VVT 20190915 method and property access re-designed, erroneous NAME property handling fixed.
    ** 010 MAG 20191211 Fixed NPE raised while unboxing Long null value to primitive long.
    ** 011 VVT 20200107 The comhandle(Object value) now accepts the null argument with no complaint
    ** 012 HC  20200313 Javadoc fixes.
    ** 013 VVT 20200916 showInvalidAccessError() now calls recordOrShowError() instead of displayError().
    **                  See #4887.
    ** 013 IAS 20201007 Added Type enum
    ** 014 HC  20211201 Added support for runtime conversion of converted OCX object references to
    **                  comhandle.
    **     AL2 20220319 Added value proxy check in assign.
    **     HC  20220414 comhandle may be constructed from a handle of a converted OCX object.
    ** 015 CA  20230215 'instantiateDefault' now is implemented by each sub-class.  Added unknown.UNKNOWN
    **                  singleton (used by FWD runtime at this time).
    **     CA  20230215 'duplicate()' method returns the real type instead of BDT.
    ** 016 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
    ** 017 ES  20230905 Added modifiyResourceIfNotPresent and createPOJOComObjectResource methods
    **     ES  20230905 Change mapping of POJOComObject after the id of its wrapped object
    ** 018 CA  20231007 Fixed a NPE when assigning to an invalid handle.
    ** 019 AL2 20250530 Added getIndependentFromContext.
    */

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

    package com.goldencode.p2j.util;

    import com.goldencode.p2j.comauto.*;
    import com.goldencode.p2j.security.*;
    import com.goldencode.p2j.util.logging.*;

    import java.io.*;
    import java.util.*;
    import java.util.logging.*;

    /**
     * A class that represents a Progress 4GL compatible COM handle data type. It provides a mechanism
     * to group the functionality that is specific to a COM handle in Progress.
     */
    public class comhandle
    extends BaseDataType
    {
       /** Logger */
       private static final CentralLogger LOG = CentralLogger.get(handle.class.getName());

       /** Stores context-local state variables. */
       private static final ContextLocal<WorkArea> work =
       new ContextLocal<WorkArea>()
       {
          @Override
          public WeightFactor getWeight()
          {
             return WeightFactor.LAST;
          };

          @Override
          protected synchronized WorkArea initialValue()
          {
             return new WorkArea();
          }
       };

       /** The empty parameter marker for property access and method calls. */
       public static final Object EMPTY = new Object();

       /** The object stored by this special handle. */
       protected ComObject value = null;

       /**
        * Special handler that shows an invalid access error when a attribute or method member is
        * accessed for an unknown/invalid handle.
        */
       private final ControlFrameComObject invalidAccessHandler = new ControlFrameComObject()
       {
          /**
           * Shows an invalid access error.
           *
           * @param   ocx
           *          Ignored.
           * @param   ctrl
           *          Ignored.
           *
           * @return  always {@code unknown}.
           */
          @Override
          public logical loadControls(String ocx, String ctrl)
          {
             showInvalidAccessError("LoadControls");
             return new logical();
          }

          /**
           * Shows an invalid access error.
           *
           * @return  always {@code unknown}.
           */
          @Override
          public comhandle getControlName()
          {
             showInvalidAccessError("CONTROL-NAME");
             return new comhandle();
          }

          /**
           * Shows an invalid access error.
           *
           * @return  always {@code unknown}.
           */
          @Override
          public comhandle getControls()
          {
             showInvalidAccessError("CONTROLS");
             return new comhandle();
          }

          /**
           * Shows an invalid access error.
           *
           * @return  always {@code unknown}.
           */
          @Override
          public handle getWidgetHandle()
          {
             showInvalidAccessError("WIDGET-HANDLE");
             return new handle();
          }

          /**
           * Shows an invalid access error.
           *
           * @return  always {@code unknown}.
           */
          @Override
          public integer getWidth()
          {
             showInvalidAccessError("WIDTH");
             return new integer();
          }

          /**
           * Shows an invalid access error.
           *
           * @param   width
           *          Ignored.
           */
          @Override
          public void setWidth(long width)
          {
             showInvalidAccessError("WIDTH");
          }

          /**
           * Shows an invalid access error.
           *
           * @param   width
           *          Ignored.
           */
          @Override
          public void setWidth(NumberType width)
          {
             showInvalidAccessError("WIDTH");
          }

          /**
           * Shows an invalid access error.
           *
           * @return  always {@code unknown}.
           */
          @Override
          public integer getHeight()
          {
             showInvalidAccessError("HEIGHT");
             return new integer();
          }

          /**
           * Shows an invalid access error.
           *
           * @param   height
           *          Ignored.
           */
          @Override
          public void setHeight(long height)
          {
             showInvalidAccessError("HEIGHT");
          }

          /**
           * Shows an invalid access error.
           *
           * @param   height
           *          Ignored.
           */
          @Override
          public void setHeight(NumberType height)
          {
             showInvalidAccessError("HEIGHT");
          }

          /**
           * Shows an invalid access error.
           *
           * @return  always {@code unknown}.
           */
          @Override
          public integer getLeft()
          {
             showInvalidAccessError("LEFT");
             return new integer();
          }

          /**
           * Shows an invalid access error.
           *
           * @param   left
           *          Ignored.
           */
          @Override
          public void setLeft(long left)
          {
             showInvalidAccessError("LEFT");
          }

          /**
           * Shows an invalid access error.
           *
           * @param   left
           *          Ignored.
           */
          @Override
          public void setLeft(NumberType left)
          {
             showInvalidAccessError("LEFT");
          }

          /**
           * Shows an invalid access error.
           *
           * @return  always {@code unknown}.
           */
          @Override
          public integer getTop()
          {
             showInvalidAccessError("TOP");
             return new integer();
          }

          /**
           * Shows an invalid access error.
           *
           * @param   top
           *          Ignored.
           */
          @Override
          public void setTop(long top)
          {
             showInvalidAccessError("LEFT");
          }

          /**
           * Shows an invalid access error.
           *
           * @param   top
           *          Ignored.
           */
          @Override
          public void setTop(NumberType top)
          {
             showInvalidAccessError("LEFT");
          }

          /**
           * Shows an invalid access error.
           *
           * @return  always {@code unknown}.
           */
          @Override
          public integer getWidgetId()
          {
             showInvalidAccessError("WIDGET-ID");
             return new integer();
          }

          /**
           * Shows an invalid access error.
           *
           * @return  always {@code unknown}.
           */
          @Override
          public handle getFrameHandle()
          {
             showInvalidAccessError("FRAME");
             return new handle();
          }

          /**
           * Shows an invalid access error.
           *
           * @return  always {@code unknown}.
           */
          @Override
          public comhandle getActiveX(String axName)
          {
             showInvalidAccessError(axName);
             return new comhandle();
          }

          /**
           * Shows an invalid access error.
           *
           * @return  always {@code unknown}.
           */
          @Override
          public character name()
          {
             showInvalidAccessError("NAME");
             return new character();
          }

          /**
           * Shows an invalid access error.
           *
           * @param   name
           *          Ignored.
           */
          @Override
          public void name(String name)
          {
             showInvalidAccessError("NAME");
          }

          /**
           * Shows an invalid access error.
           *
           * @param   name
           *          Ignored.
           */
          @Override
          public void name(character name)
          {
             showInvalidAccessError("NAME");
          }
       };

       /**
        * Default constructor, creates an instance that represents the unknown value.
        */
       public comhandle()
       {
       }

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

       /**
        * Constructs an instance that has the contained object reference and {@code unknown} that
        * exactly matches that of the passed-in instance.
        *
        * @param    that
        *           The instance to pattern from.
        */
       public comhandle(comhandle that)
       {
          // the value may be a proxy; make sure to unwrap and work with the real value
          if (BaseDataType.isProxy(that))
          {
             if (that.val() instanceof comhandle)
             {
                that = (comhandle) that.val();
             }
             else
             {
                initialize(that.val());
                return;
             }
          }
          if (that.isUnknown())
          {
             this.setUnknown();
          }
          else
          {
             _setValue(that.value);
          }
       }

       /**
        * Constructs an instance that wraps the given object reference as a {@link WrappedResource}.
        *
        * @param    value
        *           The instance to contain.
        */
       public comhandle(Object value)
       {
          if (value instanceof BaseDataType)
          {
             // the value may be a proxy; make sure to unwrap and work with the real value
             value = value == null ? null : ((BaseDataType) value).val();
          }
          if (value instanceof comhandle)
          {
             comhandle that = (comhandle) value;
             if (that.isUnknown())
             {
                this.setUnknown();
             }
             else
             {
                _setValue(that.value);
             }
          }
          else if (value instanceof ComObject)
          {
             _setValue((ComObject) value);
          }
          else if (value instanceof handle)
          {
             if (((handle) value)._isValid())
             {
                // this may be a converted OCX object
                _setValue(createPOJOComObjectResource(value));
             }
             else
             {
                setUnknown();
             }
          }
          else if (value == null)
          {
             setUnknown();
          }
          else
          {
             setUnknown();
             LOG.severe("Invalid construct!");
          }
       }

       /**
        * Convert the string representation of a comhandle into the original comhandle instance.
        * This facility can only work for comhandles that have been converted at least once into a
        * string via {@link #toString}. Such comhandles are stored in a map for conversion purposes.
        *
        * @param    txt
        *           A valid text representation of a comhandle ID.
        *
        * @return   The converted comhandle instance.
        */
       public static comhandle fromString(String txt)
       {
          Long id = ResourceIdHelper.idFromString(txt);
          if (ResourceIdHelper.isUnknown(id))
          {
             return new comhandle();
          }
          else if (ResourceIdHelper.isZero(id))
          {
             return new comhandle(ZERO_RESOURCE);
          }

          return fromResourceId(id);
       }

       /**
        * Converts the number representation of a handle into the original handle instance.
        * <p>
        * For handles <em>whose backing resources still exist</em> and which have been cached by
        * their IDs via {@link #resourceId(ComObject)} (which is called at various points in
        * the handle's life cycle), the method returns a valid handle instance. For handles whose
        * backing resources no longer exist, or which have never had their backing resources cached,
        * the method returns a zero handle, i.e. handle with "0" string representation.
        *
        * @param   resourceId
        *          Resource identifier.
        *
        * @return  The converted comhandle instance.
        */
       public static comhandle fromResourceId(Long resourceId)
       {
          WorkArea wa = work.get();
          WrappedResource resource = wa.idResources.get(resourceId);

          if (resource != null)
          {
             return new comhandle(resource);
          }

          return new comhandle(ZERO_RESOURCE);
       }

       /**
        * Get the ID of the given resource, as used by the {@link #toString} and
        * {@link #fromString} APIs.
        * <p>
        * The method allocates additional resources together with the provided wrapped resource,
        * thus it is necessary that {@link #removeResource(ComObject)} is called when
        * the returned resource id is not needed anymore (i.e. the related resource is deleted),
        * see {@link #removeResource(ComObject)} for more details.
        *
        * @param    resource
        *           A resource instance.
        *
        * @return   The ID of this resource.
        */
       public static Long resourceId(ComObject resource)
       {
          if (resource == ZERO_RESOURCE)
          {
             return 0L;
          }

          Long existingId = resource.id();

          // resource id already set at the resource?
          if (existingId != null)
          {
             return existingId;
          }

          WorkArea wa = work.get();

          // resource id already allocated?
          existingId = wa.resourceIds.get(resource);
          if (existingId != null)
          {
             // yes, save it at the resource, too
             resource.id(existingId);

             return existingId;
          }

          if (!resource.valid())
          {
             if (LOG.isLoggable(Level.FINE))
             {
                LOG.log(Level.FINE,
                        "You are creating a new ID for an invalid resource " +
                        resource.getClass().getName() + "!",
                        new Exception());
             }
             else if (LOG.isLoggable(Level.WARNING))
             {
                LOG.log(Level.WARNING,
                        "You are creating a new ID for an invalid resource " +
                        resource.getClass().getName() + "! (enable FINE logging for a stacktrace)");
             }
          }

          // allocate new resource id - collision with existing ids must be prevented
          Long id = null;
          do
          {
             id = ResourceIdHelper.getNext();
          }
          while (wa.idResources.containsKey(id));

          wa.idResources.put(id, resource);
          wa.resourceIds.put(resource, id);

          // save it at the resource, too
          resource.id(id);

          return id;
       }

       /**
        * Remove the given resource from the context-local map of all resource.
        * <p>
        * This method should be called by all com objects resources when released,
        * that is all resources which are sub-classes of {@link ComObject}.
        *
        * @param    resource
        *           A resource instance to be removed.
        */
       public static void removeResource(ComObject resource)
       {
          if (resource == null)
          {
             return;
          }

          WorkArea wa = work.get();
          Long id = wa.resourceIds.remove(resource);
          if (id != null)
          {
             wa.idResources.remove(id);
          }
       }

       /**
        * Modify the contained data of the instance to copy the instance data from the given instance.
        * Generally, the class of the given instance must match the class upon which the
        * {@code assign} method is called, although this is not a strict requirement.
        *
        * @param   newVal
        *          The backup instance from which to copy data.
        */
       @Override
       public void assign(Undoable newVal)
       {
          if (newVal instanceof comhandle)
          {
             _setValue(((comhandle) newVal).value);
          }
          else if (newVal instanceof ComObject)
          {
             _setValue((ComObject) newVal);
          }
          else if (newVal instanceof handle)
          {
             _setValue(modifiyResourceIfNotPresent(((handle) newVal).getResource()));
          }
          else
          {
             LOG.severe("Invalid construct!");
          }
       }

       /**
        * Dedicated assignment method for loading a {@code ComObject} into this {@code comhandle}.
        * This method is only called by {@link ComServer#create(String, comhandle)}. Stores a newly
        * created {@code ComObject} into this handle.
        *
        * @param   newVal
        *          The new value to be stored by this handle.
        */
       public void assign(ComObject newVal)
       {
          // TODO: check undoable ?
          _setValue(newVal);
       }

       /**
        * Sets the state (data and unknown value) of this instance based on the state of the passed
        * instance.
        *
        * @param    from
        *           The instance from which to copy state.
        */
       @Override
       public void assign(BaseDataType from)
       {
          if (from == null)
          {
             setUnknown();
             return;
          }

          // the value may be a proxy; make sure to unwrap and work with the real value
          from = from.val();
          if (from.isUnknown())
          {
             setUnknown();
          }
          else if (from instanceof comhandle)
          {
             comhandle that = (comhandle) from;
             _setValue(that.value);
          }
          else if (from instanceof handle)
          {
             // special case when ZERO_RESOURCE is passed in, convert it to local ZERO_RESOURCE
             WrappedResource res = ((handle) from).getResource();
             if (res != null && Long.valueOf(0).equals(res.id()))
             {
                _setValue(ZERO_RESOURCE);
             }
             else
             {
                _setValue(new POJOComObject(((handle) from).get()));
             }
          }
          else
          {
             String err = "Incompatible data types in expression or assignment.";
             ErrorManager.recordOrThrowError(223, err);
          }
       }

       /**
        * {@inheritDoc}
        */
       @Override
       public void assign(Object value)
       {
          if (value instanceof WrappedResource)
          {
             assign(modifiyResourceIfNotPresent((WrappedResource) value));
          }
          else
          {
             super.assign(value);
          }
       }

       /**
        * Determines if this instance is valid (FWD internal access).
        *
        * @return  boolean {@code true} if this instance can be used.
        */
       public boolean _isValid()
       {
          return (value != null) && value.valid();
       }

       /**
        * Determines if this instance is valid.
        *
        * @return  {@code true} if this instance can be used.
        */
       public logical isValid()
       {
          return new logical(_isValid());
       }

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

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

       /**
        * 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 comhandle duplicate()
       {
          return new comhandle(this);
       }

       /**
        * Reports if this instance represents the {@code unknown}.
        *
        * @return   {@code true} if this instance is set to the {@code unknown} value.
        */
       @Override
       public boolean isUnknown()
       {
          return value == null;
       }

       /**
        * Custom implementation of {@link Object#hashCode}.
        *
        * @return  a hashcode for current object.
        */
       @Override
       public int hashCode()
       {
          if (isUnknown()) // Ie. value == null
          {
             return ResourceIdHelper.UNKNOWN_STRING.hashCode();
          }

          return value.hashCode();
       }

       /**
        * Sets the state of this instance's {@code unknown value} flag to {@code true}.
        * <p>
        * <b>Warning: a separate call is needed to ensure that the data of this instance is set to the
        * correct value.</b>
        */
       @Override
       public void setUnknown()
       {
          if (!isUnknown())
          {
             checkUndoable(true);
          }

          _setValue(null);
       }

       /**
        * 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   0 1 or -1 depending on if the passed instance is or is not {@code unknown}.
        */
       @Override
       public int compareTo(Object obj)
       {
          // handle is also a BDT type
          if (obj instanceof comhandle)
          {
             comhandle that = (comhandle) obj;

             // two unknown values are equals
             if (isUnknown() || that.isUnknown())
             {
                // an unknown handle is greater than a known handle
                return Boolean.compare(isUnknown(), that.isUnknown());
             }

             return (comhandle.resourceId(this.value) == comhandle.resourceId(that.value)) ? 0 : 1;
          }

          return -1;
       }

       /**
        * Creates a string representation of the instance data using the format
        * {@link ResourceIdHelper#DEFAULT_FORMAT}. If the instance represents the {@code unknown}
        * value, a '?' will be returned.
        *
        * @return  The formatted string.
        */
       @Override
       public String toString()
       {
          return toString(null);
       }

       /**
        * Creates a string representation of the instance data using the user specified format string
        * (Progress 4GL compatible) or if no such string is provided, the default format of
        * {@link ResourceIdHelper#DEFAULT_FORMAT}. If the instance represents the {@code unknown}
        * value, a '?' will be returned.
        *
        * @param   fmt
        *          The Progress 4GL format string or {@code null} if the default format is to be used.
        *
        * @return  The formatted string.
        */
       @Override
       public String toString(String fmt)
       {
          if (isUnknown())
          {
             return ResourceIdHelper.UNKNOWN_STRING;
          }

          Long id = comhandle.resourceId(value);

          return ResourceIdHelper.idToString(fmt, id);
       }

       /**
        * Obtain the legacy name of this COM object.
        *
        * @return  the name of this COM object.
        */
       public character getName()
       {
          return value == null ? new character() : new character(value.getName());
       }

       /**
        * Sets the legacy name of this COM object.
        *
        * @param   newName
        *          The new legacy name of this COM object.
        */
       public void setName(character newName)
       {
          if (value != null)
          {
             value.setName(newName.toJavaType());
          }
       }

       /**
        * Creates a string representation of the instance data in a form that is compatible with the
        * {@code MESSAGE} language statement.  If the instance represents the {@code unknown value},
        * a '?' will be returned.
        *
        * @return  The 'message' formatted string.
        */
       @Override
       public String toStringMessage()
       {
          return toString();
       }

       /**
        * Creates a string representation of the instance data using the 'export' format.  If the
        * instance represents the {@code unknown value}, a '?' will be returned.
        *
        * @return   The 'export' formatted string.
        */
       @Override
       public String toStringExport()
       {
          return toString();
       }

       /**
        * Return the default display format string for this type.
        * <p>
        * Warning: this is larger than the Progress version because the range of handle values can be
        * a 64-bit integer in this implementation.
        *
        * @return   The default format string.
        */
       @Override
       public String defaultFormatString()
       {
          return ResourceIdHelper.DEFAULT_FORMAT;
       }

       /**
        * Replacement for the default object reading method. The latest state is read from the input
        * source.
        *
        * @param    in
        *           The input source from which fields will be restored.
        *
        * @throws   IOException
        *           In case of I/O errors.
        * @throws   ClassNotFoundException
        *           If payload can't be instantiated.
        */
       @Override
       public void readExternal(ObjectInput in)
       throws IOException,
              ClassNotFoundException
       {
          // if value is not null we use generic reader
          if (value != null)
          {
             if (!(value instanceof Externalizable))
             {
                throw new NotSerializableException("comhandle");
             }

             ((Externalizable) value).readExternal(in);
          }
          else
          {
             // there are the cases when we need to read the object before we can
             // decide if it is Externalizable or not, assuming to get native COM
             _setValue((ComObject) in.readObject());
          }
       }

       /**
        * Replacement for the default object writing method. The latest state is written to the output
        * destination.
        *
        * @param    out
        *           The output destination to which fields will be saved.
        *
        * @throws   IOException
        *           In case of I/O errors.
        */
       @Override
       public void writeExternal(ObjectOutput out)
       throws IOException
       {
          if (value != null && !(value instanceof Externalizable))
          {
             throw new NotSerializableException("comhandle");
          }

          // the object is Externalizable or null
          out.writeObject(value);
       }

       /**
        * Unwrap this com-handle to the {@link ControlFrameComObject} instance of the parent
        * {@link ControlFrame}. If the comhandle is not obtained from a Control Frame using
        * {@code COM-HANDLE} attribute handle or is invalid, a special {@link ControlFrameComObject}
        * is returned that handles invalid accesses.
        *
        * @return  See above.
        */
       public ControlFrameComObject unwrapComObject()
       {
          // to the best effort to identify the object
          if (value instanceof ControlFrameComObject)
          {
             return (ControlFrameComObject) value;
          }

          return invalidAccessHandler;
       }

       /**
        * Sets a COM property for the COM object stored by the handle.
        *
        * @param   prop
        *          The legacy property name. Case insensitive. If no property is found then a warning
        *          is displayed and method returns without altering the stored object.
        * @param   newVal
        *          The new value for the property. It must be of a compatible type with the property.
        */
       public void setProperty(String prop, Object newVal)
       {
          setIndexedProperty(prop, newVal);
       }

       /**
        * Sets a COM property for the COM object stored by the handle, with arbitrary indices.
        *
        * @param   prop
        *          The legacy property name. Case insensitive. If no property is found then a warning
        *          is displayed and method returns without altering the stored object.
        * @param   newVal
        *          The new value for the property. It must be of a compatible type with the property.
        * @param   indices
        *          A variable number of indices used to access this property's element.
        */
       public /*final*/ void setIndexedProperty(String prop, Object newVal, Object... indices)
       {
          if (value == null)
          {
             showInvalidAccessError(prop.toUpperCase());
             return;
          }

          try
          {
             if (!value.setProperty(prop, newVal, indices))
             {
                showAccessError(prop.toUpperCase(), "0x80020006", "Unknown name.");
             }
          }
          catch (PropertyAccessException pae)
          {
             showAccessError(prop.toUpperCase(), pae.getErrorCode(), pae.getMessage());
          }
       }

       /**
        * Obtain the COM object held by this comhandle.
        *
        * @return  the COM object held by this comhandle.
        */
       public ComObject getResource()
       {
          return value;
       }

       /**
        * Obtain a property value from the COM object stored in this handle.
        *
        * @param   prop
        *          The legacy name of the property. Case insensitive. If no property is found then a
        *          warning is displayed and method returns {@code unknown} value.
        *
        * @return  The value of the requested property or {@code unknown} on exceptions.
        */
       public /*final*/ BaseDataType getProperty(String prop)
       {
          if (value != null
              && (value instanceof ControlFrameComObject)
              && value.getActivexName().equalsIgnoreCase(prop)
              )
          {
             return new comhandle(value);
          }

          return getIndexedProperty(prop);
       }

       /**
        * Obtain a property value from the COM object stored in this handle.
        *
        * @param   prop
        *          The legacy name of the property. Case insensitive. If no property is found then a
        *          warning is displayed and method returns {@code unknown} value.
        * @param   indices
        *          A variable number of indices used to access this property's element.
        *
        * @return  The value of the requested property or {@code unknown} on exceptions.
        */
       public /*final*/ BaseDataType getIndexedProperty(String prop, Object... indices)
       {
          if (value == null)
          {
             showInvalidAccessError(prop.toUpperCase());
             return unknown.UNKNOWN;
          }

          return value.getProperty(prop, indices);
       }

       /**
        * Chains access to COM property/methods. This call is generated when a chained construct is
        * encountered in legacy code. For example, {@code ch:prop1:prop2:prop-or-method} will be
        * converted to something like: {@code ch.chain("prop1").chain("prop2").get("prop-or-method")}
        *
        * @param   property
        *          The property to access. It should normally be a com-object property of the object
        *          stored in this {@code comhandle}.
        *
        * @return  A {@code comhandle} to the COM object property of currently referred resource. It
        *          normally should be chained with another access for a property of method.
        */
       public comhandle chain(String property)
       {
          return new comhandle(getProperty(property));
       }

       /**
        * Calls a COM-method on the COM-object stored in this com-handle.
        *
        * @param   methodName
        *          The method name. Case insensitive. If the COM-object does not declare such method
        *          an error message is displayed and this method returns {@code unknown} value.
        * @param   params
        *          The list of actual parameters. Their types must be compatible with the parameters
        *          of called method.
        *          TODO: marshal to BDT (?) When code is generated [param] is already BDT.
        *
        * @return  The result of the called method, if any.
        */
       public BaseDataType call(String methodName, Object... params)
       {
          if (value == null)
          {
             showInvalidAccessError(methodName.toUpperCase());
             //what is the error
             return unknown.UNKNOWN;
          }

          return value.call(methodName, params);
       }

       /**
        * Calls a COM-method without parameters on the COM-object stored in this com-handle.
        *
        * @param   methodName
        *          The method name. Case insensitive. If the COM-object does not declare such method
        *          an error message is displayed and this method returns {@code unknown} value.
        * @param   params
        *          The list of actual parameters. Their types must be compatible with the parameters
        *          of called method.
        *          TODO: marshal to BDT (?) When code is generated [param] is already BDT.
        *
        * @return  The result of the called method, in a {@code comhandle}, so it can be used in a
        *          chained call or property access.
        */
       public comhandle chainCall(String methodName, Object... params)
       {
          if (value == null)
          {
             showInvalidAccessError(methodName.toUpperCase());
             //what is the error
             return new comhandle();
          }

          return value.chainCall(methodName, params);
       }

       /**
        * Shows a standard error for accessing an property of method of a com object stored in a
        * {@code com-handle}.
        *
        * @param   prop
        *          The legacy name of the property/method being accessed.
        * @param   errCode
        *          Error code.
        * @param   errMsg
        *          Error message.
        */
       public static void showAccessError(String prop, String errCode, String errMsg)
       {
          String msg = "Error occurred while accessing component property/method: " +
                       prop + ".\n" + errMsg + "\nError code: " + errCode + " " +
                       ProcedureManager.thisProcedure().unwrap().name().toStringMessage();
          ErrorManager.displayError(5890, msg, false);
       }

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

       /**
        * Shows a standard error for accessing an invalid {@code com-handle}.
        *
        * @param   prop
        *          The legacy name of the property or method being accessed.
        */
       private static void showInvalidAccessError(String prop)
       {
          String msg = "Invalid component-handle referenced while processing method/statement: " +
                       prop + ".\n\n" + ProcedureManager.thisProcedure().unwrap().name().toStringMessage();
          ErrorManager.recordOrShowError(5884, msg, false);
       }

       /**
        * Updates the current value. Takes care to update the refcount for both of them.
        *
        * @param   val
        *          The new value to be assigned.
        */
       private void _setValue(ComObject val)
       {
          if (value == val)
          {
             return; // no-op
          }

          if (value != null && value.valid())
          {
             value.deref();
          }

          value = val;

          if (val != null && value.valid())
          {
             value.ref();
          }
       }

       /**
        * Create a new POJOComObject, if not already present in the idResource to ComObject mapping  .
        *
        * @param   value
        *          Handle to be wrapped inside a POJOComObject.
        *
        * @return  A new POJOComObject if handle not present in a mapping, or existing POJOComObject
        *          for given {@value}.
        */
       private POJOComObject createPOJOComObjectResource(Object value)
       {
          WorkArea wa = work.get();

          Long id = handle.resourceId(((handle) value).getResource());
          ComObject pojo = wa.idResources.get(id);
          if (pojo == null)
          {
             pojo = new POJOComObject(((handle) value).getResource());
             wa.idResources.put(id, pojo);
             wa.resourceIds.put(pojo, id);
          }

          return (POJOComObject) pojo;
       }

       /**
        * Change the wrapped value of a comhandle, if @newVlaue not already present in the mapping,
        * also deletes the old value from the mapping.
        *
        * @param   newValue
        *          A wrapped resource with an id.
        *
        * @return  Newly created POJOComObject if @newValue not present in the mapping,
        *          or old of @this otherwise.
        */
       private POJOComObject modifiyResourceIfNotPresent(WrappedResource newValue)
       {
          WorkArea wa = work.get();

          Long oldId = null;
          if (value != null)
          {
             oldId = comhandle.resourceId(value);
          }
          Long newId = handle.resourceId(newValue);

          ComObject pojo = wa.idResources.get(newId);
          if (pojo == null)
          {
             pojo = new POJOComObject((newValue));
             wa.idResources.put(newId, pojo);
             wa.resourceIds.put(pojo, newId);

             if (value != null)
             {
                wa.resourceIds.remove(value);
                wa.idResources.remove(oldId);
             }

             return (POJOComObject) pojo;
          }

          return (POJOComObject) pojo;
       }

       /**
        * A "zero" pseudo-resource. A "zero" resource is used for handles created by the
        * {@link handle#fromString(String)} method when a formally valid ID is provided, but the ID
        * doesn't denote a valid resource.
        * <p>
        * A zero resource is not valid but is not unknown.
        */
       public static final ComObject ZERO_RESOURCE = new ComObject()
       {
          @Override
          protected void init()
          {
             // disable any initialization
          }

          @Override
          public boolean valid()
          {
             return false;
          }

          @Override
          public boolean unknown()
          {
             return false;
          }

          @Override
          public Long id()
          {
             return 0L;
          }

          @Override
          public void id(long id)
          {
             // no-op
          }

          @Override
          public String getActivexName()
          {
             return null;
          }

          @Override
          public void delete()
          {
          }
       };

       /**
        * Stores global data relating to the state of the current context.
        */
       private static class WorkArea
       {
          /**
           * Stores comhandles that have been converted to strings and may need to be converted back
           * into a comhandle.
           */
          private Map<Long, ComObject> idResources = new HashMap<>();

          /**
           * Map of resources and their resource ids. Resource ids are tracked here because they are
           * naturally related to comhandles.
           */
          private Map<ComObject, Long> resourceIds = new /*Weak*/HashMap<>();
       }
    }