Project

General

Profile

Parameter Definition and Passing

Parameters in 4GL can be defined at the external program, internal procedure, user-defined functions, class constructors, methods, and even user-defined getter/setter. This chapter will cover parameters definitions - how FWD converts them, and how parameters can be passed when invoking i.e. an internal procedure or OO method.

The parameter definition is mostly identical for class members and procedures/functions, with these differences:
  • for external program, internal procedure and user-defined function, all parameters are registered:
    • in name_map.xml, via parameter child nodes at the class-mapping or method-mapping nodes.
    • via LegacySignature.parameters array, using LegacyParameter annotation to hold the information about the blocks's signature.
    • virtual user-defined functions or virtual internal procedures (like IN SUPER or IN handle) are registered only in name_map.xml.
  • for class members, parameters are registered only via LegacySignature.parameters array, using LegacyParameter annotation to hold the information about the member's signature.
  • for virtual functions defined in a class (IN SUPER or IN handle functions), the registration is done only in name_map.xml.
FWD supports these parameters at definition or passing:
  • scalar or extent parameter definition and arguments
  • table, table-handle parameter definition and argument
  • dataset, dataset-handle parameter definition and argument
  • buffer parameter definition and argument
  • passing field or class property reference as an argument

All parameter modes (INPUT, OUTPUT, INPUT-OUTPUT) are supported. In some cases, when the converted code does not emit a direct Java call to the target - for dynamic calls, like RUN, DYNAMIC-FUNCTION, DYNAMIC-INVOKE, DYNAMIC-NEW - the modes are serialized as a string and added as an argument to the call; this string contains one of the I (for INPUT), O (for OUTPUT), U (for INPUT-OUTPUT) characters, for each passed argument. The reason for this approach is that the runtime needs to validate the argument number and modes against the resolved target, as the invoked target can't be statically linked at conversion time.

For class method calls, for cases where they are statically linked to the called Java method, they are emitted as a direct Java method call, with only the arguments directly passed at the call.

This chapter will cover how parameters are defined and how arguments are used. When there is a difference how parameters are defined and/or passed in FWD, for an internal procedure vs a class method, this will be explicitly stated and shown. Otherwise, assume the conversion is the same in both cases.

Some outdated content can be seen in Parameter Passing.

Scalar Parameter

Scalar parameters are defined as simple, non-extent, variables. When used as input, the arguments can be passed either as a literal or a variable (fields and class properties will be covered later in this chapter).

This test shows how 4GL data types map to FWD data types, and how the parameters are registered in name_map.xml and at the internal procedure definition or function:

def var  v1 as character.
def var  v2 as com-handle.
def var  v3 as date.
def var  v4 as datetime.
def var  v5 as datetime-tz.
def var  v6 as decimal.
def var  v7 as handle.
def var  v8 as integer.
def var  v9 as int64.
def var v10 as logical.
def var v11 as longchar.
def var v12 as memptr.
def var v13 as progress.lang.object.
def var v14 as raw.
def var v15 as recid.
def var v16 as rowid.

procedure param_test1.
   def input parameter  p1 as character.
   def input parameter  p2 as com-handle.
   def input parameter  p3 as date.
   def input parameter  p4 as datetime.
   def input parameter  p5 as datetime-tz.
   def input parameter  p6 as decimal.
   def input parameter  p7 as handle.
   def input parameter  p8 as integer.
   def input parameter  p9 as int64.
   def input parameter p10 as logical.
   def input parameter p11 as longchar.
   def input parameter p12 as memptr.
   def input parameter p13 as progress.lang.object.
   def input parameter p14 as raw.
   def input parameter p15 as recid.
   def input parameter p16 as rowid.
end.

function func_test1 returns int(input  p1 as character,
                                input  p2 as com-handle,
                                input  p3 as date,
                                input  p4 as datetime,
                                input  p5 as datetime-tz,
                                input  p6 as decimal,
                                input  p7 as handle,
                                input  p8 as integer,
                                input  p9 as int64,
                                input p10 as logical,
                                input p11 as longchar,
                                input p12 as memptr,
                                input p13 as progress.lang.object,
                                input p14 as raw,
                                input p15 as recid,
                                input p16 as rowid).
end.

it will convert in FWD like:
   @LegacySignature(type = Type.PROCEDURE, name = "param_test1", parameters = 
   {
      @LegacyParameter(name = "p1", type = "CHARACTER", mode = "INPUT"),
      @LegacyParameter(name = "p2", type = "COMHANDLE", mode = "INPUT"),
      @LegacyParameter(name = "p3", type = "DATE", mode = "INPUT"),
      @LegacyParameter(name = "p4", type = "DATETIME", mode = "INPUT"),
      @LegacyParameter(name = "p5", type = "DATETIMETZ", mode = "INPUT"),
      @LegacyParameter(name = "p6", type = "DECIMAL", mode = "INPUT"),
      @LegacyParameter(name = "p7", type = "HANDLE", mode = "INPUT"),
      @LegacyParameter(name = "p8", type = "INTEGER", mode = "INPUT"),
      @LegacyParameter(name = "p9", type = "INT64", mode = "INPUT"),
      @LegacyParameter(name = "p10", type = "LOGICAL", mode = "INPUT"),
      @LegacyParameter(name = "p11", type = "LONGCHAR", mode = "INPUT"),
      @LegacyParameter(name = "p12", type = "MEMPTR", mode = "INPUT"),
      @LegacyParameter(name = "p13", type = "OBJECT", qualified = "progress.lang.object", mode = "INPUT"),
      @LegacyParameter(name = "p14", type = "RAW", mode = "INPUT"),
      @LegacyParameter(name = "p15", type = "RECID", mode = "INPUT"),
      @LegacyParameter(name = "p16", type = "ROWID", mode = "INPUT")
   })
   public void paramTest1(final character _p1, final comhandle _p2, final date _p3, final datetime _p4, final datetimetz _p5, final decimal _p6, final handle _p7, final integer _p8, final int64 _p9, final logical _p10, final longchar _p11, final memptr _p12, final object<? extends com.goldencode.p2j.oo.lang._BaseObject_> _p13, final raw _p14, final recid _p15, final rowid _p16)
   {
      character p1 = UndoableFactory.initInput(_p1);
      comhandle p2 = UndoableFactory.initInput(_p2);
      date p3 = UndoableFactory.initInput(_p3);
      datetime p4 = UndoableFactory.initInput(_p4);
      datetimetz p5 = UndoableFactory.initInput(_p5);
      decimal p6 = UndoableFactory.initInput(_p6);
      handle p7 = UndoableFactory.initInput(_p7);
      integer p8 = UndoableFactory.initInput(_p8);
      int64 p9 = UndoableFactory.initInput(_p9);
      logical p10 = UndoableFactory.initInput(_p10);
      longchar p11 = UndoableFactory.initInput(_p11);
      memptr p12 = UndoableFactory.initInput(_p12);
      object<? extends com.goldencode.p2j.oo.lang._BaseObject_> p13 = UndoableFactory.initInput(_p13);
      raw p14 = UndoableFactory.initInput(_p14);
      recid p15 = UndoableFactory.initInput(_p15);
      rowid p16 = UndoableFactory.initInput(_p16);

      internalProcedure(new Block());
   }

   @LegacySignature(type = Type.FUNCTION, name = "func_test1", returns = "INTEGER", parameters = 
   {
      @LegacyParameter(name = "p1", type = "CHARACTER", mode = "INPUT"),
      @LegacyParameter(name = "p2", type = "COMHANDLE", mode = "INPUT"),
      @LegacyParameter(name = "p3", type = "DATE", mode = "INPUT"),
      @LegacyParameter(name = "p4", type = "DATETIME", mode = "INPUT"),
      @LegacyParameter(name = "p5", type = "DATETIMETZ", mode = "INPUT"),
      @LegacyParameter(name = "p6", type = "DECIMAL", mode = "INPUT"),
      @LegacyParameter(name = "p7", type = "HANDLE", mode = "INPUT"),
      @LegacyParameter(name = "p8", type = "INTEGER", mode = "INPUT"),
      @LegacyParameter(name = "p9", type = "INT64", mode = "INPUT"),
      @LegacyParameter(name = "p10", type = "LOGICAL", mode = "INPUT"),
      @LegacyParameter(name = "p11", type = "LONGCHAR", mode = "INPUT"),
      @LegacyParameter(name = "p12", type = "MEMPTR", mode = "INPUT"),
      @LegacyParameter(name = "p13", type = "OBJECT", qualified = "progress.lang.object", mode = "INPUT"),
      @LegacyParameter(name = "p14", type = "RAW", mode = "INPUT"),
      @LegacyParameter(name = "p15", type = "RECID", mode = "INPUT"),
      @LegacyParameter(name = "p16", type = "ROWID", mode = "INPUT")
   })
   public integer funcTest1(final character _p1, final comhandle _p2, final date _p3, final datetime _p4, final datetimetz _p5, final decimal _p6, final handle _p7, final integer _p8, final int64 _p9, final logical _p10, final longchar _p11, final memptr _p12, final object<? extends com.goldencode.p2j.oo.lang._BaseObject_> _p13, final raw _p14, final recid _p15, final rowid _p16)
   {
      character p1 = TypeFactory.initInput(_p1);
      comhandle p2 = TypeFactory.initInput(_p2);
      date p3 = TypeFactory.initInput(_p3);
      datetime p4 = TypeFactory.initInput(_p4);
      datetimetz p5 = TypeFactory.initInput(_p5);
      decimal p6 = TypeFactory.initInput(_p6);
      handle p7 = TypeFactory.initInput(_p7);
      integer p8 = TypeFactory.initInput(_p8);
      int64 p9 = TypeFactory.initInput(_p9);
      logical p10 = TypeFactory.initInput(_p10);
      longchar p11 = TypeFactory.initInput(_p11);
      memptr p12 = TypeFactory.initInput(_p12);
      object<? extends com.goldencode.p2j.oo.lang._BaseObject_> p13 = TypeFactory.initInput(_p13);
      raw p14 = TypeFactory.initInput(_p14);
      recid p15 = TypeFactory.initInput(_p15);
      rowid p16 = TypeFactory.initInput(_p16);

      return function(this, "func_test1", integer.class, new Block());
   }

