What are data simplifiers in AWS Blu Age
On mainframe and midrange systems (referred to in the following topic as "legacy" systems), frequently used programming languages such as COBOL, PL/I or RPG provide low-level access to memory. This access focuses on memory layout accessed through native types such as zoned, packed, or alphanumeric, possibly also aggregated through groups or arrays.
A mix of accesses to a given piece of memory, through both typed fields and as direct access to bytes (raw memory), coexists in a given program. For example, COBOL programs will pass arguments to callers as contiguous sets of bytes (LINKAGE), or read/write data from files in the same manner (records), while interpreting such memory ranges with typed fields organized in copybooks.
Such combinations of raw and structured access to memory, the reliance on precise, byte-level memory layout, and legacy types, such as zoned or packed, are features that are neither natively nor easily available in the Java programming environment.
As a part of the AWS Blu Age solution for modernizing legacy programs to Java, the Data Simplifier library provides such constructs to modernized Java programs, and exposes those in a way that is as familiar as possible to Java developers (getters/setters, byte arrays, class-based). It is a core dependency of the modernized Java code generated from such programs.
For simplicity, most of the following explanations are based on COBOL constructs, but you can use the same API for both PL1 and RPG data layout modernization, since most of the concepts are similar.
Main classes
For easier reading, this document uses the Java short names of the AWS Blu Age API interfaces and classes. For more information, see FQN of discussed Java types.
Low level memory representation
At the lowest level, memory (a contiguous range of bytes accessible in a fast, random way) is represented by the Record
interface.
This interface is essentially an abstraction of a byte array of a fixed size.
As such, it provides setters and getters able to access or modify the underlying bytes.
Structured data representation
To represent structured data, such as "01 data items", or "01 copybooks", as found in COBOL DATA DIVISION, subclasses of the RecordEntity
class are used.
Those are normally not written by hand, but generated by the AWS Blu Age modernization tools from the corresponding legacy constructs.
It is still useful to know about their main structure and API, so you can understand how the code in a modernized program uses them.
In the case of COBOL, that code is Java generated from their PROCEDURE DIVISION.
Generated code represents each "01 data item" with a RecordEntity
subclass; each elementary field or aggregate composing it is represented as a private Java field, organized as a tree (each item has a parent, except for the root one).
For illustration purposes, here is an example COBOL data item, followed by the corresponding AWS Blu Age generated code that modernizes it:
01 TST2. 02 FILLER PIC X(4). 02 F1 PIC 9(2) VALUE 42. 02 FILLER PIC X. 02 PIC 9(3) VALUE 123. 02 F2 PIC X VALUE 'A'.
public class Tst2 extends RecordEntity { private final Group root = new Group(getData()).named("TST2"); private final Filler filler = new Filler(root,new AlphanumericType(4)); private final Elementary f1 = new Elementary(root,new ZonedType(2, 0, false),new BigDecimal("42")).named("F1"); private final Filler filler1 = new Filler(root,new AlphanumericType(1)); private final Filler filler2 = new Filler(root,new ZonedType(3, 0, false),new BigDecimal("123")); private final Elementary f2 = new Elementary(root,new AlphanumericType(1),"A").named("F2"); /** * Instantiate a new Tst2 with a default record. * @param configuration the configuration */ public Tst2(Configuration configuration) { super(configuration); setupRoot(root); } /** * Instantiate a new Tst2 bound to the provided record. * @param configuration the configuration * @param record the existing record to bind */ public Tst2(Configuration configuration, RecordAdaptable record) { super(configuration); setupRoot(root, record); } /** * Gets the reference for attribute f1. * @return the f1 attribute reference */ public ElementaryRangeReference getF1Reference() { return f1.getReference(); } /* * * Getter for f1 attribute. * @return f1 attribute */ public int getF1() { return f1.getValue(); } /** * Setter for f1 attribute. * @param f1 the new value of f1 */ public void setF1(int f1) { this.f1.setValue(f1); } /** * Gets the reference for attribute f2. * @return the f2 attribute reference */ public ElementaryRangeReference getF2Reference() { return f2.getReference(); } /** * Getter for f2 attribute. * @return f2 attribute */ public String getF2() { return f2.getValue(); } /** * Setter for f2 attribute. * @param f2 the new value of f2 */ public void setF2(String f2) { this.f2.setValue(f2); } }
Elementary fields
Fields of class Elementary
(or Filler
, when unnamed) represent a "leaf" of the legacy data structure.
They are associated with a contiguous span of underlying bytes ("range") and commonly have a type (possibly parameterized) expressing how to interpret and modify those bytes (by respectively "decoding" and "encoding" a value from/to a byte array).
All elementary types are subclasses of RangeType
. Common types are:
COBOL Type | Data Simplifier Type |
---|---|
|
|
|
|
|
|
|
|
Aggregate fields
Aggregate fields organize the memory layout of their contents (other aggregates or elementary fields). They do not have an elementary type themselves.
Group
fields represent contiguous fields in memory.
Each of their contained fields are laid out in the same order in memory, the first field being at offset 0
with respect to the group field position in memory, the second field being at offset 0 + (size in bytes of first field)
, etc.
They are used to represent sequences of COBOL fields under the same containing field.
Union
fields represent multiples fields accessing the same memory.
Each of their contained fields are laid out at offset 0
with respect to the union field position in memory.
They are for example used to represent the COBOL "REDEFINES" construct (the first Union children being the redefined data item, the second children being its first redefinition, etc.).
Array fields (subclasses of Repetition
) represent the repetition, in memory, of the layout of their child field (be it an aggregate itself or an elementary item).
They lay out a given number of such child layouts in memory, each being at offset index * (size in bytes of child)
.
They are used to represent COBOL "OCCURS" constructs.
Primitives
In some modernization cases, "Primitives" may also be used to present independent, "root"
data items. Those are very similar in use to RecordEntity
but don't come from it,
nor are based on generated code. Instead they are directly provided by the AWS Blu Age runtime as
subclasses of the Primitive
interface. Examples of such provided classes are
Alphanumeric
or ZonedDecimal
.
Data binding and access
Association between structured data and underlying data can be done in multiple ways.
An important interface for this purpose is RecordAdaptable
, which is used to obtain a Record
providing a "writable view" on the RecordAdaptable
underlying data.
As we will see below, multiple classes implement RecordAdaptable
.
Reciprocally, AWS Blu Age APIs and code manipulating low-level memory (such as programs arguments, file I/O records, CICS comm area, allocated memory...) will often expect a RecordAdaptable
as an handle to that memory.
In the COBOL modernization case, most data items are associated with memory which will be fixed during the life time of the corresponding program execution.
For this purpose, RecordEntity
subclasses are instantiated once in a generated parent object (the program Context), and will take care of instantiating their underlying Record
, based on the RecordEntity
byte size.
In other COBOL cases, such as associating LINKAGE elements with program arguments, or modernizing the SET ADDRESS OF construct, a RecordEntity
instance must be associated with a provided RecordAdaptable
.
For this purpose, two mechanisms exist:
-
if the
RecordEntity
instance already exists, theRecordEntity.bind(RecordAdaptable)
method (inherited fromBindable
) can be used to make this instance "point" to thisRecordAdaptable
. Any getter or setter called on theRecordEntity
will then be backed (bytes reading or writing) by the underlyingRecordAdaptable
bytes. -
if the
RecordEntity
is to be instantiated, a generated constructor accepting aRecordAdaptable
is available.
Conversely, the Record
currently bound to structured data can be accessed.
For this, RecordEntity
implements RecordAdaptable
, so getRecord()
can be called on any such instance.
Finally, many COBOL or CICS verbs require access to a single field, for reading or writing purpose.
The RangeReference
class is used to represent such access.
Its instances can be obtained from RecordEntity
generated getXXXReference()
methods (XXX
being the accessed field), and passed to runtime methods.
RangeReference
is typically used to access whole RecordEntity
or Group
, while its subclass ElementaryRangeReference
represents accesses to Elementary
fields.
Note that most observations above apply to Primitive
subclasses, since they strive at implementing similar behavior as RecordEntity
while being provided by the AWS Blu Age runtime (instead of generated code).
For this purpose, all subclasses of Primitive
implement RecordAdaptable
, ElementaryRangeReference
and Bindable
interfaces so as to be usable in place of both RecordEntity
subclasses and elementary fields.
FQN of discussed Java types
The following table shows the fully qualified names of the Java types discussed in this section.
Short Name | Fully Qualified Name |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|