This document describes how to implement existing stubs in the com.goldencode.p2j.oo
classes.
For creating a new Java class or interface, which maps to a legacy OE class, read TODO: here.
Class/Interface definitions¶
Each Java class/interface with an OE equivalent, must have theLegacyResource
and LegacyResourceSupport
annotations:
LegacyResource
must have as resource value, the fully qualified of the legacy OE class name (i.e.Progress.Lang.Object
).LegacyResourceSupport
must specify the conversion and support level, using a bitwise value, like this:@LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_PARTIAL)
where the possible constants are as defined by theReportConstants
interface:- No conversion support:
CVT_LVL_NONE
- Partial conversion support:
CVT_LVL_PARTIAL
- use this for classes which are missing methods/properties - Conversion support exists, but edge conditions and variants need exploration:
CVT_LVL_BASIC
- for cases when the conversion is not complete, like unsupported overloads - Full conversion support, but there are permanent restrictions:
CVT_LVL_FULL_RESTR
- Full conversion support, no restrictions:
CVT_LVL_FULL
- No runtime support:
RT_LVL_NONE
- Runtime support has been stubbed out but is not functional:
RT_LVL_STUB
- Runtime support was implemented from documentation and real compatibility is unknown:
RT_LVL_UNTESTED
- Partial runtime support exists:
RT_LVL_PARTIAL
- Runtime support exists; edge conditions, implicit behavior and errors need exploration:
RT_LVL_BASIC
- Full runtime support, but there are permanent restrictions:
RT_LVL_FULL_RESTR
- Full runtime support, no restrictions:
RT_LVL_FULL
- No conversion support:
All classes must inherit from the com.goldencode.p2j.oo.lang.BaseObject
class (or another class which has this as super-class) and each interface must implement the com.goldencode.p2j.oo.lang._BaseObject_
(or another interface which implements it).
p2j.oo
package. For example, for Progress.Lang.Object
, we have:
__lang_BaseObject_execute__
, to include instance-level initialisation (like record open scope). This method requires noLegacySignature
orLegacyResourceSupport
annotation.__lang_BaseObject_constructor__
, to map a 4GL-compatible constructor. This requires aLegacySignature
andLegacyResourceSupport
annotation.__lang_BaseObject_destructor__
, to map a 4GL-compatible destructor. This requires aLegacySignature
andLegacyResourceSupport
annotation.
The body of these Java methods follows a similar structure to the normal 4GL-compatible class methods.
Class Members (Properties, Variables and Events)¶
Legacy properties and variables are defined as Java fields; their modifiers follow the legacy one. No annotations are required.
When defining a property/variable, one must use either TypeFactory
or UndoableFactory
APIs, to create a new instance and assign its reference. Important: in 4GL, all properties/variables are mutable; in Java, as only one instance must exist for a property/variable, one must never re-assign the field (or local variable) to another reference (except the special case of extent values). Consider these fields and variables as 'final' and never change their reference. Use the BaseDataType.assign
API to change them.
At the time of this writing, no indication was found that members for legacy OE classes can be undoable; so, in most cases, TypeFactory
APIs would be used.
- the getter will be a 'function' in OE terms, as it returns a value:
@LegacySignature(type = Type.GETTER, name = "AutoDestroy") @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL) public logical getAutoDestroy() { return function(this, "AutoDestroy", logical.class, new Block((Body) () -> { returnNormal(autoDestroy); })); }
- the setter will be an 'internal procedure' in OE terms:
@LegacySignature(type = Type.SETTER, name = "DefaultCapacity", parameters = { @LegacyParameter(name = "var", type = "INT64", mode = "INPUT") }) @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL) public void setDefaultCapacity(final int64 _var) { int64 var = TypeFactory.initInput(_var); internalProcedure(this, "DefaultCapacity", new Block((Body) () -> { defaultCapacity.assign(var); })); }
TODO: events
Class Members (Methods)¶
Each Java method with a legacy equivalent must be annotated with aLegacyResourceSupport
annotation (with similar semantics as for the class), and a LegacySignature
annotation. This annotation will always include a 4GL-style specification of its signature, where:
type
will be one of thecom.goldencode.p2j.util.InternalEntry
constants, to:- specify a 4GL-style method:
Type.METHOD
- specify a 4GL-style property getter:
Type.GETTER
- specify a 4GL-style property setter:
Type.SETTER
- specify a method to get the length of an extent property:
Type.LENGTH
- specify a method to initialize a dynamic extent property:
Type.RESIZE
- specify a 4GL-style constructor:
Type.CONSTRUCTOR
- specify a 4GL-style destructor:
Type.DESTRUCTOR
- specify a 4GL-style method:
name
, to specify the 4GL name of the member (method, etc)extent
, to specify the extent (in case the method returns an extent)qualified
, to specify the fully-qualified OE name of the method's return type- 0 or more parameters, via the
parameters
value, each of them aLegacyParameter
annotation, with:name
, the parameter's legacy nametype
, the parameter's type, as returned byBaseDataType.getTypeName
(which returns the simple name of the BDT implementation, in upper-case, likeINTEGER
)mode
, the parameter's mode (INPUT
,INPUT-OUTPUT
, orOUTPUT
)qualified
- forobject
parameters, the fully qualified name of the legacy classextent
, in case of extent parameters- at this time, there is no known usage of
table
,dataset
,buffer
andbufferFor
,append
andhandleTo
, in legacy OE classes supported by FWD
- for
INPUT
, a_p1
parameter is defined (assuming parameter's legacy name isp1
), and in the Java method's body, there will be a definition like:int64 p1 = TypeFactory.initInput(_p1);
In the Java method, onlyp1
(the copy) will be used - the_p1
instance must not be changed! - for
OUTPUT
andINPUT-OUTPUT
, there will be aTypeFactory.initOutput(p1);
orTypeFactory.initInputOutput(p1);
API call in the Java method'sBlock.init
initialization code. Thep1
parameter name will be used throughout the code.
The Java method structure is defined as, for a method returning a value:
@LegacySignature(type = ..., name = "...", parameters = // set the legacy name and type { @LegacyParameter(name = "...", type = "...", mode = "...") // add parameters as needed }) @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL) // set the cvt and rt support level <access mode> <return type> <method_name>(<arguments>) { // initialize all INPUT arguments // declare local variables return function(this, "<legacy name>", <BDT-return-type>.class, new Block((Init) () -> { // initialize OUTPUT and INPUT-OUTPUT arguments }, (Body) () -> { // implement the method returnNormal(<value>); })); }
The
function
and returnNormal
are APIs in BlockManager
FWD class.
For a method returning void, the structure will be:
@LegacySignature(type = ..., name = "...", parameters = // set the legacy name and type { @LegacyParameter(name = "...", type = "...", mode = "...") // add parameters as needed }) @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL) // set the cvt and rt support level <access mode> void <method_name>(<arguments>) { // initialize all INPUT arguments // declare local variables internalProcedure(this, "<legacy name>", new Block((Init) () -> { // initialize OUTPUT and INPUT-OUTPUT arguments }, (Body) () -> { // implement the method })); }
Everything is similar, except there is no returned value.
In cases when there is no chance for the method to do more complex work, which can (for example) raise 4GL conditions (ERROR, etc) or call into other methods, it is allowed to short-circuit the implementation and make it a pure-Java (without the BlockManager.internalProcedure
and function
overhead).
To declare variables, you can use either 4GL-style or Java-style, as appropriate. It is encouraged to use pure-Java code for certain implementations, but if the method relies on some other legacy 4GL classes, you should make the code dependant on that (for example, ByteBucket
relies on Memptr
class to store data).
It is encouraged to add pure-Java 'helper' methods, as needed - in this case, make them non-public (if possible) and do not add annotations to them (as they will not have a 4GL-equivalent). There is no requirement for the method's code to follow 4GL-style converted code - no need for 4GL-style loops, unless transactions are involved (which should not be needed).
If the legacy class defines a destructor (or if some class member needs to be cleaned up when the instance gets deleted), define all this logic in the destructor
method. This acts like an BlockManager.internalProcedure
, with its Java name following a special convention: __<qualified name without Progress or OpenEdge prefix>_destructor__
, as in __core_Memptr_destructor__
. To not call the super-destructor here! This will be done automatically by the FWD runtime.
When implementing a legacy constructor, its name again follows a special convention: __<qualified name without Progress or OpenEdge prefix>_constructor__
, as in __core_Memptr_constructor__
. These constructors have an internalProcedure
body and must always explicitly call either a super-constructor or one of the 'this' constructors. This will be performed by calling the equivalent Java method for the legacy super-constructor or 'this' constructor. Do not place logic in Java-style constructors! All initialization must be performed in the corresponding 4GL-style constructor.
super
for a method in a super-class. For OUTPUT
or INPUT-OUTPUT
arguments, you need to wrap them explicitly using a call to:
OutputParameter.wrap(var)
forOUTPUT
argumentsOutputParameter.wrap(var, true)
forINPUT-OUTPUT
arguments
This is required only when passing a local 4GL-style variable or paramater to another Java method associated with a legacy 4GL method. Calls to pure-Java helper methods don't require this special processing, as you are responsible of managing the reference.
ERROR
conditions can be managed via ErrorManager
APIs:
recordOrShowError
, to record and show an error to the user (considering NO-ERROR), but without raising an ERROR condition. IfNO-ERROR
is used, this can be accessed viaERROR-STATUS
handle.recordOrThrowError
, to raise the actual ERROR condition.
Method Local Variables¶
When declared in a method, these variables must be scoped to it, and declared outside the function
and internalProcedure
bodies. Use TypeFactory
APIs as needed, and like the variables which are class members, these are too mutable.
Java local (or class) variables can be used as needed.
To declare a legacy object, use com.goldencode.p2j.util.object
and TypeFactory.object()
. To instantiate a new class, use ObjectOps.newInstance
APIs, by specifying a Java class (for a supported legacy class) or legacy fully-qualified class name.
© 2004-2022 Golden Code Development Corporation. ALL RIGHTS RESERVED.