Note how LegacyParameter is used to specify the 4GL legacy data type and the parameter's mode. Beside the LegacySignature annotations, the signature will also be registered in name_map.xml:

<name-map>
    <method-mapping jname="paramTest1" pname="param_test1" type="PROCEDURE">
      <parameter jname="p1" mode="INPUT" pname="p1" type="CHARACTER"/>
      <parameter jname="p2" mode="INPUT" pname="p2" type="COMHANDLE"/>
      <parameter jname="p3" mode="INPUT" pname="p3" type="DATE"/>
      <parameter jname="p4" mode="INPUT" pname="p4" type="DATETIME"/>
      <parameter jname="p5" mode="INPUT" pname="p5" type="DATETIMETZ"/>
      <parameter jname="p6" mode="INPUT" pname="p6" type="DECIMAL"/>
      <parameter jname="p7" mode="INPUT" pname="p7" type="HANDLE"/>
      <parameter jname="p8" mode="INPUT" pname="p8" type="INTEGER"/>
      <parameter jname="p9" mode="INPUT" pname="p9" type="INT64"/>
      <parameter jname="p10" mode="INPUT" pname="p10" type="LOGICAL"/>
      <parameter jname="p11" mode="INPUT" pname="p11" type="LONGCHAR"/>
      <parameter jname="p12" mode="INPUT" pname="p12" type="MEMPTR"/>
      <parameter jname="p13" mode="INPUT" pname="p13" qualified="progress.lang.object" type="OBJECT"/>
      <parameter jname="p14" mode="INPUT" pname="p14" type="RAW"/>
      <parameter jname="p15" mode="INPUT" pname="p15" type="RECID"/>
      <parameter jname="p16" mode="INPUT" pname="p16" type="ROWID"/>
    </method-mapping>
    <method-mapping jname="funcTest1" pname="func_test1" returns="INTEGER" type="FUNCTION">
      <parameter jname="p1" mode="INPUT" pname="p1" type="CHARACTER"/>
      <parameter jname="p2" mode="INPUT" pname="p2" type="COMHANDLE"/>
      <parameter jname="p3" mode="INPUT" pname="p3" type="DATE"/>
      <parameter jname="p4" mode="INPUT" pname="p4" type="DATETIME"/>
      <parameter jname="p5" mode="INPUT" pname="p5" type="DATETIMETZ"/>
      <parameter jname="p6" mode="INPUT" pname="p6" type="DECIMAL"/>
      <parameter jname="p7" mode="INPUT" pname="p7" type="HANDLE"/>
      <parameter jname="p8" mode="INPUT" pname="p8" type="INTEGER"/>
      <parameter jname="p9" mode="INPUT" pname="p9" type="INT64"/>
      <parameter jname="p10" mode="INPUT" pname="p10" type="LOGICAL"/>
      <parameter jname="p11" mode="INPUT" pname="p11" type="LONGCHAR"/>
      <parameter jname="p12" mode="INPUT" pname="p12" type="MEMPTR"/>
      <parameter jname="p13" mode="INPUT" pname="p13" qualified="progress.lang.object" type="OBJECT"/>
      <parameter jname="p14" mode="INPUT" pname="p14" type="RAW"/>
      <parameter jname="p15" mode="INPUT" pname="p15" type="RECID"/>
      <parameter jname="p16" mode="INPUT" pname="p16" type="ROWID"/>
    </method-mapping>

For invoking the internal procedure or user-defined function:

run param_test1(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16).
func_test1(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16).

FWD will generate a dynamic call and a Java method call:
         ControlFlowOps.invokeWithMode("param_test1", "IIIIIIIIIIIIIIII", v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16);
         funcTest1(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16);

The Java method call will be emitted for direct class method calls, too. In the RUN case, note how the second argument is the argument modes as specified at the caller, for each argument.

For the OUTPUT and INPUT-OUTPUT cases, the parameter modes will be specified like:

procedure param_test2.
   def output parameter  p1 as character.
end.

procedure param_test3.
   def input-output parameter  p1 as character.
end.

run param_test2(output v1).
run param_test3(input-output v1).

func_test2(output v1).
func_test3(input-output v1).

The conversion will emit LegacySignature annotation for the parameter definition:

   @LegacySignature(type = Type.PROCEDURE, name = "param_test2", parameters = 
   {
      @LegacyParameter(name = "p1", type = "CHARACTER", mode = "OUTPUT")
   })
   public void paramTest2(final character _p1)
   {
      character p1 = UndoableFactory.initOutput(_p1);

      internalProcedure(new Block());
   }

   @LegacySignature(type = Type.PROCEDURE, name = "param_test3", parameters = 
   {
      @LegacyParameter(name = "p1", type = "CHARACTER", mode = "INPUT-OUTPUT")
   })
   public void paramTest3(final character _p1)
   {
      character p1 = UndoableFactory.initInputOutput(_p1);

      internalProcedure(new Block());
   }

   @LegacySignature(type = Type.FUNCTION, name = "func_test2", returns = "INTEGER", parameters = 
   {
      @LegacyParameter(name = "p1", type = "CHARACTER", mode = "OUTPUT")
   })
   public integer funcTest2(final character _p1)
   {
      character p1 = TypeFactory.initOutput(_p1);

      return function(this, "func_test2", integer.class, new Block());
   }

   @LegacySignature(type = Type.FUNCTION, name = "func_test3", returns = "INTEGER", parameters = 
   {
      @LegacyParameter(name = "p1", type = "CHARACTER", mode = "INPUT-OUTPUT")
   })
   public integer funcTest3(final character _p1)
   {
      character p1 = TypeFactory.initInputOutput(_p1);

      return function(this, "func_test3", integer.class, new Block());
   }

