Project

General

Profile

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 the LegacyResource and LegacyResourceSupport annotations:
  1. LegacyResource must have as resource value, the fully qualified of the legacy OE class name (i.e. Progress.Lang.Object).
  2. 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 the ReportConstants 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

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).

Each class has some special methods, to represent instance-level initialization, legacy-compatible constructor and destructor. These follow special names, and must not be changed, for the code in 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 no LegacySignature or LegacyResourceSupport annotation.
  • __lang_BaseObject_constructor__, to map a 4GL-compatible constructor. This requires a LegacySignature and LegacyResourceSupport annotation.
  • __lang_BaseObject_destructor__, to map a 4GL-compatible destructor. This requires a LegacySignature and LegacyResourceSupport 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.

Unless the getter/setter for a legacy property contains complex logic, it can be accessed directly in the defining class. Otherwise, (or for external access) using its associated getter/setter Java methods. These Java methods follow the same pattern as normal Java methods (with a legacy equivalent):
  • 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 a LegacyResourceSupport 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 the com.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
  • 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 a LegacyParameter annotation, with:
    • name, the parameter's legacy name
    • type, the parameter's type, as returned by BaseDataType.getTypeName (which returns the simple name of the BDT implementation, in upper-case, like INTEGER)
    • mode, the parameter's mode (INPUT, INPUT-OUTPUT, or OUTPUT)
    • qualified - for object parameters, the fully qualified name of the legacy class
    • extent, in case of extent parameters
    • at this time, there is no known usage of table, dataset, buffer and bufferFor, append and handleTo, in legacy OE classes supported by FWD
Each Java method defines the parameters depending on their mode:
  • for INPUT, a _p1 parameter is defined (assuming parameter's legacy name is p1), and in the Java method's body, there will be a definition like:
    int64 p1 = TypeFactory.initInput(_p1);
    

    In the Java method, only p1 (the copy) will be used - the _p1 instance must not be changed!
  • for OUTPUT and INPUT-OUTPUT, there will be a TypeFactory.initOutput(p1); or TypeFactory.initInputOutput(p1); API call in the Java method's Block.init initialization code. The p1 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.

Calling other Java methods which implement a legacy 4GL method should be performed directly, using Java-style calling conventions - this includes 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) for OUTPUT arguments
  • OutputParameter.wrap(var, true) for INPUT-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. If NO-ERROR is used, this can be accessed via ERROR-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.