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