and name_map.xml registration of the parameters:
    </method-mapping>
    <method-mapping jname="paramTest2" pname="param_test2" type="PROCEDURE">
      <parameter jname="p1" mode="OUTPUT" pname="p1" type="CHARACTER"/>
    </method-mapping>
    <method-mapping jname="paramTest3" pname="param_test3" type="PROCEDURE">
      <parameter jname="p1" mode="INPUT-OUTPUT" pname="p1" type="CHARACTER"/>
    </method-mapping>
    <method-mapping jname="funcTest2" pname="func_test2" returns="INTEGER" type="FUNCTION">
      <parameter jname="p1" mode="OUTPUT" pname="p1" type="CHARACTER"/>
    </method-mapping>
    <method-mapping jname="funcTest3" pname="func_test3" returns="INTEGER" type="FUNCTION">
      <parameter jname="p1" mode="INPUT-OUTPUT" pname="p1" type="CHARACTER"/>
    </method-mapping>

The calls are being converted like:

         ControlFlowOps.invokeWithMode("param_test2", "O", v1);
         ControlFlowOps.invokeWithMode("param_test3", "U", v1);
         funcTest2(wrap(character.class, v1));
         funcTest3(wrap(character.class, v1, true));

For dynamic calls (like RUN statement) FWD can automatically prepare the reference so that it will be compatible with the OUTPUT or INPUT-OUTPUT mode. For direct calls, FWD will explicitly prepare the arguments, via OutputParameter.wrap APIs:
  • first argument is the argument's data type
  • second argument is the argument reference
  • third argument is true for the INPUT-OUTPUT mode

TODO: for 4GL class method calls, wrap API calls are no longer emitted, the runtime automatically prepares the references, depending on the parameter definition's mode. Omit the wrap for direct user-defined function calls, too.

Extent Parameter

Extent parameters are defined as different Java types, depending on the parameter modes:
  • for INPUT, the parameter definition is a simple Java array
  • for OUTPUT, OutputExtentParameter type is used
  • for INPUT-OUTPUT, InputOutputExtentParameter type is used

The OutputExtentParameter and InputOutputExtentParameter are required as the Java array reference is immutable: in 4GL, for OUTPUT and INPUT-OUTPUT, the called procedure must not alter the caller's array until the procedure has finished.

For example, defining a dynamic extent parameter:

function funcTest1 returns int(input p1 as char extent).
end.

function funcTest2 returns int(output p1 as char extent).
end.

function funcTest3 returns int(input-output p1 as char extent).
end.

will convert like:

   @LegacySignature(type = Type.FUNCTION, name = "funcTest1", returns = "INTEGER", parameters = 
   {
      @LegacyParameter(name = "p1", type = "CHARACTER", extent = -1, mode = "INPUT")
   })
   public integer funcTest1(final character[] _p1)
   {
      character[] p1[] = 
      {
         TypeFactory.initInput(_p1)
      };

      return function(this, "funcTest1", integer.class, new Block());
   }

   @LegacySignature(type = Type.FUNCTION, name = "funcTest2", returns = "INTEGER", parameters = 
   {
      @LegacyParameter(name = "p1", type = "CHARACTER", extent = -1, mode = "OUTPUT")
   })
   public integer funcTest2(final OutputExtentParameter<character> extp1)
   {
      character[] p1[] = 
      {
         TypeFactory.initOutput(extp1)
      };

      return function(this, "funcTest2", integer.class, new Block());
   }

   @LegacySignature(type = Type.FUNCTION, name = "funcTest3", returns = "INTEGER", parameters = 
   {
      @LegacyParameter(name = "p1", type = "CHARACTER", extent = -1, mode = "INPUT-OUTPUT")
   })
   public integer funcTest3(final InputOutputExtentParameter<character> extp1)
   {
      character[] p1[] = 
      {
         TypeFactory.initInputOutput(extp1)
      };

      return function(this, "funcTest3", integer.class, new Block());
   }

with the parameters registered also in name_map.xml:
    <method-mapping jname="funcTest1" pname="funcTest1" returns="INTEGER" type="FUNCTION">
      <parameter extent="-1" jname="p1" mode="INPUT" pname="p1" type="CHARACTER"/>
    </method-mapping>
    <method-mapping jname="funcTest2" pname="funcTest2" returns="INTEGER" type="FUNCTION">
      <parameter extent="-1" jname="p1" mode="OUTPUT" pname="p1" type="CHARACTER"/>
    </method-mapping>
    <method-mapping jname="funcTest3" pname="funcTest3" returns="INTEGER" type="FUNCTION">
      <parameter extent="-1" jname="p1" mode="INPUT-OUTPUT" pname="p1" type="CHARACTER"/>
    </method-mapping>

At both the LegacyParameter annotation and name_map.xml, extent = -1 marks this parameter as dynamic extent.

At the function, procedure or method block, the parameter array reference is kept in a single-element Java array:

      character[] p1[] = 
      {
         TypeFactory.initInput(_p1)
      };

This is required because the dynamic extent parameter can be resized, and in Java terms this means its array reference is changing; this solution is also required to be able to update the array reference at the caller, as needed, when OUTPUT and INPUT-OUTPUT modes are used.

At the caller, dynamic extent arguments specified like:

def var v1 as character extent.

funcTest1(input v1).
funcTest2(output v1).
funcTest3(input-output v1).

will convert to:
   @LegacySignature(type = Type.VARIABLE, extent = -1, name = "v1")
   character[] v1 = UndoableFactory.characterExtent();

         funcTest1(v1);
         funcTest2(new OutputExtentParameter<character>()
         {
            public character[] getVariable()
            {
               return v1;
            }

            public void setVariable(final character[] newRef)
            {
               v1 = newRef;
            }
         });
         funcTest3(new InputOutputExtentParameter<character>()
         {
            public character[] getVariable()
            {
               return v1;
            }

            public void setVariable(final character[] newRef)
            {
               v1 = newRef;
            }
         });

Anonymous OutputExtentParameter and InputOutputExtentParameter instances are required to pass back the array reference to the caller, to update it.

For non-dynamic extent parameters, defined like:

function funcTest1 returns int(input p1 as char extent 5).
end.

function funcTest2 returns int(output p1 as char extent 5).
end.

function funcTest3 returns int(input-output p1 as char extent 5).
end.

will convert to these Java methods:
   @LegacySignature(type = Type.FUNCTION, name = "funcTest1", returns = "INTEGER", parameters = 
   {
      @LegacyParameter(name = "p1", type = "CHARACTER", extent = 5, mode = "INPUT")
   })
   public integer funcTest1(final character[] _p1)
   {
      character[] p1 = TypeFactory.initInput(_p1);

      return function(this, "funcTest1", integer.class, new Block());
   }

   @LegacySignature(type = Type.FUNCTION, name = "funcTest2", returns = "INTEGER", parameters = 
   {
      @LegacyParameter(name = "p1", type = "CHARACTER", extent = 5, mode = "OUTPUT")
   })
   public integer funcTest2(final OutputExtentParameter<character> extp1)
   {
      character[] p1 = TypeFactory.initOutput(extp1, 5);

      return function(this, "funcTest2", integer.class, new Block());
   }

   @LegacySignature(type = Type.FUNCTION, name = "funcTest3", returns = "INTEGER", parameters = 
   {
      @LegacyParameter(name = "p1", type = "CHARACTER", extent = 5, mode = "INPUT-OUTPUT")
   })
   public integer funcTest3(final InputOutputExtentParameter<character> extp1)
   {
      character[] p1 = TypeFactory.initInputOutput(extp1, 5);

      return function(this, "funcTest3", integer.class, new Block());
   }

and parameters also registered in name_map.xml:
    <method-mapping jname="funcTest1" pname="funcTest1" returns="INTEGER" type="FUNCTION">
      <parameter extent="5" jname="p1" mode="INPUT" pname="p1" type="CHARACTER"/>
    </method-mapping>
    <method-mapping jname="funcTest2" pname="funcTest2" returns="INTEGER" type="FUNCTION">
      <parameter extent="5" jname="p1" mode="OUTPUT" pname="p1" type="CHARACTER"/>
    </method-mapping>
    <method-mapping jname="funcTest3" pname="funcTest3" returns="INTEGER" type="FUNCTION">
      <parameter extent="5" jname="p1" mode="INPUT-OUTPUT" pname="p1" type="CHARACTER"/>
    </method-mapping>

