Usage
The Genero BDL reflection API allows you to implement generic code, to introspect program elements at runtime.
In programming, introspection is the ability to describe elements of a program at runtime. This feature allows you to implement common generic code to manipulate program elements the definition of which is not known at compile time.
Program elements such as variables, user-defined types, anonymous types, function references, methods and interfaces can be inspected by the reflection API.
The reflection API can also modify the value of program variables, and add/remove elements or arrays and dictionaries.
Importing the reflect
package
In order to use the reflection API, you need to import the reflect
package in
your module.
The reflection API is provided as a C Extension
module named reflect
, containing a set of classes to manipulale program
elements.
reflect
module must be imported at the beginning of your
.4gl module(s) with:IMPORT reflect
Exception handling
TRY / CATCH
block:DEFINE val reflect.Value
DEFINE rec RECORD ... END RECORD
TRY
LET val = reflect.Value.valueOf( rec )
...
CATCH
DISPLAY "ERROR: Could not use reflect.Value object for var"
RETURN -1
END TRY
The exceptions thrown by the
reflect.*
API can only be caught with a TRY/CATCH
block: If WHENEVER
ERROR CONTINUE
is active and the reflection API throws an exception, the program
stops.
Manipulating variables: reflect.Value
Variables can be inspected and modified with the reflect.Value
class.
reflect.Value
objects must first be created with the
reflect.Value
class methods copyOf()
or valueOf()
:reflect.Value.copyOf( expression )
creates areflect.Value
object that references a copy of the expression passed as parameter. This is typically used to create a value from a literal, to be assigned to areflect.Value
object with theset()
method.reflect.Value.valueOf( variable )
creates areflect.Value
object that references the variable passed as parameter. This is used to describe and/or modify the underlying variable.
Once you have a reflect.Value
object, you can get its corresponding type object
with the getType()
method,
to inspect detailed information about the variable type.
Some reflect.Value
methods are specific to a given type kind of the value object. It is for example
only possible to call the appendArrayElement()
method with a value object of the kind
"ARRAY"
.
For a complete list of methods, see reflect.Value
methods.
Inspecting types: reflect.Type
Types can be inspected with the reflect.Type
class.
reflect.Type
objects must first be created with the reflect.Type.typeOf()
class method
or from a reflect.Value
object with the getType()
method:reflect.Type.typeOf()
can create areflect.Type
object directly from the expression or variable passed as parameter.reflect.Value.getType()
returns thereflect.Type
object representing the type of a existingreflect.Value
object.
Each type belongs to a type kind. Some
methods of the reflect.Type
class apply to certain kind of types, not to all kind
of types.
For a complete list of methods, see reflect.Type
methods.
Inspecting methods: reflect.Method
Methods can be inspected with the reflect.Method
class.
reflect.Method
objects must first be created from a
reflect.Type
object, with the getMethod()
method. The type object must represent a record-type with methods, or an interface.
For a complete list of methods, see The reflect.Method class.
From specific to generic code
The key to write generic code is that the "specific" language elements must be passed over to the
code using the reflection API. For program variables, this is done with the reflect.Value.valueOf()
class
method.
To write a library with the reflection API, you can for example implement a function or method
that takes a reflect.Value
object as parameter, that will be registered locally.
The "specific" code using the library can then pass its variables over for processing.
setVariable()
method registers the variable to be
processed by the find()
method, of the t_find
utility
type:IMPORT reflect
PUBLIC TYPE t_find RECORD
val reflect.Value
END RECORD
PUBLIC FUNCTION (self t_find) setVariable( v reflect.Value ) RETURNS ()
IF v.getType().getKind() != "RECORD" THEN
DISPLAY "ERROR: Can only process records"
EXIT PROGRAM 1
END IF
LET self.val = v
END FUNCTION
PUBLIC FUNCTION (self t_find) find( pattern STRING ) RETURNS (STRING)
DEFINE m reflect.Value
DEFINE t reflect.Type
DEFINE x, fc INTEGER
LET t = self.val.getType()
LET fc = t.getFieldCount()
FOR x=1 TO fc
LET m = self.val.getField(x)
IF m.toString() LIKE pattern THEN
RETURN t.getFieldName(x)
END IF
END FOR
RETURN NULL
END FUNCTION
IMPORT reflect
IMPORT FGL myutils
MAIN
DEFINE r_cust RECORD
pkey INTEGER,
name VARCHAR(50),
addr VARCHAR(200)
END RECORD
DEFINE f myutils.t_find
LET r_cust.pkey = 111
LET r_cust.name = "Mike Rean"
LET r_cust.addr = "145 Sunset St."
CALL f.setVariable( reflect.Value.valueOf(r_cust) )
DISPLAY "Search 1: ", f.find( "xxx" )
DISPLAY "Search 2: ", f.find( "Mike%" )
DISPLAY "Search 3: ", f.find( "%Sunset%" )
END MAIN
Manipulating TEXT/BYTE data
TEXT
and BYTE
variable have methods to manipulate the LOB data,
such as readFile()
.
To write generic code for TEXT/BYTE
variables, use the reflect.Type.isAssignableFrom()
method, to check if the corresponding
reflect.Value
object can be assigned to a TEXT
or
BYTE
variable, assign to a local TEXT
or BYTE
variable with reflect.Value.assignToVariable()
, then call required methods with that
local variable:
IMPORT reflect
FUNCTION loadLobFromFile(v reflect.Value, fn STRING) RETURNS ()
DEFINE tx TEXT, bt BYTE
DEFINE t reflect.Type
LET t = v.getType()
CASE
WHEN t.isAssignableFrom(reflect.Value.valueOf(tx).getType())
CALL v.assignToVariable(tx)
CALL tx.readFile(fn)
WHEN t.isAssignableFrom(reflect.Value.valueOf(bt).getType())
CALL v.assignToVariable(bt)
CALL bt.readFile(fn)
OTHERWISE
DISPLAY "ERROR: Invalid reflect.Value object"
EXIT PROGRAM 1
END CASE
END FUNCTION
FUNCTION main()
DEFINE tx TEXT
DEFINE v_tx reflect.Value
LOCATE tx IN MEMORY
LET v_tx = reflect.Value.valueOf(tx)
CALL loadLobFromFile(v_tx,"data.txt")
DISPLAY "tx = ", tx
END FUNCTION