ClassEvent.java
/*
** Module : ClassEvent.java
** Abstract : Implementation of 4GL-style class event management.
**
** Copyright (c) 2018-2022, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description----------------------------------------
** 001 CA 20181216 First version.
** 002 CA 20190219 Added runtime support.
** 003 CA 20220106 For PUBLISH statement, arguments must be evaluated only if the subscription is found. For
** CLASS events, they are evaluated immediately (that's why there is no lambda emitted for
** them).
*/
/*
** 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.reflect.*;
import java.util.*;
import com.goldencode.p2j.oo.lang.*;
/**
* Management of class events.
*/
public class ClassEvent
{
/** Representation of the event's signature. */
private final InternalEntry signature;
/** The event's name. */
private final character event = new character(UUID.randomUUID().toString());
/**
* The event's publisher (object instance where it was defined or the pseudo-object for the
* legacy class, if is static.
*/
private final handle publisher = new handle();
/** The event's legacy name. */
private final String evtName;
/**
* Create a new class event from the given publisher (which is a static class).
*
* @param publisher
* The legacy type.
* @param evtName
* The legacy class event name.
* @param fieldName
* The converted name for the Java field.
*/
public ClassEvent(Class<? extends _BaseObject_> publisher, String evtName, String fieldName)
{
ObjectOps.load(publisher);
this.publisher.assign(new ExternalProgramWrapper(ObjectOps.getStaticInstance(publisher)));
this.evtName = evtName;
this.signature = buildSignature(publisher, fieldName);
}
/**
* Create a new class event from the given publisher.
*
* @param publisher
* The legacy object instance.
* @param evtName
* The legacy class event name.
* @param fieldName
* The converted name for the Java field.
*/
public ClassEvent(_BaseObject_ publisher, String evtName, String fieldName)
{
this.publisher.assign(new ExternalProgramWrapper(publisher));
this.evtName = evtName;
this.signature = buildSignature(publisher.getClass(), fieldName);
}
/**
* Notify all subscribers by publishing this event using the given arguments.
*
* @param args
* The published arguments.
*/
public void publish(Object... args)
{
// the modes are not required - it is a 4GL compile-time error to not match exactly as
// the specified signature, but only for the OO subscribers. internal procedures can
// convert arguments.
ProcedureManager.publish(true, event, publisher, signature.getParameterModes(), () -> args);
}
/**
* Unsubscribe the specified method, in the given object, from this event.
* <p>
* The method must be defined in the given object.
*
* @param subscriber
* The subscriber reference.
* @param method
* The method name.
*/
public void subscribe(handle subscriber, character method)
{
if (!valid(subscriber, method))
{
return;
}
if (!ProcedureManager.subscribe(subscriber, event, publisher, method, null))
{
ErrorManager.recordOrThrowError(15329,
"Subscribe operation failed. " +
method.toStringMessage() + " has already subscribed " +
"to the " + evtName + " event",
false, false);
}
}
/**
* Subscribe the specified method, in the given object, for notifications from this event.
* <p>
* The method must be defined in the given object.
*
* @param ref
* The object reference.
* @param method
* The method name.
*/
public void subscribe(object<? extends _BaseObject_> ref, character method)
{
// this API is emitted always with a valid method
// object may be a progress.lang.class, then method must be static method in the target class
// referent is always valid
Object referent = ref.ref();
Class<? extends _BaseObject_> type = (Class<? extends _BaseObject_>) referent.getClass();
if (referent instanceof LegacyClass)
{
type = ((LegacyClass) referent).getType();
referent = ObjectOps.getStaticInstance(type);
}
// hook the method with a real Java method based on the signature!
InternalEntry target = SourceNameMapper.findLegacyMethod(type,
method.toStringMessage(),
signature);
if (target == null)
{
// error (but should never happen)
throw new RuntimeException("Could not resolve the target method " +
method.toStringMessage() + " in class " + type +
" having the event's signature!");
}
handle subscriber = new handle(new ExternalProgramWrapper(referent));
if (!ProcedureManager.subscribe(subscriber, event, publisher, method, target))
{
ErrorManager.recordOrThrowError(15329,
"Subscribe operation failed. " +
method.toStringMessage() + " has already subscribed " +
"to the " + evtName + " event",
false, false);
}
}
/**
* Unsubscribe the specified internal procedure, in the given program, from this event.
* <p>
* The internal procedure must be defined in the given program.
*
* @param subscriber
* The program handle.
* @param method
* The method name.
*/
public void unsubscribe(handle subscriber, character method)
{
if (!valid(subscriber, method))
{
return;
}
ProcedureManager.unsubscribe(false, subscriber, event, publisher, method);
}
/**
* Unsubscribe the specified method, in the given object, from this event.
* <p>
* The method must be defined in the given object.
*
* @param ref
* The object reference.
* @param method
* The method name.
*/
public void unsubscribe(object<? extends _BaseObject_> ref, character method)
{
// this API is emitted always with a valid method
// object may be a progress.lang.class, then method must be static
// referent is always valid
Object referent = ref.ref();
if (referent instanceof LegacyClass)
{
referent = ObjectOps.getStaticInstance(((LegacyClass) referent).getType());
}
handle subscriber = new handle(new ExternalProgramWrapper(referent));
ProcedureManager.unsubscribe(false, subscriber, event, publisher, method);
}
/**
* Validate the specified subscriber and method name.
*
* @param subscriber
* The subscriber handle - must be a valid external program.
* @param method
* The method name - must be a defined internal procedure.
*
* @return See above.
*/
private boolean valid(handle subscriber, character method)
{
if (!ProcedureManager.isProcedure(subscriber))
{
ErrorManager.recordOrThrowError(15327,
"Unable to resolve handler when subscribing to or " +
"unsubscribing from event " + evtName,
false, false);
return false;
}
// the method must exist in the given handle, but the signature is not validated
String iename = method.isUnknown() ? "" : method.toStringMessage();
String pname = ProcedureManager.getAbsoluteName(subscriber.get());
String jname = SourceNameMapper.getMethodName(pname, iename, false);
if (jname == null)
{
int[] nums = { 14589, 15327 };
String[] texts =
{
"Subscribe or Unsubscribe method failed: unable to find internal procedure " + iename,
"Unable to resolve handler when subscribing to or unsubscribing from event " +
evtName + " "
};
ErrorManager.recordOrThrowError(nums, texts, false, false);
return false;
}
return true;
}
/**
* Build an {@link InternalEntry} using the {@link LegacySignature} annotation appearing at the
* Java field declaring this class event.
*
* @param def
* The OO class.
* @param field
* The converted field name.
*
* @return An {@link InternalEntry} representing the event's signature.
*/
private InternalEntry buildSignature(Class<?> def, String field)
{
if (def == null || !_BaseObject_.class.isAssignableFrom(def))
{
throw new RuntimeException("Can't initialize CLASS EVENT signature!");
}
try
{
Field f = def.getDeclaredField(field);
LegacySignature ls = f.getAnnotation(LegacySignature.class);
return SourceNameMapper.buildInternalEntry(ls, field);
}
catch (NoSuchFieldException e)
{
return buildSignature(def.getSuperclass(), field);
}
catch (Exception e)
{
throw new RuntimeException("Can't initialize CLASS EVENT signature!", e);
}
}
}