The differences between dynamic and fixed extent parameters is that at the function/procedure, the parameter is defined as one-dimensional array, as the array can't be resized.

The call is done in the same way as for dynamic extent parameters.

   @LegacySignature(type = Type.VARIABLE, extent = 5, name = "v1")
   character[] v1 = UndoableFactory.characterExtent(5);

         funcTest1(v1);
         funcTest2(new OutputExtentParameter<character>()
         {
            public character[] getVariable()
            {
               return v1;
            }

            public void setVariable(final character[] newRef)
            {
               v1 = newRef;
            }
         });
         funcTest3(new InputOutputExtentParameter<character>()
         {
            public character[] getVariable()
            {
               return v1;
            }

            public void setVariable(final character[] newRef)
            {
               v1 = newRef;
            }
         });

Table Parameter (procedure or user-defined function)

Table parameters can be defined (and passed) as TABLE or TABLE-HANDLE. In FWD, both cases emit a TableParameter parameter at the procedure or function definition. All modes are supported - INPUT, OUTPUT or INPUT-OUTPUT.

For example, defining these table parameters:
def temp-table tt1 field f1 as int.
def var ht as handle.

function tableParamTest1 returns int(input table tt1).
end.

function tableParamTest2 returns int(output table tt1).
end.

function tableParamTest3 returns int(input-output table tt1).
end.

tableParamTest1(input table tt1).
tableParamTest2(output table tt1).
tableParamTest3(input-output table tt1).

tableParamTest1(input table-handle ht).
tableParamTest2(output table-handle ht).
tableParamTest3(input-output table-handle ht).

will emit this Java code:
   Tt1_1_1.Buf tt1 = TemporaryBuffer.define(Tt1_1_1.Buf.class, "tt1", "tt1", false);

   @LegacySignature(type = Type.VARIABLE, name = "ht")
   handle ht = UndoableFactory.handle();

   @LegacySignature(type = Type.FUNCTION, name = "tableParamTest1", returns = "INTEGER", parameters = 
   {
      @LegacyParameter(type = "TABLE", table = "tt1", mode = "INPUT")
   })
   public integer tableParamTest1(final TableParameter tt1Buf2)
   {
      return function(this, "tableParamTest1", integer.class, new Block((Init) () -> 
      {
         TemporaryBuffer.associate(tt1Buf2, tt1, true, false);
      }));
   }

   @LegacySignature(type = Type.FUNCTION, name = "tableParamTest2", returns = "INTEGER", parameters = 
   {
      @LegacyParameter(type = "TABLE", table = "tt1", mode = "OUTPUT")
   })
   public integer tableParamTest2(final TableParameter tt1Buf3)
   {
      return function(this, "tableParamTest2", integer.class, new Block((Init) () -> 
      {
         TemporaryBuffer.associate(tt1Buf3, tt1, false, true);
      }));
   }

   @LegacySignature(type = Type.FUNCTION, name = "tableParamTest3", returns = "INTEGER", parameters = 
   {
      @LegacyParameter(type = "TABLE", table = "tt1", mode = "INPUT-OUTPUT")
   })
   public integer tableParamTest3(final TableParameter tt1Buf4)
   {
      return function(this, "tableParamTest3", integer.class, new Block((Init) () -> 
      {
         TemporaryBuffer.associate(tt1Buf4, tt1, true, true);
      }));
   }

where:
  • LegacyParameter is used to identify this parameter as TABLE
  • TemporaryBuffer.associate is used to associate the table referenced by the parameter with the instance received as argument:
    • first argument is the local TableParameter instance, received as argument
    • second argument is the target DMO
    • last two arguments represent flags for the association mode (input and output); when both are true, it is assumed the INPUT-OUTPUT mode.

It will also register the parameters in name_map.xml:

    <method-mapping jname="tableParamTest1" pname="tableParamTest1" returns="INTEGER" type="FUNCTION">
      <parameter mode="INPUT" table="tt1" type="TABLE"/>
    </method-mapping>
    <method-mapping jname="tableParamTest2" pname="tableParamTest2" returns="INTEGER" type="FUNCTION">
      <parameter mode="OUTPUT" table="tt1" type="TABLE"/>
    </method-mapping>
    <method-mapping jname="tableParamTest3" pname="tableParamTest3" returns="INTEGER" type="FUNCTION">
      <parameter mode="INPUT-OUTPUT" table="tt1" type="TABLE"/>
    </method-mapping>

At the caller, a TableParameter instance is passed as argument:
         tableParamTest1(new TableParameter(tt1));
         tableParamTest2(new TableParameter(tt1));
         tableParamTest3(new TableParameter(tt1));
         tableParamTest1(new TableParameter(ht));
         tableParamTest2(new TableParameter(ht));
         tableParamTest3(new TableParameter(ht));

where:
  • if the argument is TABLE, the DMO instance will be passed as single argument to TableParameter.
  • if the argument is TABLE-HANDLE, the handle reference will be passed as single argument to TableParameter.
For table-handle arguments, they will also convert at the table definition as a TableParameter:
def temp-table tt1 field f1 as int.
def var ht as handle.

function tableHandleParamTest1 returns int(input table-handle h).
end.

function tableHandleParamTest2 returns int(output table-handle h).
end.

function tableHandleParamTest3 returns int(input-output table-handle h).
end.

tableHandleParamTest1(input table tt1).
tableHandleParamTest2(output table tt1).
tableHandleParamTest3(input-output table tt1).

tableHandleParamTest1(input table-handle ht).
tableHandleParamTest2(output table-handle ht).
tableHandleParamTest3(input-output table-handle ht).

will convert to:
   Tt1_1_1.Buf tt1 = TemporaryBuffer.define(Tt1_1_1.Buf.class, "tt1", "tt1", false);

   @LegacySignature(type = Type.VARIABLE, name = "ht")
   handle ht = UndoableFactory.handle();

   @LegacySignature(type = Type.FUNCTION, name = "tableHandleParamTest1", returns = "INTEGER", parameters = 
   {
      @LegacyParameter(name = "h", type = "HANDLE", mode = "INPUT")
   })
   public integer tableHandleParamTest1(final TableParameter _h)
   {
      handle h = new handle();

      return function(this, "tableHandleParamTest1", integer.class, new Block((Init) () -> 
      {
         TemporaryBuffer.createDynamicTable(_h, h, true, false);
      }));
   }

   @LegacySignature(type = Type.FUNCTION, name = "tableHandleParamTest2", returns = "INTEGER", parameters = 
   {
      @LegacyParameter(name = "h", type = "HANDLE", mode = "OUTPUT")
   })
   public integer tableHandleParamTest2(final TableParameter _h)
   {
      handle h = new handle();

      return function(this, "tableHandleParamTest2", integer.class, new Block((Init) () -> 
      {
         TemporaryBuffer.createDynamicTable(_h, h, false, true);
      }));
   }

   @LegacySignature(type = Type.FUNCTION, name = "tableHandleParamTest3", returns = "INTEGER", parameters = 
   {
      @LegacyParameter(name = "h", type = "HANDLE", mode = "INPUT-OUTPUT")
   })
   public integer tableHandleParamTest3(final TableParameter _h)
   {
      handle h = new handle();

      return function(this, "tableHandleParamTest3", integer.class, new Block((Init) () -> 
      {
         TemporaryBuffer.createDynamicTable(_h, h, true, true);
      }));
   }

where:
  • LegacyParameter annotation will mark this parameter as a HANDLE
  • TemporaryBuffer.createDynamicTable will associate the local handle instance with the TableParameter instance received as argument:
    • first argument is the local TableParameter instance, received as argument
    • second argument is the local variable handle
    • last two arguments represent flags for the association mode (input and output); when both are true, it is assumed the INPUT-OUTPUT mode.

