HandleResource.java
/*
** Module : HandleResource.java
** Abstract : Implementation of common attributes and APIs for all resources.
**
** Copyright (c) 2013-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
** 001 CA 20130221 Created initial version.
** 002 OM 20130304 Refactored isValid and isUnknown of WrappedResource to valid and unknown.
** 003 EVK 20130903 Added method type().
** 004 CA 20130927 All resources are known by default; special resources which need to be unknown
** will override the unknown() API. The resource type is determined based on the
** LegacyResource annotation: an annotation at the class has precedence over an
** annotation at the interface.
** 005 CA 20131113 Added INSTANTIATING-PROCEDURE support.
** Changes related to properly instantiate dynamic resources.
** 006 CA 20140326 Fixed implicitDeletion in case is called from an Agent, outside of an external
** procedure (when the remote persistent procedure is deleted by the Agent).
** 007 CA 20150502 The resource ID needs to be kept at the resource, so it can be accessed after
** the resource is deleted.
** Track the resources created by each procedure, as dynamic resources don't get
** deleted when the creator proc gets deleted, but their INSTANTIATING-PROCEDURE
** attr needs to be unknown.
** 008 EVL 20160224 Javadoc fixes to make compatible with Oracle Java 8 for Solaris 10.
** 009 CA 20160327 getResourceType() invokes type(), so sub-classes can override it.
** 010 CA 20171202 Allow the LegacyResource annotation to be determined from the super-class, too.
** 011 CA 20181029 Cache the legacy resource names, for each FWD class implementing a resource.
** 012 HC 20181128 Moved the relevant portion of HandleChain.delete() in this class.
** 013 CA 20190122 OBJECT pseudo-resources don't require procedure-level processing.
** 014 OM 20190417 Added unableToAssignUnknown() method.
** 015 HC 20200119 Added extension points to allow to externalize widget implementation.
** 016 CA 20200110 Implicit static resource deletion must be executed when the instantiating
** procedure gets deleted.
** 017 CA 20201011 Track the number of triggers on which this resource is listening on. On delete, it
** allows to skip unnecessary work if there is no trigger for this resource.
** OM 20201030 Invalid attribute API support for getters/setters.
** OM 20201203 Fixed handling of READ/ONLY attributes.
** CA 20220120 Small performance improvement, use setUnknown instead of set(null), to avoid undoable
** overhead.
** VVT 20220306 Uniform toString() support added.
** VVT 20220307 AbstractPrintable class eliminated. See #6013-153.
** VVT 20221003 CommonHandle.getResourceType() method renamed to resourceType() to prevent conflicts
** with namesakes in DMO. See #6694. Also, a few missing annotations added.
** CA 20221006 Cache the hash-code values. Resources can't override equals() or hashCode().
** CA 20221031 Javadoc fixes.
** CA 20220918 Use a weak reference for the INSTANTIATING-PROCEDURE, to allow the garbage collector to
** remove the references once this was deleted.
** VVT 20220913 CommonHandle.getResourceType() method renamed to resourceType() to prevent conflicts
** with namesakes in DMO. See #6694. Also, a few missing annotations added.
** CA 20230110 Cache the helper for ProcedureManager, ObjectOps and others (as needed), to reduce
** context-local lookup.
** CA 20230116 Avoid using handle.unwrap, handle.getReference or other BDT usage from within FWD runtime.
** 018 CA 20230215 Improved other cases to reduce BDT usage from within FWD runtime.
** 019 CA 20230724 Further reduce context-local usage.
** 020 CA 20231129 Directly set the INSTANTIATING-PROCEDURE referent, from the parent BUFFER's, instead of
** passing it as a handle.
** 021 CA 20240918 Removed default methods from interfaces and moved them as concrete implementations, as
** default methods cause the Java metaspace to spike one order of magnitude.
** 022 ICP 20250131 Used character.of to leverage cached instances.
*/
/*
** 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.lang.ref.*;
import java.util.*;
import java.util.concurrent.*;
import com.goldencode.p2j.ui.*;
import com.goldencode.proxy.*;
import com.goldencode.util.*;
/**
* This acts as a superclass which implements:
* <table summary="">
* <tr>
* <th>attribute or base method</th>
* <th>API</th>
* </tr>
* <tr>
* <td>TYPE</td>
* <td>{@link #resourceType()} and {@link #type()}</td>
* </tr>
* <tr>
* <td>INSTANTIATING-PROCEDURE</td>
* <td>{@link #instantiatingProcedure()}</td>
* </tr>
* <tr>
* <td>Unknown status</td>
* <td>{@link #unknown()}</td>
* </tr>
* <tr>
* <td>Read only field error</td>
* <td>{@link #readOnlyError(String, Object)}</td>
* </tr>
* <tr>
* <td>Invalid attribute access</td>
* <td>{@link #invalidAttribute(String, boolean)}</td>
* </tr>
* </table>
* <p>
* The abstract class also provides setters for the implemented attributes
* which are not defined inside the interfaces to be used for initializing of
* those attributes
*/
public abstract class HandleResource
implements Printable,
CommonHandle,
InstantiatingProcedure,
Deletable
{
/** A mapping of resource names for each FWD class implementing a resource. */
private static final Map<Class<?>, String> CLS_2_RES = new ConcurrentHashMap<>();
/** Weak reference to the instantiating procedure. */
protected WeakReference<Object> instProc;
/** Variable for holding the type attribute */
private String type = null;
/**
* The resource's ID. Remains <code>null</code> until the ID is requested via
* {@link handle#resourceId}
*/
private Long id = null;
/** The number of trigger registration (ever). It will not reflect trigger deregistration. */
private int triggerRegistration;
/** Cached hash code. */
private int hashCode;
/** Helper to use the ProcedureManager without any context local lookups. */
protected final ProcedureManager.ProcedureHelper pm = ProcedureManager.getProcedureHelper();
/**
* Default c'tor.
*/
public HandleResource()
{
this(false);
}
/**
* Initialize this resource by determining its type.
* <p>
* The resource's type is determined from a {@link LegacyResource} annotation, which is
* searched in these locations, in this order:
* <ol>
* <li>The class implementing the resource in P2J is checked for an annotation. If found, this
* is used to determine the resource type.</li>
* <li>The entire interface hierarchy is checked. There should be only one interface with a
* {@link LegacyResource} annotation; if there is more than annotated interface, then n
* {@link IllegalStateException} is thrown.</li>
* </ol>
*
* @param dynamic
* Flag indicating if this is a static or dynamic resource.
*
* @throws IllegalStateException
* If the resource type could not be determined from a {@link LegacyResource}
* annotation, after checking the interface hierarchy, or more than one interface
* is annotated.
*/
protected HandleResource(boolean dynamic)
{
this.hashCode = super.hashCode();
// check the class first
Class<?> cls = getClass();
type = CLS_2_RES.get(cls);
if (type == null)
{
LegacyResource lr = cls.getAnnotation(LegacyResource.class);
if (lr != null)
{
type = lr.resource();
}
else
{
// else, determine it from the interface. the annotated interface in most cases is a
// directly implemented interface, but ensure we reach all interfaces, to cover all
// bases
Deque<Class<?>> ifcs = new ArrayDeque<Class<?>>();
// add all the interfaces from the extended classes first
Class<?> superCls = cls;
while (superCls != null)
{
ifcs.addAll(Arrays.asList(superCls.getInterfaces()));
lr = superCls.getAnnotation(LegacyResource.class);
if (lr != null)
{
if (type != null)
{
// if more than one interface is annotated, then we are ambiguous
final String msg =
"More than one LegacyResource annotation in the extended class %s " +
"for class %s.";
throw new IllegalStateException(
String.format(msg, superCls.getName(), cls.getName()));
}
type = lr.resource();
}
superCls = superCls.getSuperclass();
}
Set<Class<?>> checked = new HashSet<Class<?>>();
while (!ifcs.isEmpty())
{
Class<?> ifc = ifcs.remove();
if (checked.contains(ifc))
continue;
// keep track of which interface we've visited, as the same interface may appear more
// than once in the hierarchy
checked.add(ifc);
lr = ifc.getAnnotation(LegacyResource.class);
if (lr != null)
{
if (type != null)
{
// if more than one interface is annotated, then we are ambiguous
final String msg =
"More than one LegacyResource annotation in the implemented interface %s " +
"for class %s.";
throw new IllegalStateException(String.format(msg, ifc.getName(), cls.getName()));
}
type = lr.resource();
}
ifcs.addAll(Arrays.asList(ifc.getInterfaces()));
}
}
if (type == null)
{
final String msg = "Could not determine the legacy resource type for class %s.";
throw new IllegalStateException(String.format(msg, cls.getName()));
}
CLS_2_RES.put(cls, type);
}
// this will ensure the INSTANTIATING-PROCEDURE attribute is setup accordingly and also that
// static resources will be registered with the correct top-level block
if (processResource() && !ProxyFactory.isProxyClass(this.getClass()))
{
// for proxy cases, explicit registration is needed
pm.processResource(this, dynamic);
}
}
/**
* Registers a new resource in the internal map of resource class to resource name.
*
* @param clazz
* Resource class.
* @param name
* Resource name.
*/
public static void registerResource(Class clazz, String name)
{
CLS_2_RES.put(clazz, name);
}
/**
* Determine whether this object is equivalent to the specified object. Forces the identity equality.
*
* @param obj
* Object to test for equality.
*
* @return {@code true} if equivalent, else {@code false}.
*/
@Override
public final boolean equals(Object obj)
{
return this == obj;
}
/**
* Return the hash code for this object. Overrides the parent's behavior to return a value which is cached
* at construction and does not change. Implemented as a performance enhancement.
*
* @return Hash code.
*/
@Override
public final int hashCode()
{
return hashCode;
}
/**
* Get this resource's ID, if is already set.
*
* @return The resource's ID or <code>null</code> if not set.
*/
@Override
public Long id()
{
return id;
}
/**
* Set this resource's ID.
*
* @param id
* The resource's ID.
*/
@Override
public void id(long id)
{
this.id = id;
}
/**
* Get the type of this handle.
*
* @return See above.
*/
@Override
public character resourceType()
{
return character.of(type());
}
/**
* A newly-created resource is always not-unknown. If implementations need to return something
* different, then override this method.
*
* @return always <code>false</code>
*/
@Override
public boolean unknown()
{
return false;
}
/**
* Show a specific error, as this read-only {@code attribute} was used on the right-side of an assignment.
*
* @param attr
* The read-only attribute.
*
* @deprecated Use the overloaded method with multiple arguments.<p>
* This API is deprecated because of two reasons: first, it looses the possible side effect of
* the assigned expression (consider the case when it is a function or method, but not
* exclusively). Secondly, some (if not all) are unknown value sensitive, meaning that, prior
* testing whether this is a writable attribute, P4GL checks first if the expression is
* unknown and if this value is supported. In this case a different error condition is raised.
*/
@Override
@Deprecated
public void readOnlyError(String attr)
{
handle.readOnlyError(new handle(this), attr, handle.UNKNOWN_ARGUMENT);
}
/**
* Shows a specific error, as this read-only {@code attribute} was used on the right-side of an assignment.
* If the {@code expr} is evaluated to {@code ?} (unknown value) and this attribute does not support it,
* a specific error is raised.
*
* @param attribute
* The read-only attribute.
* @param expr
* The expression whose value was attempted to be assigned to READ-ONLY attribute.
*/
@Override
public void readOnlyError(String attribute, Object expr)
{
handle.readOnlyError(new handle(this), attribute, expr);
}
/**
* Get the type of this handle as type of String.
*
* @return See above.
*/
public String type()
{
return type;
}
/**
* Getter for the INSTANTIATING-PROCEDURE attribute.
* <p>
* The returned handle will refer a valid external procedure (persistent or not) or will be
* unknown, in case its instantiating procedure was persistent and has been deleted.
*
* @return See above.
*/
@Override
public handle instantiatingProcedure()
{
return instProc == null ? new handle() : pm.resolveInstantiatingProcedure(instProc.get());
}
/**
* Getter for the INSTANTIATING-PROCEDURE attribute, as direct resource.
* <p>
* The returned reference will refer the exact external procedure (persistent or not) or will be
* null, if {@link #instProc} was not set.
*
* @return See above.
*/
public Object _instantiatingProcedure()
{
return instProc == null ? null : instProc.get();
}
/**
* Check if the resource can veto the delete or not, before attempting the {@link #delete()} call.
*
* @param ref
* The exact handle holding the reference, on which the delete is attempted.
* @param explicit
* Flag indicating this is called via <code>DELETE OBJECT</code> from the application.
*
* @return <code>false</code> if the delete is vetoed.
*/
@Override
public boolean allowDelete(handle ref, boolean explicit)
{
return true;
}
/**
* Perform actual delete of an resource. At the time of this call, it is assumed the resource
* is valid for deletion (the handle and the resource are both valid).
* <p>
* The method first calls {@link #resourceDelete()}, if the method returns {@code false}, the deletion
* is aborted.
*/
@Override
public void delete()
{
// call resource-specific delete
if (!resourceDelete())
{
// if the resource-specific delete was not possible, exit
return;
}
doDelete();
}
/**
* Increment the {@link #triggerRegistration} counter.
*/
public void incrementTrigger()
{
triggerRegistration++;
}
/**
* Returns a string representation of the object. In general, the
* {@code toString} method returns a string that
* "textually represents" this object. The result should
* be a concise but informative representation that is easy for a
* person to read.
* It is recommended that all subclasses override this method.
* <p>
* The {@code toString} method for class {@code Object}
* returns a string consisting of the name of the class of which the
* object is an instance, the at-sign character `{@code @}', and
* the unsigned hexadecimal representation of the hash code of the
* object. In other words, this method returns a string equal to the
* value of:
* <blockquote>
* <pre>
* getClass().getName() + '@' + Integer.toHexString(hashCode())
* </pre></blockquote>
*
* @return a string representation of the object.
*/
@Override
public String toString()
{
return asString();
}
/**
* Add this resource to the {@link WidgetPool}.
*
* @param pool
* The pool name, set to <code>null</code> if the unnamed pool is to be used.
*/
public void addToPool(character pool)
{
pm.wp.addResource(this, pool);
}
/**
* Perform actual delete of an resource. At the time of this call, it is assumed the resource
* is valid for deletion (the handle and the resource are both valid).
*/
protected void doDelete()
{
// remove it from the procedure's resource list
Object instProc = this.instProc == null ? null : this.instProc.get();
if (pm.isValid(instProc))
{
pm.deregisterResource(instProc, this);
}
if (triggerRegistration != 0)
{
Long resourceId = this.id();
int widgetId = (this instanceof CommonWidget
? ((CommonWidget) this).getId()
: WidgetId.INVALID_WIDGET_ID);
if ((resourceId != null && resourceId != ResourceIdHelper.INVALID_RESOURCE) ||
widgetId != WidgetId.INVALID_WIDGET_ID)
{
LogicalTerminal.cleanupTriggers(resourceId, widgetId);
}
}
// remove the instantiating procedure reference, to help out the garbage collector
this.instProc = null;
// the resource was deleted, clean up after it
pm.h.removeResource(this);
// remove from the widget pools too
pm.wp.removeResource(this);
}
/**
* Worker to be implemented by each resource. Called by {@link #delete()}.
*
* @return <code>true</code> if the resource was deleted.
*/
@SuppressWarnings("static-method")
protected boolean resourceDelete()
{
return true;
}
/**
* To be used only in cases a getter for a not-supported attribute is forced to be implemented
* by the resource. It displays appropriate error message.
*
* @param attr
* The not-supported attribute attribute.
* @param setter
* {@code true} for write access.
*/
protected void invalidAttribute(String attr, boolean setter)
{
handle.invalidAttribute(attr, setter, type());
}
/**
* Check if the resource may be implicitly deleted.
*
* @return <code>true</code> if the resource's instantiating procedure is currently being
* deleted, and the resource is currently being processed.
*/
protected boolean implicitDeletion()
{
return pm.inNotifications() &&
pm.pendingResourceDelete(this) &&
(this.instProc != null && this.instProc.get() == pm.getProcessedProcedure());
}
/**
* Set the instantiating procedure for this resource.
*
* @param hproc
* The procedure where the resource was instantiated.
*/
protected void setInstantiatingProcedure(handle hproc)
{
if (hproc != null && hproc._isValid())
{
this.instProc = new WeakReference<>(hproc.get());
// register the resource with the procedure, too
pm.registerResource(hproc.get(), this);
}
else
{
this.instProc = null;
}
}
/**
* Check if this resource must be processed via {@link ProcedureManager#processResource}, once
* it has been instantiated.
*
* @return Always <code>true</code>.
*/
@SuppressWarnings("static-method")
protected boolean processResource()
{
return true;
}
/**
* Show an error complaining that the UNKNOWN value cannot be assigned to a specified
* attribute.
*
* @param attribute
* The targeted attribute.
* @param widgetType
* The widget type.
*/
protected static void unableToAssignUnknown(String attribute, String widgetType)
{
ErrorManager.recordOrShowError(4083, attribute, widgetType);
// **Unable to assign UNKNOWN value to attribute <attribute> on <widget id>.
}
}