It will also register the parameters in name_map.xml:

    <method-mapping jname="tableHandleParamTest1" pname="tableHandleParamTest1" returns="INTEGER" type="FUNCTION">
      <parameter jname="h" mode="INPUT" pname="h" type="HANDLE"/>
    </method-mapping>
    <method-mapping jname="tableHandleParamTest2" pname="tableHandleParamTest2" returns="INTEGER" type="FUNCTION">
      <parameter jname="h" mode="OUTPUT" pname="h" type="HANDLE"/>
    </method-mapping>
    <method-mapping jname="tableHandleParamTest3" pname="tableHandleParamTest3" returns="INTEGER" type="FUNCTION">
      <parameter jname="h" mode="INPUT-OUTPUT" pname="h" type="HANDLE"/>
    </method-mapping>

At the caller, a TableParameter instance is passed as argument:
         tableHandleParamTest1(new TableParameter(tt1));
         tableHandleParamTest2(new TableParameter(tt1));
         tableHandleParamTest3(new TableParameter(tt1));
         tableHandleParamTest1(new TableParameter(ht));
         tableHandleParamTest2(new TableParameter(ht));
         tableHandleParamTest3(new TableParameter(ht));

where:
  • if the argument is TABLE, the DMO instance will be passed as single argument to TableParameter.
  • if the argument is TABLE-HANDLE, the handle reference will be passed as single argument to TableParameter.

Table Parameter (class constructors and methods).

Table parameters can be defined (and passed) as TABLE or TABLE-HANDLE. In FWD, when defining such parameters at a class method or constructor, different Java types will be used, depending on the parameter (table or table-handle) and its mode (INPUT, OUTPUT, INPUT-OUTPUT). This allows FWD conversion to keep the Java converted method names (for a legacy class method) as closely as possible to its legacy name, to allow for natural Java method overload and Java method override.

For example, defining these table parameters:
   def temp-table tt1 field f1 as int.
   def var ht as handle.

   method public void tableParamTest(input table tt1).
   end.

   method public void tableParamTest(output table tt1).
   end.

   method public void tableParamTest(input-output table tt1).
   end.

   method public void tableParamTest():
      tableParamTest(input table tt1).
      tableParamTest(output table tt1).
      tableParamTest(input-output table tt1).

      tableParamTest(input table-handle ht).
      tableParamTest(output table-handle ht).
      tableParamTest(input-output table-handle ht).
   end.


will emit this Java code:
   private Tt1_1_1.Buf tt1 = TemporaryBuffer.define(ParamTest2.class, Tt1_1_1.Buf.class, "tt1", "tt1", false);

   @LegacySignature(type = Type.VARIABLE, name = "ht")
   private handle ht = UndoableFactory.handle();

   @LegacySignature(type = Type.METHOD, name = "tableParamTest", parameters = 
   {
      @LegacyParameter(type = "TABLE", table = "tt1", mode = "INPUT")
   })
   public void tableParamTest(final InputTableParameter tt1Buf2)
   {
      internalProcedure(ParamTest2.class, this, "tableParamTest", new Block((Init) () -> 
      {
         TemporaryBuffer.associate(tt1Buf2, tt1);
      }));
   }

   @LegacySignature(type = Type.METHOD, name = "tableParamTest", parameters = 
   {
      @LegacyParameter(type = "TABLE", table = "tt1", mode = "OUTPUT")
   })
   public void tableParamTest(final OutputTableParameter tt1Buf3)
   {
      internalProcedure(ParamTest2.class, this, "tableParamTest", new Block((Init) () -> 
      {
         TemporaryBuffer.associate(tt1Buf3, tt1);
      }));
   }

   @LegacySignature(type = Type.METHOD, name = "tableParamTest", parameters = 
   {
      @LegacyParameter(type = "TABLE", table = "tt1", mode = "INPUT-OUTPUT")
   })
   public void tableParamTest(final InputOutputTableParameter tt1Buf4)
   {
      internalProcedure(ParamTest2.class, this, "tableParamTest", new Block((Init) () -> 
      {
         TemporaryBuffer.associate(tt1Buf4, tt1);
      }));
   }

where:
  • LegacyParameter is used to identify this parameter as TABLE
  • InputTableParameter, OutputTableParameter and InputOutputTableParameter types are used, depending on the parameter's mode.
  • TemporaryBuffer.associate is used to associate the table referenced by the parameter with the instance received as argument:
    • first argument is the local TableParameter instance, received as argument
    • second argument is the target DMO
At the caller, the method overload is again resolved by using the proper Java argument type, depending on its mode:
         tableParamTest(new InputTableParameter(tt1));
         tableParamTest(new OutputTableParameter(tt1));
         tableParamTest(new InputOutputTableParameter(tt1));
         tableParamTest(new InputTableParameter(ht));
         tableParamTest(new OutputTableParameter(ht));
         tableParamTest(new InputOutputTableParameter(ht));

where:
  • InputTableParameter, OutputTableParameter and InputOutputTableParameter types are used, depending on the argument's mode.
  • if the argument is TABLE, the DMO instance will be passed as single argument.
  • if the argument is TABLE-HANDLE, the handle reference will be passed as single argument.
For table-handle arguments, they will also convert at the table parameter definition, depending on its mode:
   def temp-table tt1 field f1 as int.
   def var ht as handle.

   method public void tableHandleParamTest(input table-handle h).
   end.

   method public void tableHandleParamTest(output table-handle h).
   end.

   method public void tableHandleParamTest(input-output table-handle h).
   end.

   method public void tableHandleParamTest():
      tableHandleParamTest(input table tt1).
      tableHandleParamTest(output table tt1).
      tableHandleParamTest(input-output table tt1).

      tableHandleParamTest(input table-handle ht).
      tableHandleParamTest(output table-handle ht).
      tableHandleParamTest(input-output table-handle ht).
   end.

will convert to:
   private Tt1_1_1.Buf tt1 = TemporaryBuffer.define(ParamTest2.class, Tt1_1_1.Buf.class, "tt1", "tt1", false);

   @LegacySignature(type = Type.VARIABLE, name = "ht")
   private handle ht = UndoableFactory.handle();

   @LegacySignature(type = Type.METHOD, name = "tableHandleParamTest", parameters = 
   {
      @LegacyParameter(name = "h", type = "HANDLE", mode = "INPUT")
   })
   public void tableHandleParamTest(final InputTableHandle _h)
   {
      handle h = new handle();

      internalProcedure(ParamTest2.class, this, "tableHandleParamTest", new Block((Init) () -> 
      {
         TemporaryBuffer.createDynamicTable(_h, h);
      }));
   }

   @LegacySignature(type = Type.METHOD, name = "tableHandleParamTest", parameters = 
   {
      @LegacyParameter(name = "h", type = "HANDLE", mode = "OUTPUT")
   })
   public void tableHandleParamTest(final OutputTableHandle _h)
   {
      handle h = new handle();

      internalProcedure(ParamTest2.class, this, "tableHandleParamTest", new Block((Init) () -> 
      {
         TemporaryBuffer.createDynamicTable(_h, h);
      }));
   }

   @LegacySignature(type = Type.METHOD, name = "tableHandleParamTest", parameters = 
   {
      @LegacyParameter(name = "h", type = "HANDLE", mode = "INPUT-OUTPUT")
   })
   public void tableHandleParamTest(final InputOutputTableHandle _h)
   {
      handle h = new handle();

      internalProcedure(ParamTest2.class, this, "tableHandleParamTest", new Block((Init) () -> 
      {
         TemporaryBuffer.createDynamicTable(_h, h);
      }));
   }


where:
  • LegacyParameter annotation will mark this parameter as a HANDLE
  • InputTableHandle, OutputTableHandle and InputOutputTableHandle types are used, depending on the parameter's mode.
  • TemporaryBuffer.createDynamicTable will associate the local handle instance with the TableParameter instance received as argument:
    • first argument is the local TableParameter instance, received as argument
    • second argument is the local variable handle
At the caller, the method overload is again resolved by using the proper Java argument type, depending on its mode:
         tableHandleParamTest(new InputTableHandle(tt1));
         tableHandleParamTest(new OutputTableHandle(tt1));
         tableHandleParamTest(new InputOutputTableHandle(tt1));
         tableHandleParamTest(new InputTableHandle(ht));
         tableHandleParamTest(new OutputTableHandle(ht));
         tableHandleParamTest(new InputOutputTableHandle(ht));

where:
  • if the argument is TABLE, the DMO instance will be passed as single argument.
  • if the argument is TABLE-HANDLE, the handle reference will be passed as single argument.

Dataset Parameter (procedure or user-defined function)

Dataset parameters can be defined (and passed) as DATASET or DATASET-HANDLE. In FWD, both cases emit a DataSetParameter parameter at the procedure or function definition. All modes are supported - INPUT, OUTPUT or INPUT-OUTPUT.

For example, defining these dataset parameters:
def temp-table tt1 field f1 as int.
def dataset ds1 for tt1.
def var hds as handle.

function datasetParamTest1 returns int(input dataset ds1).
end.

function datasetParamTest2 returns int(output dataset ds1).
end.

function datasetParamTest3 returns int(input-output dataset ds1).
end.

datasetParamTest1(input dataset ds1).
datasetParamTest2(output dataset ds1).
datasetParamTest3(input-output dataset ds1).

datasetParamTest1(input dataset-handle hds).
datasetParamTest2(output dataset-handle hds).
datasetParamTest3(input-output dataset-handle hds).

will emit this Java code:
   Tt1_1_1.Buf tt1 = TemporaryBuffer.define(Tt1_1_1.Buf.class, "tt1", "tt1", false);

   final DataSet ds1 = DataSetManager.define("ds1", false, false)
         .forBuffers(tt1)
         .generate();

   @LegacySignature(type = Type.VARIABLE, name = "hds")
   handle hds = UndoableFactory.handle();

   @LegacySignature(type = Type.FUNCTION, name = "datasetParamTest1", returns = "INTEGER", parameters = 
   {
      @LegacyParameter(type = "DATASET", dataset = "ds1", mode = "INPUT")
   })
   public integer datasetParamTest1(final DataSetParameter ds1_1)
   {
      return function(this, "datasetParamTest1", integer.class, new Block((Init) () -> 
      {
         DataSet.associate(ds1_1, ParamTest7.this.ds1, true, false);
      }));
   }

   @LegacySignature(type = Type.FUNCTION, name = "datasetParamTest2", returns = "INTEGER", parameters = 
   {
      @LegacyParameter(type = "DATASET", dataset = "ds1", mode = "OUTPUT")
   })
   public integer datasetParamTest2(final DataSetParameter ds1_1)
   {
      return function(this, "datasetParamTest2", integer.class, new Block((Init) () -> 
      {
         DataSet.associate(ds1_1, ParamTest7.this.ds1, false, true);
      }));
   }

   @LegacySignature(type = Type.FUNCTION, name = "datasetParamTest3", returns = "INTEGER", parameters = 
   {
      @LegacyParameter(type = "DATASET", dataset = "ds1", mode = "INPUT-OUTPUT")
   })
   public integer datasetParamTest3(final DataSetParameter ds1_1)
   {
      return function(this, "datasetParamTest3", integer.class, new Block((Init) () -> 
      {
         DataSet.associate(ds1_1, ParamTest7.this.ds1, true, true);
      }));
   }

where:
  • LegacyParameter is used to identify this parameter as DATASET
  • DataSet.associate is used to associate the dataset referenced by the parameter with the instance received as argument:
    • first argument is the local DataSetParameter instance, received as argument
    • second argument is the target DataSet
    • last two arguments represent flags for the association mode (input and output); when both are true, it is assumed the INPUT-OUTPUT mode.

It will also register the parameters in name_map.xml:

    <method-mapping jname="datasetParamTest1" pname="datasetParamTest1" returns="INTEGER" type="FUNCTION">
      <parameter dataset="ds1" mode="INPUT" type="DATASET"/>
    </method-mapping>
    <method-mapping jname="datasetParamTest2" pname="datasetParamTest2" returns="INTEGER" type="FUNCTION">
      <parameter dataset="ds1" mode="OUTPUT" type="DATASET"/>
    </method-mapping>
    <method-mapping jname="datasetParamTest3" pname="datasetParamTest3" returns="INTEGER" type="FUNCTION">
      <parameter dataset="ds1" mode="INPUT-OUTPUT" type="DATASET"/>
    </method-mapping>

At the caller, a DataSetParameter instance is passed as argument:
         datasetParamTest1(new DataSetParameter(ds1));
         datasetParamTest2(new DataSetParameter(ds1));
         datasetParamTest3(new DataSetParameter(ds1));
         datasetParamTest1(new DataSetParameter(hds));
         datasetParamTest2(new DataSetParameter(hds));
         datasetParamTest3(new DataSetParameter(hds));

where:
  • if the argument is DATASET, the DataSet instance will be passed as single argument to DataSetParameter.
  • if the argument is DATASET-HANDLE, the handle reference will be passed as single argument to DataSetParameter.
For dataset-handle arguments, they will also convert at the dataset parameter definition as a DataSetParameter:
def temp-table tt1 field f1 as int.
def dataset ds1 for tt1.
def var hds as handle.

function datasetHandleParamTest1 returns int(input dataset-handle h).
end.

function datasetHandleParamTest2 returns int(output dataset-handle h).
end.

function datasetHandleParamTest3 returns int(input-output dataset-handle h).
end.

datasetHandleParamTest1(input dataset ds1).
datasetHandleParamTest2(output dataset ds1).
datasetHandleParamTest3(input-output dataset ds1).

datasetHandleParamTest1(input dataset-handle hds).
datasetHandleParamTest2(output dataset-handle hds).
datasetHandleParamTest3(input-output dataset-handle hds).

will convert to:
   Tt1_1_1.Buf tt1 = TemporaryBuffer.define(Tt1_1_1.Buf.class, "tt1", "tt1", false);

   final DataSet ds1 = DataSetManager.define("ds1", false, false)
         .forBuffers(tt1)
         .generate();

   @LegacySignature(type = Type.VARIABLE, name = "hds")
   handle hds = UndoableFactory.handle();

   @LegacySignature(type = Type.FUNCTION, name = "datasetHandleParamTest1", returns = "INTEGER", parameters = 
   {
      @LegacyParameter(name = "h", type = "HANDLE", mode = "INPUT")
   })
   public integer datasetHandleParamTest1(final DataSetParameter _h)
   {
      handle h = new handle();

      return function(this, "datasetHandleParamTest1", integer.class, new Block((Init) () -> 
      {
         DataSet.createDynamicDataSet(_h, h, true, false);
      }));
   }

   @LegacySignature(type = Type.FUNCTION, name = "datasetHandleParamTest2", returns = "INTEGER", parameters = 
   {
      @LegacyParameter(name = "h", type = "HANDLE", mode = "OUTPUT")
   })
   public integer datasetHandleParamTest2(final DataSetParameter _h)
   {
      handle h = new handle();

      return function(this, "datasetHandleParamTest2", integer.class, new Block((Init) () -> 
      {
         DataSet.createDynamicDataSet(_h, h, false, true);
      }));
   }

   @LegacySignature(type = Type.FUNCTION, name = "datasetHandleParamTest3", returns = "INTEGER", parameters = 
   {
      @LegacyParameter(name = "h", type = "HANDLE", mode = "INPUT-OUTPUT")
   })
   public integer datasetHandleParamTest3(final DataSetParameter _h)
   {
      handle h = new handle();

      return function(this, "datasetHandleParamTest3", integer.class, new Block((Init) () -> 
      {
         DataSet.createDynamicDataSet(_h, h, true, true);
      }));
   }

where:
  • LegacyParameter annotation will mark this parameter as a HANDLE
  • DataSet.createDynamicDataSet will associate the local handle instance with the DataSetParameter instance received as argument:
    • first argument is the local DataSetParameter instance, received as argument
    • second argument is the local variable handle
    • last two arguments represent flags for the association mode (input and output); when both are true, it is assumed the INPUT-OUTPUT mode.

It will also register the parameters in name_map.xml:

    <method-mapping jname="datasetHandleParamTest1" pname="datasetHandleParamTest1" returns="INTEGER" type="FUNCTION">
      <parameter jname="h" mode="INPUT" pname="h" type="HANDLE"/>
    </method-mapping>
    <method-mapping jname="datasetHandleParamTest2" pname="datasetHandleParamTest2" returns="INTEGER" type="FUNCTION">
      <parameter jname="h" mode="OUTPUT" pname="h" type="HANDLE"/>
    </method-mapping>
    <method-mapping jname="datasetHandleParamTest3" pname="datasetHandleParamTest3" returns="INTEGER" type="FUNCTION">
      <parameter jname="h" mode="INPUT-OUTPUT" pname="h" type="HANDLE"/>
    </method-mapping>

At the caller, a DataSetParameter instance is passed as argument:
         datasetHandleParamTest1(new DataSetParameter(ds1));
         datasetHandleParamTest2(new DataSetParameter(ds1));
         datasetHandleParamTest3(new DataSetParameter(ds1));
         datasetHandleParamTest1(new DataSetParameter(hds));
         datasetHandleParamTest2(new DataSetParameter(hds));
         datasetHandleParamTest3(new DataSetParameter(hds));

where:
  • if the argument is DATASET, the DataSet instance will be passed as single argument to DataSetParameter.
  • if the argument is DATASET-HANDLE, the handle reference will be passed as single argument to DataSetParameter.

Dataset Parameter (class constructors and methods).

Dataset parameters can be defined (and passed) as DATASET or DATASET-HANDLE. In FWD, when defining such parameters at a class method or constructor, different Java types will be used, depending on the parameter (dataset or dataset-handle) and its mode (INPUT, OUTPUT, INPUT-OUTPUT). This allows FWD conversion to keep the Java converted method names (for a legacy class method) as closely as possible to its legacy name, to allow for natural Java method overload and Java method override.

For example, defining these dataset parameters:
   def temp-table tt1 field f1 as int.
   def dataset ds1 for tt1.
   def var hds as handle.

   method public void datasetParamTest(input dataset ds1).
   end.

   method public void datasetParamTest(output dataset ds1).
   end.

   method public void datasetParamTest(input-output dataset ds1).
   end.

   method public void datasetParamTest():
      datasetParamTest(input dataset ds1).
      datasetParamTest(output dataset ds1).
      datasetParamTest(input-output dataset ds1).

      datasetParamTest(input dataset-handle hds).
      datasetParamTest(output dataset-handle hds).
      datasetParamTest(input-output dataset-handle hds).
   end.


will emit this Java code:
   private Tt1_1_1.Buf tt1 = TemporaryBuffer.define(ParamTest3.class, Tt1_1_1.Buf.class, "tt1", "tt1", false);

   private final DataSet ds1 = DataSetManager.define("ds1", false, false)
         .forBuffers(tt1)
         .generate();

   @LegacySignature(type = Type.VARIABLE, name = "hds")
   private handle hds = UndoableFactory.handle();

   @LegacySignature(type = Type.METHOD, name = "datasetParamTest", parameters = 
   {
      @LegacyParameter(type = "DATASET", dataset = "ds1", mode = "INPUT")
   })
   public void datasetParamTest(final InputDataSetParameter ds1_1)
   {
      internalProcedure(ParamTest3.class, this, "datasetParamTest", new Block((Init) () -> 
      {
         DataSet.associate(ds1_1, ParamTest3.this.ds1);
      }));
   }

   @LegacySignature(type = Type.METHOD, name = "datasetParamTest", parameters = 
   {
      @LegacyParameter(type = "DATASET", dataset = "ds1", mode = "OUTPUT")
   })
   public void datasetParamTest(final OutputDataSetParameter ds1_1)
   {
      internalProcedure(ParamTest3.class, this, "datasetParamTest", new Block((Init) () -> 
      {
         DataSet.associate(ds1_1, ParamTest3.this.ds1);
      }));
   }

   @LegacySignature(type = Type.METHOD, name = "datasetParamTest", parameters = 
   {
      @LegacyParameter(type = "DATASET", dataset = "ds1", mode = "INPUT-OUTPUT")
   })
   public void datasetParamTest(final InputOutputDataSetParameter ds1_1)
   {
      internalProcedure(ParamTest3.class, this, "datasetParamTest", new Block((Init) () -> 
      {
         DataSet.associate(ds1_1, ParamTest3.this.ds1);
      }));
   }

where:
  • LegacyParameter is used to identify this parameter as DATASET
  • InputDataSetParameter, OutputDataSetParameter and InputOutputDataSetParameter types are used, depending on the parameter's mode.
  • DataSet.associate is used to associate the dataset referenced by the parameter with the instance received as argument:
    • first argument is the local dataset Parameter instance, received as argument
    • second argument is the target dataset
At the caller, the method overload is again resolved by using the proper Java argument type, depending on its mode:
         datasetParamTest(new InputDataSetParameter(ds1));
         datasetParamTest(new OutputDataSetParameter(ds1));
         datasetParamTest(new InputOutputDataSetParameter(ds1));
         datasetParamTest(new InputDataSetParameter(hds));
         datasetParamTest(new OutputDataSetParameter(hds));
         datasetParamTest(new InputOutputDataSetParameter(hds));

where:
  • InputDataSetParameter, OutputDataSetParameter and InputOutputDataSetParameter types are used, depending on the argument's mode.
  • if the argument is DATASET, the DataSet instance will be passed as single argument.
  • if the argument is DATASET-HANDLE, the handle reference will be passed as single argument.
For dataset-handle arguments, they will also convert at the dataset parameter definition, depending on its mode:
   def temp-table tt1 field f1 as int.
   def dataset ds1 for tt1.
   def var hds as handle.

   method public void datasetHandleParamTest(input dataset-handle h).
   end.

   method public void datasetHandleParamTest(output dataset-handle h).
   end.

   method public void datasetHandleParamTest(input-output dataset-handle h).
   end.

   method public void datasetHandleParamTest():
      datasetHandleParamTest(input dataset ds1).
      datasetHandleParamTest(output dataset ds1).
      datasetHandleParamTest(input-output dataset ds1).

      datasetHandleParamTest(input dataset-handle hds).
      datasetHandleParamTest(output dataset-handle hds).
      datasetHandleParamTest(input-output dataset-handle hds).
   end.

will convert to:
   private Tt1_1_1.Buf tt1 = TemporaryBuffer.define(ParamTest3.class, Tt1_1_1.Buf.class, "tt1", "tt1", false);

   private final DataSet ds1 = DataSetManager.define("ds1", false, false)
         .forBuffers(tt1)
         .generate();

   @LegacySignature(type = Type.VARIABLE, name = "hds")
   private handle hds = UndoableFactory.handle();

   @LegacySignature(type = Type.METHOD, name = "datasetHandleParamTest", parameters = 
   {
      @LegacyParameter(name = "h", type = "HANDLE", mode = "INPUT")
   })
   public void datasetHandleParamTest(final InputDataSetHandle _h)
   {
      handle h = new handle();

      internalProcedure(ParamTest3.class, this, "datasetHandleParamTest", new Block((Init) () -> 
      {
         DataSet.createDynamicDataSet(_h, h);
      }));
   }

   @LegacySignature(type = Type.METHOD, name = "datasetHandleParamTest", parameters = 
   {
      @LegacyParameter(name = "h", type = "HANDLE", mode = "OUTPUT")
   })
   public void datasetHandleParamTest(final OutputDataSetHandle _h)
   {
      handle h = new handle();

      internalProcedure(ParamTest3.class, this, "datasetHandleParamTest", new Block((Init) () -> 
      {
         DataSet.createDynamicDataSet(_h, h);
      }));
   }

   @LegacySignature(type = Type.METHOD, name = "datasetHandleParamTest", parameters = 
   {
      @LegacyParameter(name = "h", type = "HANDLE", mode = "INPUT-OUTPUT")
   })
   public void datasetHandleParamTest(final InputOutputDataSetHandle _h)
   {
      handle h = new handle();

      internalProcedure(ParamTest3.class, this, "datasetHandleParamTest", new Block((Init) () -> 
      {
         DataSet.createDynamicDataSet(_h, h);
      }));
   }

where:
  • LegacyParameter annotation will mark this parameter as a HANDLE
  • InputDataSetHandle, OutputDataSetHandle and InputOutputDataSetHandle types are used, depending on the parameter's mode.
  • DataSet.createDynamicDataSet will associate the local handle instance with the dataset parameter instance received as argument:
    • first argument is the dataset parameter instance, received as argument
    • second argument is the local variable handle
At the caller, the method overload is again resolved by using the proper Java argument type, depending on its mode:
         datasetHandleParamTest(new InputDataSetHandle(ds1));
         datasetHandleParamTest(new OutputDataSetHandle(ds1));
         datasetHandleParamTest(new InputOutputDataSetHandle(ds1));
         datasetHandleParamTest(new InputDataSetHandle(hds));
         datasetHandleParamTest(new OutputDataSetHandle(hds));
         datasetHandleParamTest(new InputOutputDataSetHandle(hds));

where:
  • if the argument is DATASET, the DataSet instance will be passed as single argument.
  • if the argument is DATASET-HANDLE, the handle reference will be passed as single argument.

Buffer Parameter

Buffer parameters are defined using the FWD converted DMO type, for that table. Passing the parameter just means using the buffer instance as argument for that call.

For example:

def temp-table tt1 field f1 as int.

function bufferTest returns int(buffer b for tt1):
   return b.f1.
end.

create tt1.
tt1.f1 = 10.
bufferTest(buffer tt1).

will convert to:
   Tt1_1_1.Buf tt1 = TemporaryBuffer.define(Tt1_1_1.Buf.class, "tt1", "tt1", false);

   /**
    * External procedure (converted to Java from the 4GL source code
    * in param_test4.p).
    */
   @LegacySignature(type = Type.MAIN, name = "param_test4.p")
   public void execute()
   {
      externalProcedure(ParamTest4.this, new Block((Body) () -> 
      {
         RecordBuffer.openScope(tt1);
         tt1.create();
         tt1.setF1(new integer(10));
         bufferTest(tt1);
      }));
   }

   @LegacySignature(type = Type.FUNCTION, name = "bufferTest", returns = "INTEGER", parameters = 
   {
      @LegacyParameter(type = "BUFFER", buffer = "b", bufferFor = "tt1")
   })
   public integer bufferTest(final Tt1_1_1.Buf _b)
   {
      Tt1_1_1.Buf b = RecordBuffer.defineAlias(Tt1_1_1.Buf.class, _b, "b", "b");

      return function(this, "bufferTest", integer.class, new Block((Body) () -> 
      {
         RecordBuffer.openScope(b);
         returnNormal(b.getF1());
      }));
   }

with the parameter registered also in name_map.xml:
    <method-mapping jname="bufferTest" pname="bufferTest" returns="INTEGER" type="FUNCTION">
      <parameter buffer="b" for="tt1" type="BUFFER"/>
    </method-mapping>

Field Parameter passing

Table fields can be passed as arguments to any call. All conversion is the same. The following example shows how to pass scalar and extent field, to a function call:

def temp-table tt1 field f1 as int field f2 as int extent 5.

function fieldParamTest1 returns int(input i1 as int, input i2 as int extent 5):
end.

function fieldParamTest2 returns int(output i1 as int, output i2 as int extent 5):
end.

function fieldParamTest3 returns int(input-output i1 as int, input-output i2 as int extent 5):
end.

create tt1.
tt1.f1 = 10.
tt1.f2 = 20.

fieldParamTest1(tt1.f1, tt1.f2).
fieldParamTest2(output tt1.f1, output tt1.f2).
fieldParamTest3(input-output tt1.f1, input-output tt1.f2).

In the example above, the function definitions will convert as described in the previous section for extent parameters. At the caller, the conversion will be:
         fieldParamTest1(tt1.getF1(), tt1.getF2());
         fieldParamTest2(wrap(tt1, "f1", integer.class), new OutputExtentField<integer>(tt1, "f2"));
         fieldParamTest3(wrap(tt1, "f1", integer.class, true), new InputOutputExtentField<integer>(tt1, "f2"));

where:
  • for scalar fields:
    • for INPUT mode, the exact field's value will be used
    • for OUTPUT mode, OutputParameter.wrap methods will always be used, where:
      • the first argument is the buffer instance
      • the second argument is the DMO property name
      • the third argument is the DMO property's type
      • the fourth argument is a flag indicating INPUT-OUTPUT mode
  • for extent fields:
    • for INPUT mode, the exact field's value will be used
    • for OUTPUT mode, an OutputExtentField or InputOutputExtentField instance will always be used, where:
      • the first argument is the buffer instance
      • the second argument is the DMO property name
      • the third argument is a flag indicating INPUT-OUTPUT mode

Property Parameter Passing

Extent or scalar class properties (instance or static) can be passed to any call type: RUN, user-defined function class, class method call, etc. At the call target, the parameter definition is defined following the rules presented in the previous section. At the caller, the property argument will be emitted depending on the property's type (scalar, extent, instance or static) and if the property is qualified with a class reference (static or instance):
  • qualified property references (instance or static) can be used only as INPUT arguments.
  • simple property references (instance or static) can be used as INPUT, OUTPUT or INPUT-OUTPUT arguments.
For example, having a oo.param_test class defining these properties:
class oo.param_test.
   def public property p1 as int get. set.
   def public property p2 as int extent 5 get. set.

   def public static property s1 as int get. set.
   def public static property s2 as int extent 5 get. set.

   method public static void propertyParamTest(input i1 as int, input i2 as int extent 5).
   end.

   method public static void propertyParamTest(output i1 as int, output i2 as int extent 5).
   end.

   method public static void propertyParamTest(input-output i1 as int, input-output i2 as int extent 5).
   end.

   method public void testMe().
      propertyParamTest(input p1, input p2).
      propertyParamTest(output p1, output p2).
      propertyParamTest(input-output p1, input-output p2).

      propertyParamTest(input s1, input s2).
      propertyParamTest(output s1, output s2).
      propertyParamTest(input-output s1, input-output s2).

      def var o as oo.param_test.

      propertyParamTest(input o:p1, input o:p2).

      propertyParamTest(input oo.param_test:s1, input oo.param_test:s2).
   end.

end.

will convert the caller like:
         propertyParamTest(getP1(), bulkGetP2());
         propertyParamTest(wrap(integer.class, ObjectOps.thisObject(), "p1"), new OutputExtentParameter<integer>()
         {
            public integer[] getVariable()
            {
               return bulkGetP2();
            }

            public void setVariable(final integer[] newRef)
            {
               bulkSetP2(newRef);
            }
         });
         propertyParamTest(wrap(integer.class, ObjectOps.thisObject(), "p1", true), new InputOutputExtentParameter<integer>()
         {
            public integer[] getVariable()
            {
               return bulkGetP2();
            }

            public void setVariable(final integer[] newRef)
            {
               bulkSetP2(newRef);
            }
         });
         propertyParamTest(getS1(), bulkGetS2());
         propertyParamTest(wrap(integer.class, "oo.param_test", "s1"), new OutputExtentParameter<integer>()
         {
            public integer[] getVariable()
            {
               return bulkGetS2();
            }

            public void setVariable(final integer[] newRef)
            {
               bulkSetS2(newRef);
            }
         });
         propertyParamTest(wrap(integer.class, "oo.param_test", "s1", true), new InputOutputExtentParameter<integer>()
         {
            public integer[] getVariable()
            {
               return bulkGetS2();
            }

            public void setVariable(final integer[] newRef)
            {
               bulkSetS2(newRef);
            }
         });
         propertyParamTest(o.ref().getP1(), o.ref().bulkGetP2());
         propertyParamTest(com.goldencode.testcases.src.oo.ParamTest.getS1(), com.goldencode.testcases.src.oo.ParamTest.bulkGetS2());

where:
  • for INPUT arguments, the getter is called to obtain the property's value.
  • for OUTPUT arguments, this will:
    • use OutputParameter.wrap API call, to prepare a scalar property as argument, where:
      • first argument is the property's type
      • second argument is the object reference (for instance properties) or qualified class name (for static properties)
      • third argument is the legacy property name
      • fourth argument is a flag indicating if this is an INPUT-OUTPUT argument
    • use an anonymous instance of OutputExtentParameter, for extent property arguments

© 2004-2022 Golden Code Development Corporation. ALL RIGHTS RESERVED.