Project

General

Profile

Data Types

All data type support is provided at the Progress v10.2B level. DATETIME, DATETIME-TZ, INT64 are added to supported list. Some data types from Progress v10 or later are not supported at this time. For example, CLASS, and LONGCHAR are not supported.

This chapter describes the data types that are available for use in business logic. There are certain data types (BLOB, CLOB) that are only available as a field in a database (or a temp-table). Such types are not documented in this chapter. Please see Part 3 or Part 5 for details.

In all cases, WIDGET-HANDLE is treated as a synonym for HANDLE. There is no separate processing or differentiation for WIDGET-HANDLE in Progress 4GL (as far as is known to the authors) or in FWD.

Constants

Most constants are the literal forms of the primitive data types. For example, the text 14 in a 4GL program would be lexicographically determined to be an integer literal, which is a constant (an unchanging value). There are some special cases, where the Progress 4GL provides symbolic constants or constants that are not forms of specific data types.

Progress 4GL transparently treats the literal forms of primitive data types as instances of those respective types. Not all primitive data types have literal forms in Progress. In particular, the following types have no literal forms: CLASS, COM-HANDLE, HANDLE, LONGCHAR, MEMPTR, RAW, RECID, ROWID and WIDGET-HANDLE.

The following describes how each literal is emitted, but for full details on the behavior of each data type, please see the Variables section of this chapter.

CHARACTER

All known forms of string literals are supported in FWD. Both the ' and " forms are handed, including all the possible escaped quotes and other escaped characters.

Use of international characters is supported as long as they are properly interpreted by the default locale of the JVM in which FWD runs. This default locale (e.g. set through the LANG environment variable on Linux) must be defined for the JVM in which the FWD conversion process runs as well as the JVM in which the runtime environment runs.

As in Progress 4GL, the preprocessor is responsible for certain transformations. Among other changes, preprocessor argument/definition references are expanded, unescaped newlines are removed and escaped null characters modify the rest of the string. For full details, please see the String Literal section of the Preprocessor chapter in Part 1 of this book. The transformed result is written to the cache file, which is the fully preprocessed version of the source file that is read as input by the Progress 4GL parsing logic of FWD.

The subsequent FWD conversion converts a Progress 4GL compatible string to the equivalent Java source string, by stripping off enclosing quotes (including any string options) and converting the contents (including rewriting all escape sequences). Note that the output format is designed to be written to a Java source file, which requires a different approach than writing a string that can be used at runtime.

Escaped characters (using either the tilde ~ or the backslash \ character) and special formations are converted to native representations and any characters that need to be escaped in Java are so escaped. In particular, almost all usage of the backslash is compatible between Progress and Java, so only those constructs that must be different will be handled differently.

The following conversions will be made:

Input Characters Output Characters Description
~b \\b backspace
~t \\t horizontal tab
~n \\n linefeed
~f \\f formfeed
~r \\r carriage return
~" \\" double quote
~' \\' single quote
~\ \\ backslash
~~ ~ escaped tilde converts to a single tilde character
~{ { left curly brace
~E \\033 ASCII escape character
~nnn \\nnn octal escape sequence (nnn is a 3 digit octal number)
'' \\' single quote (only when inside a string that was enclosed by single quotes)
"" \\" double quote (only when inside a string that was enclosed by double quotes)
\~ ~ escaped tilde converts to a single tilde character
\\ \\ backslash
\{ { left curly brace
\E \\033 ASCII escape character

Any other escape sequence that is unrecognized will result in the tilde or backslash being dropped and the following character output into the string unchanged which is the same behavior as Progress 4GL.

The final result when output will be enclosed inside a pair of double quotes. Note that a Progress 4GL string literal is always emitted as a Java string literal, even if the result is only a single character. There is no difference in the 4GL between a single character and a string, and FWD maintains this logic for the converted code.

The decision to emit the Java string literal for Progress 4GL string literals is one that makes a direct and simple conversion. However, this has the implication that any 4GL replacement interface that could be called in Progress 4GL with either a character or a string literal, must have method signatures that allow for both String and com.goldencode.p2j.util.character types.

The simple example of the character constant. The following 4GL code:

def var chVar as char initial "The text constant".

Will be converted to Java as:

character chVar = new character("The text constant");

DATE

There is no date literal in Java. A new instance of the com.goldencode.p2j.util.date is created instead. This means that the use of a date literal will generate the date.fromLiteral(...) with no name and no way to modify the value of that instance. It is not stored as a member of the class nor is it stored as a local variable.

All known forms of the date literal are supported in FWD. For details, please see the Date Literals section of the Lexer chapter in Part 1 of this book.

In the most common form of a date literal, the literal always has a value that is not unknown. Assuming that the code passes 4GL compilation, the value must be a valid date literal. The lexed text for the literal will be converted to a Java string literal (enclosed directly in double quotes) and that string will be passed to the date static method.

This Progress 4GL code:

if my-date-var > 12-31-1999 then

will result in this Java code:

if (_isGreaterThan(myDateVar, date.fromLiteral("12-31-1999"))

The only exception to this conversion is in the special case of the TODAY "function" inside a variable initializer. Variable initializers are documented as always needing to be constants. But there is an undocumented feature that allows TODAY to be used as a "constant". There is a special date static method used to obtain a date instance with today's date: date.today(). The default constructor will quickly build an unknown object.

This Progress 4GL code:

DEFINE VARIABLE my-date-var1 AS DATE INIT TODAY.
DEFINE VARIABLE my-date-var2 AS DATE.

will result in this Java code:

date myDateVar1 = date.today();
date myDateVar2 = new date();

DATETIME and DATETIME-TZ

Like DATE datatype, there is no DATETIME and DATETIME-TZ literals in java language. Each occurrence of such literals will be converted in calls to static methods datetime.fromLiteral() and datetimetz.fromLiteral().

All known forms of the datetime literal are supported in FWD. For details, please see the Date Literals section of the Lexer chapter in Part 1 of this book.

In the most common form of a datetime literal, the literal always has a value that is not unknown. Assuming that the code passes 4GL compilation, the value must be a valid datetime literal. The lexed text for the literal will be converted to a Java string literal (enclosed directly in double quotes) and that string will be passed to the date constructor.

This Progress 4GL code:

if (dt-var1 > 2013-07-01T12:32:45.678 or dtz-var1 < 2013-07-01T12:34:56.789+01:00) then

will result in this Java code:

if (_or(_isGreaterThan(dtVar1, datetime.fromLiteral("2013-07-01T12:32:45.678"))
       (_isLessThan(dtzVar1, datetimetz.fromLiteral("2013-07-01T12:32:45.678+01:00"))

The only exception to this conversion is in the special case of the NOW "function" inside a variable initializer. Variable initializers are documented as always needing to be constants. But there is an undocumented feature that allows NOW to be used as a "constant". There is a special datetime static method used to obtain a datetime instance with current timestamp: datetime.now(). The default constructors will quickly build unknown objects.

This Progress 4GL code:

DEFINE VARIABLE my-datetime-var1 AS DATETIME INIT NOW.
DEFINE VARIABLE my-datetime-var2 AS DATETIME.
DEFINE VARIABLE my-datetime-tz-var1 AS DATETIME-TZ INIT NOW.
DEFINE VARIABLE my-datetime-tz-var2 AS DATETIME-TZ.

will result in this Java code:

date myDatetimeVar1 = datetime.now();
date myDatetimeVar2 = new datetime();
date myDatetimeTzVar1 = datetimetz.now();
date myDatetimeTzVar2 = new datetimetz();

DECIMAL

At this time, the Java double literal is used as the replacement for the Progress 4GL decimal literal. This has two important limitations (see below). In a future version of FWD the problem cases will be converted in a different form. Where converting to a double does not cause a problem, it is better to convert using Java literals since this increases readability of code and is closer to how Java would be hand-coded.

The text representation of a Progress 4GL decimal literal and the Java double is almost always compatible. This means that wherever a 4GL literal occurs, the corresponding location in the converted Java program will have the same text. Given this input in 4GL:

sales-price gt 932.33

This output would be generated in Java:

isGreaterThan(salesPrice, 932.33)

The only exception is when the Progress 4GL decimal literal has no decimal point. This might happen in any case where a decimal literal is expected, but the programmer encoded it as an integer literal. The result is still a decimal literal, but it has no decimal point. In this case, the resulting Java literal text will have a 'D' suffix which tells the Java compiler that the value is a double rather than an int. For example:

DEFINE VARIABLE sales-price AS DECIMAL INIT 950.

This would be generated in Java:

decimal salesPrice = new decimal(950D);

The double is a data type based on the 64-bit IEEE 754 floating point type. It is a binary representation of a decimal number. It stores an approximation of a real number inside a 64-bit data structure. It has 3 "fields" within the 64-bits of memory dedicated to storing its value.

The first field is a 1-bit value for the sign.

The second field is a 52-bit binary value for the significand (often also called the mantissa). Actually, the value is always stored with an assumed 1 in the most significant bit, so although that bit is never stored, the real precision is 53-bits. The significand stores a number that will be multiplied by an exponent to get the numeric value being represented. It stores all the digits (both to the left and to the right of the decimal point) as a binary number whose magnitude will be determined by the exponent (see field 3).

The third field is an 11-bit base-2 exponent. This means that the exponents from -1022 through positive 1023 can be represented. This is a binary (base-2) exponent. This means that the multiplier (which determines the magnitude of the significand) is 2 to the power of the exponent. For example, if the exponent is 2, the multiplier is 2 2 , or 4. If the exponent is -4, then the multiplier 2 -4 or 1/16.

The actual number being represented is calculated from the 3 fields as follows:

value = sign * significand * 2 exponent

This approach can only accurately store two kinds of numbers: integers (whole numbers) that fit within its range and numbers whose fractional portion (the multiplier) has a denominator that is a power of 2. Any other number is stored as an approximation. For example, representing the fraction 1/10 in base-2 results in a repeating fraction. This is the same conceptually as 1/3 in base-10, which is the repeating number .333333...

This means that there are "gaps" in the numbers that can be accurately represented. These gaps can lead to errors in math. Some errors that occur in comparisons of floating point numbers can be corrected using an "epsilon" value (the smallest possible difference in values for the floating point format) such that numbers that have slightly different binary representations but which actually refer to the same number, can be detected. But epsilon is only useful in comparison operations.

If a number cannot be properly represented, then arithmetic operations may lead to incorrect results. Although there is no direct usage of double math in the FWD runtime or in the converted code, a problem occurs when a value that is appears in the source code as one value but which cannot be properly represented as a binary value in memory, can cause a subtle problem wherever that double literal is used. Ultimately, the double value will be converted into a com.goldencode.p2j.decimal (which uses the reliable BigDecimal internally instead of the double) when math is done, but that conversion will occur using a binary floating point value that may differ from the original text of the source code. These problems will be rare (such problems don't exist for simple numbers such as 1 / 10), but they are possible.

The first limitation is based on the number total number of significant digits that must be stored. The Java primitive double can only reliably store 15 to 17 decimal digits. It varies based on the specific number being encoded. Since a Progress 4GL decimal can store up to 50 significant digits, there is a mismatch.

The second limitation is inherent in the floating point nature of the Java double. A deviation can occur where the inexact representation of Java's double causes two (different but close) constants in the source program to be determined as the same number when they really aren't. This can only occur with numbers that exceed the maximum number of significant digits. For example, in Java, the following expression will evaluate to true:

343753.04000000004D == 34375304000000006D

In Progress 4GL, these constants can be distinguished. In particular, Progress 4GL will honor up to 11 digits to the right of the decimal point. In Java, as long as the value is within the 15 to 17 digit limit, the double will work. As an alternative, instances of the decimal class can be instantiated from a string literal. At this time, FWD does not detect and handle such cases.

The decision to emit the Java double literal for Progress 4GL decimal literals is one that makes a direct and simple conversion. However, this has the implication that any 4GL replacement interface that could be called in Progress 4GL with either a decimal or a double literal, must have method signatures that allow for both double and com.goldencode.p2j.util.decimal types.

INTEGER

Progress 4GL integer literals are signed 32-bit values that range which has a minimum value of -2147483648 and a maximum value of 2147483647. The text representation of such literal is identical in both Progress 4GL and Java. The Java int literal is an exact match. This means that wherever a 4GL literal occurs, the corresponding location in the converted Java program will have the same text. The common usage case:

if i < 6

This output would be generated in Java:

if (_isLessThan(i, 6))

The decision to emit the Java int literal for Progress 4GL integer literals is one that makes a direct and simple conversion. However, this has the implication that any 4GL replacement interface that could be called in Progress 4GL with either a integer or a int literal, must have method signatures that allow for both int and com.goldencode.p2j.util.integer types.

INT64

Progress 4GL int64 literals are signed 64-bit values that range which has a minimum value of -9223372036854775808 and a maximum value of 9223372036854775807 which are big enough not to fit in interger's 32-bit space. The text representation of such literal is identical in both Progress 4GL and Java. The Java long literal is an exact match. This means that wherever a 4GL literal occurs, the corresponding location in the converted Java program will have the same text. The common usage case:

if i < 6000000000

This output would be generated in Java (note the long L suffix):

if (_isLessThan(i, 6000000000L))

The decision to emit the Java long literal for Progress 4GL int64 literals is one that makes a direct and simple conversion. However, this has the implication that any 4GL replacement interface that could be called in Progress 4GL with either a int64 or a long literal, must have method signatures that allow for both long and com.goldencode.p2j.util.int64 types.

On the other hand, because com.goldencode.p2j.util.int64 is the base class for com.goldencode.p2j.util.integer and in java int is automatically widened to long the the matrix of integer-typed signatures of the methods can be greatly simplified.

LOGICAL

Progress 4GL logical literals have the following 4 possible conversions:

Progress 4GL logical Literal Java boolean Literal
false false
no false
true true
yes true

No abbreviations are possible in the Progress 4GL forms, though the Progress 4GL literals can be uppercase or mixed case (they are case-insensitive). The FWD lexer handles any text input differences based on case. The result is converted to the Java boolean literal, which is an exact match. This means that wherever a 4GL literal occurs, the corresponding location in the converted Java program will have true or false depending on the 4GL input.

The decision to emit the Java boolean literals for Progress 4GL logical literals is one that makes a direct and simple conversion. However, this has the implication that any 4GL replacement interface that could be called in Progress 4GL with either a logical or a boolean literal, must have method signatures that allow for both boolean and com.goldencode.p2j.util.logical types.

Unknown Value

In Progress 4GL, there is a special literal dedicated to representing the unknown value: the ? character. At first glance, it seems that using the Java null literal is a close match. In both cases, the type of the literal is unspecified but can be determined by context. A problem occurs with how Java treats null. De-referencing null and passing null to many APIs will cause a NullPointerException. This changes the flow of control and is very different from how Progress 4GL handles unknown value. In Progress 4GL, use of unknown value will silently propagate through expressions with no ill effects and no control flow changes. This means that to use null as a replacement for unknown value, null checks would have to be placed throughout the code and many parts of the converted code would need to be re-factored to handle all the possible cases. This would result in a significant increase in the complexity of the converted code, with an accompanying decrease in readability. For this reason, Java class instances are being used internalize the state of being set to the unknown value.

In addition, certain use cases have been converted without a direct replacement for the unknown value literal.

The following is the table of possible conversions when ? is the right operand (rvalue) to the assignment operator. The lvalue is the left operand of the assignment operator, the resource which is being assigned to.

lvalue Type lvalue Extent lvalue is Element or Array Java Result
variable 1 (not an array) n/a variable.setUnknown()
variable > 1 (array) Element (this is a reference to a specific element of the array, like variable[4]) Emits as a call to a worker routine in the com.goldencode.p2j.util.ArrayAssigner class.

import static com.goldencode.p2j.util.ArrayAssigner.*;
...
assignSingleUnknown(variable, index)
variable > 1 (array) Array (this is a reference to the entire array like variable) Emits as a call to a worker routine in the com.goldencode.p2j.util.ArrayAssigner class.

import static com.goldencode.p2j.util.ArrayAssigner.*;
...
assignUnknown(variable)
database field n/a n/a A new instance of the correct com.goldencode.p2j.util.BaseDataType subclass will be created with the internal state set to be unknown value. The instance will be passed as a parameter to the setter method for that field (in the buffer being accessed).

If the buffer being used is named customer-order and the field is an integer value named order-num, then the following would be the result:

customerOrder.setOrderNum(new integer());

The following lists how a type-specific unknown value is created:

Type Java Result
character new character()
class Not supported.
com-handle Not supported.
date new date()
datetime new datetime()
datetime-tz new datetimetz()
decimal new decimal()
handle new handle()
integer new integer()
int64 new int64()
logical new logical()
longchar Not supported.
memptr new memptr()
raw new raw()
recid new recid()
rowid rowid.instantiateUnknownRowid()

The result of the above instantiation will be an instance of that type which has the value set as unknown.

Most non-assignment cases use the type-specific instance approach (e.g. new character()). These cases include (but are not limited to) explicit RETURN statements in a user-defined function, parameters to a user-defined function, parameters to a built-in 4GL function and variable initializers.

When used as one operand (left or right) of the EQ (or =) equals operator or NE (or <>) not equal operator, the following conversion occurs:

Operator Java Result
variable EQ ?

or

? EQ variable
Emits as a call to a worker routine in the com.goldencode.p2j.util.CompareOps class.

import static com.goldencode.p2j.util.CompareOps.*;
...
isUnknown(variable)

In some situations, if the expression is being directly used for Java control flow purposes (e.g. if ()) the CompareOps._isUnknown() is used instead of CompareOps.isUnknown(). The _isUnknown() returns a boolean instead of a logical.
variable NE ?

or

? NE variable
Emits as a call to a worker routine in the com.goldencode.p2j.util.CompareOps class.

import static com.goldencode.p2j.util.CompareOps.*;
...
notUnknown(variable)

In some situations, if the expression is being directly used for Java control flow purposes (e.g. if ()) the CompareOps._notUnknown() is used instead of CompareOps.notUnknown(). The _notUnknown() returns a boolean instead of a logical.

In Progress it isn't possible to compare two instances of the unknown value literal, so that case is not handled and does not need to be handled.

The common example usage in 4GL:

if chVar ne ? then
   chVar = ?.
if intVar ne ? then
   intVar = ?.
if decVar ne ? then
   decVar = ?.

converting to Java as:

if (_notUnknown(chVar))
{
   chVar.setUnknown();
}
if (_notUnknown(intVar))
{
   intVar.setUnknown();
}
if (_notUnknown(decVar))
{
   decVar.setUnknown();
}

Any other usage not documented above converts to an instance of com.goldencode.p2j.util.unknown. For example, this is used for comparison operators (except for EQ and NE) since the operator does not need to know the type in order to determine the logical result of a comparison with unknown value. This unknown type is only used in cases where the data type is not necessary.

Compiler Constants

The Progress 4GL compiler provides a small set of poorly documented symbolic constants. When these text symbols are present in a 4GL program, the corresponding value is substituted in as a constant by the compiler.

Progress 4GL Constant 4GL Data Type Value Java Result
WINDOW-DELAYED-MINIMIZE integer 4 CommonWindow.WINDOW_STATE_DELAYED_MINIMIZE
WINDOW-MAXIMIZED integer 1 CommonWindow.WINDOW_STATE_MAXIMIZED
WINDOW-MINIMIZED integer 2 CommonWindow.WINDOW_STATE_MINIMIZED
WINDOW-NORMAL integer 3 CommonWindow.WINDOW_STATE_NORMAL

Unquoted Character Literals in Form Item

The 4GL FORM language statement is a user-interface feature which allows the static definition of a frame in a 4GL source program. In a FORM statement, the individual frame elements are known as form items. Form items can include a wide variety of expressions, the simplest of which is a character literal (a quoted string). When strings are form items, they are included as hard coded portions of the resulting frame.

The Progress 4GL compiler has a quirk in its lexing/parsing which allows an unquoted character literal in a form item. Character literals elsewhere in a 4GL program must always be quoted (enclosed in matched pairs of single or double quotes). Such text would not normally be allowed, but it is silently honored in a form item.

There are two possible compositions: ------ and ++++++. The idea seems to be that these would be allowed as hard coded separators, probably for column displays (DOWN frames). Any form item that consists of one or more contiguous hyphen characters, without any enclosing quotes and without any intervening whitespace, will be treated as if there were enclosing quotes (the text is treated as a string literal). The first non-hyphen character ends the literal. This means that the following are valid: -, --, ---, ----, ----- and so on. Likewise, any form item that consists of one or more contiguous plus characters, without any enclosing quotes and without any intervening whitespace, will be treated as if there were enclosing quotes (the text is treated as a string literal). The first non-plus character ends the literal. This means that the following are valid: +, ++, +++, ++++, +++++ and so on. The two characters cannot be intermixed, which means that -+-+-+ is not valid.

FWD handles this inside the lexer, by properly matching such contexts and silently treating it as a string literal. It will be emitted into the resulting Java source code as a string literal (see the CHARACTER section above).

LOGICAL Type Initializers Encoded as Strings

The Progress 4GL provides the ability to customize a format string for each variable. The logical type format string is in the form of a string literal that includes a text value that represents true and a text value that represents false, each separated by a / character. For example, the default format string is "yes/no" which equates yes with true and no with false. But a user-defined format string can be specified. The value "over/under" would equate over with true and under with false.

As a general rule, logical literals cannot be specified using values customized by a user-defined format string. This means that the following is invalid:

DEFINE VARIABLE  flag AS LOGICAL FORMAT "valid/invalid".

/* this WILL NOT work */
flag = valid.

valid is not recognized by the Progress compiler as a logical literal, so the above code will fail.

Instead, these format strings are used in user-interface processing to accept logical values input as text that matches one of the customized values. This means that a user that types the text valid into a FILL-IN widget that is defined for a logical variable which has a format string of "valid/invalid", will successfully set that logical value to true. In other words, format strings are used for display purposes (both to format the content of a widget and to properly parse the input for a widget), but cannot be used to change the known set of logical literals that can be hard coded into 4GL source code.

As with many things in Progress 4GL, there is an exception to this rule. It is possible to define a variable initializer as a string literal even though the variable's type is logical. In this case, the 4GL environment uses the format string to interpret the initializer string and determine the proper value for initialization of the logical variable.

DEFINE VARIABLE flag1 AS LOGICAL INIT "invalid" FORMAT "valid/invalid".
DEFINE VARIABLE flag2 AS LOGICAL INIT "over" FORMAT "over/under".

The order of the FORMAT and INIT clauses does not matter.

Progress will match against the format string with case-insensitive text. Uppercase, lowercase or mixed case is all equivalent. As is true with many Progress 4GL features, it gets even more complicated. Progress will match exactly or it will also match any unambiguous abbreviated value. Given a format string of the form 'XXX/YYY' then x, X, xx, XX, xxx, XXX all should resolve to true. Likewise, y, Y, yy, YY, yyy, and YYY will resolve to false.

If no format string is available, then the default is made to be 'yes/no' and 'true/false'. Otherwise the specified format is used.

This is not allowed outside of a variable initializer. For example, this code doesn't work:

DEFINE VARIABLE  flag AS LOGICAL FORMAT "valid/invalid".

/* this WILL NOT work */
flag = "valid".

Since strings cannot be assigned to logical types, the above code will fail.

FWD fully honors these behavior. Given this:

DEFINE VARIABLE flag AS LOGICAL INIT "over" FORMAT "over/under".

the result will be this in Java:

logical flag = new logical(true);

The FWD conversion process reads the content of the string literal and matches it using the format string. But this match is always converted to one of the standard Java boolean literals true or false.

Other Non-CHARACTER Type Initializers Encoded as Strings

For CHARACTER and LONGCHAR types, the initializer constant must always be a string literal. Each other data type accepts the equivalent literal for its respective type. An integer variable can accept a numeric literal (either a whole number or a decimal number) as an initializer. Progress also allows these other data types to optionally accept a string literal as an initializer constant. The content of the string literal must match the syntax rules for literals of that type, but otherwise such strings work just as a hard coded literal would work. This allows code such as the following to work:

DEFINE VARIABLE num AS INTEGER INIT "14".

The above results in this Java code:

integer num = new integer(14);

The string will be converted to a result matches the type-specific constant form as if it was coded in the 4GL without the enclosing quotes.

Logical types have special format string aware behavior (see the section above entitled Logical Type Initializers Encoded as Strings).

DATE, DATETIME and DATETIME-TZ Initializer Special Cases

Variable initializers are constants in the source code which provide a hard-coded initial value for the corresponding variable. The only exception to the rule that initializers must be constants, is for date and time related variables. Progress 4GL allows a variable's initializer to be set as the built-in function TODAY or the built-in function NOW.

FWD fully supports the use of TODAY as a DATE variable initializer. The following 4GL:

DEFINE VARIABLE some-day AS DATE INIT TODAY.

will result in the following Java code:

date someDay = date.today();

The default constructor for the com.goldencode.p2j.util.date class creates an instance that is set to the current day at the moment the variable is initialized. This is the exact equivalent of the TODAY built-in function.

The same holds for NOW built-in function of DATETME and DATETIME-TZ types.

Variables

Design Decisions

In hand-written Java code, the use of primitives has many benefits. Performance of calculations and readability of the code are the two most important advantages. For example, in an arithmetic expression, using primitives allows the data to be operated on directly, reducing method calls, temporary variable and making the expression simpler and easier to read.

But there are still reasons that hand-coded Java must sometimes use the Java wrapper classes such as java.lang.Integer instead of primitives like int. An example is the storage of a number in a map or other collection. Primitives aren't treated as the equivalent of an Object reference which is both their strength and their biggest limitation.

A Java replacement for Progress 4GL variables which is fully compatible must address a wide range of language features. To meet that compatibility objective, Java primitives (e.g. int, double) and the Java object wrappers (e.g. Integer, Double) cannot be directly used from converted business logic. To do so is impractical since would cause that converted code to be greatly expanded with unnecessary and complicated code. The best solution is to create a set of Java classes which perfectly duplicate the behavior of the original 4GL type. These FWD wrapper classes hide the complexity needed to provide the features, keeping the converted code (the user of these wrappers) as simple, clean and straightforward as possible.

The following provides details on each of the major 4GL features causing the need for FWD wrappers:

Core Data Type Behavior

Many of the 4GL data types have no Java primitive or Java wrapper class that is directly compatible. For example, the 4GL date has no suitable Java replacement that has the same behavior. Likewise, the 4GL decimal cannot be properly duplicated using a Java double or the java.lang.Double.

Of the 4GL types, only character, logical and integer have Java types which are compatible with the basic behavior. Respectively, the Java replacement options are java.lang.String, boolean or java.lang.Boolean and int or java.lang.Integer. Any other types must be implemented with a FWD class in order to get a compatible result.

Unknown Value

All Progress 4GL types can be set to the unknown value. This means that any Java replacement type must be able to represent all of the type-specific state as well as the special state of "unknown".

The Java primitive int can represent the exact same set of whole numbers which can be represented by the 4GL integer, however it cannot represent the unknown value. The Java int cannot represent any data except for a number and there are no numbers that can be reserved for the purpose of symbolizing unknown, since they must all be used to symbolize the corresponding number that can be represented by the 4GL integer.

The Java primitive boolean only has two values (true and false). The Progress 4GL logical has three values (true, false and unknown).

These are the only two data types that have compatible primitive types in Java (see Core Data Type Behavior above). This means that primitive variables cannot be used as a replacement in converted code without having additional state. For example, each 4GL integer variable could be converted into two variables, an int for the date value and a boolean for whether the variable is unknown. Laying aside the problems of how this breaks all the common best practices for object-oriented programming, this would have severely negative effects on the resulting converted code. All variable usage that could read or modify the state of the unknown value for that variable would have to be refactored to be aware of the additional state variable. Worse yet, any FWD library code or any application code that does not have visibility to allow assignment back to the boolean flag would have to have some other object instance that could be modified to signal to the called code that the boolean flag must be set or cleared. This is just not practical to do, nor would the results be at all acceptable from a readability or maintainability perspective.

An alternate approach would be to use the Java object wrappers as a replacement (String for character, Integer for integer and Boolean for logical). Each of these types can be set to null which in Java is the rough equivalent of unknown value. A mismatch exists since Progress 4GL silently allows unknown value to "propagate" where Java tends to catastrophically fail at runtime (e.g. NullPointerException). For example, in Progress an arithmetic sub-expression will return an unknown value if either or both operands are the unknown value. The entire enclosing expression would finish evaluation and the result would usually be the unknown value. In Java, if auto-boxing was used to allow an Integer to be processed in an arithmetic sub-expression, any null value for that Integer would cause a NullPointerException to be raised, which would abort the processing of the expression. To be more specific, null cannot be assigned to a primitive and any unboxing or usage of a null Java wrapper instance will result in a NullPointerException. When the exception occurs, the flow of control aborts the assignment or expression evaluation in Java, which would break compatibility.

Just as with the forced use of primitives, the converted Java code could be refactored to implement null checks and other necessary null processing to allow expressions and other variable usage to silently tolerate the presence of null references. But in doing so, the code would expand into a complete mess of complicated, impossible to read logic. It would be severely fragile and would remove all benefit of using native Java types, making the result much worse.

This single 4GL feature by itself is enough to justify the design decision to create custom FWD classes to most cleanly replace the 4GL data types.

Other Type-Specific State

Some Progress 4GL data types have additional state that is not present in either Java primitives or Java object wrappers. For example, the decimal type in Progress has a DECIMALS setting which sets the number of significant digits to the right of the decimal point. Likewise, the character type has a CASE-SENSITIVE setting which determines if string comparisons are or are not to honor case-sensitivity.

Just as with unknown value (see Unknown Value above), trying to maintain that state external to the variable is not practical and would have significant negative effects on the resulting converted code.

This causes FWD wrapper classes to be the best solution.

Undo

In Progress 4GL, UNDO is a feature by which the control flow of the application can reset the state of a variable to match a prior snapshot. This is done based on the block structure of the program and there is no explicit application code needed. Using FWD wrapper classes for variables allows the FWD runtime to provide UNDO processing integrated with the block structure of the converted application. This is because the wrappers are object references that are registered for UNDO support at the moment the variable instance is created. Then the FWD runtime can take snapshots and restore prior state exactly as the Progress 4GL runtime would do it, without any additional code to be emitted into the converted application sources. This means that the FWD wrapper approach simplifies the resulting code, by allowing the runtime to handle UNDO without extra code.

This feature requires that the FWD wrapper classes be mutable.

Operator Behavior

Even if the Java primitives or Java object wrappers could be used as replacements for 4GL data types, the using 4GL behavior of comparison and arithmetic operators would make it impossible to use Java operators directly with that data. For example, all of the 4GL operators have specific behavior in regards to unknown value.

J2SE 5.0 and later provides a new language feature for auto-boxing and auto-unboxing. This largely makes the use of Java wrappers transparent (it wraps or "boxes" a primitive into a wrapper when needed and unwraps or "unboxes" a wrapper into a primitive when needed). This makes it possible to write arithmetic and logical expressions directly using Java wrappers like java.lang.Integer.

Unfortunately, the following limitations (which are also quite significant ) apply:

  1. Poor null handling (see Unknown Value above).
  2. The == operator cannot be used because it does not give consistent results when comparing two operands where either one or the both of them are wrappers. This is due to the fact that when this operator is dealing with objects (versus primitives), it is comparing object references and this will generally yield false-negatives in many situations.
  3. Overloaded method signatures can be tricky because method resolution changes with auto-boxing added. These method resolution changes are supposed to be compatible but this remains to be seen.
  4. The combination of auto-boxing and generics means that the ternary operator will auto-cast the 2 expression results to the least common denominator result if their types are different.

As noted in the above sections, many expressions would require significant refactoring to make this work. For example, in the case where a null check fails, all possible assignments that could be forced to the unknown value must be detected. In addition (possibly more important), any side effects of the expression must be triggered in a safe manner. For example, if a function is called as part of an expression, that function may have side effects that the rest of the procedure relies upon. Not only is the refactoring of the expression highly non-trivial but there are very strong reasons to actually allow the expression to execute even in the presence of the unknown value (which is how Progress works today). If null is used as a proxy for the unknown value, this aspect of the behavior cannot be duplicated since Java generates a runtime error for these cases. Since the Jave object wrappers have no way of encoding the unknown value except setting the current reference to null, this eliminates them from being a useful/good solution to this problem.

Auto-boxing/unboxing only works with the built-in Java wrappers (java.lang.*). Since Java has no ability to overload operators (at least at the time of this writing), custom Java methods will be written to implement the exact behavior of 4GL operators. Any use of 4GL operators will be converted to Java method calls. Those methods are coded to accept and return the proper data types.

INPUT-OUTPUT and OUTPUT Parameters

Function or procedure parameters can be modified in the called code. If the parameter has INPUT-OUTPUT or OUTPUT type, any modifications will be visible by the calling code.

Primitives cannot be directly used since in Java there is no such thing as a reference to a primitive. That means that the modifications of primitives passed as parameters cannot be seen by the calling code.

Java object wrappers cannot be used directly since they are immutable. This means that the content of the object passed by reference to a Java object wrapper (e.g. Integer) cannot be modified in the called code. The called code can assign the reference to another object, but as that reference is passed by value in Java, this does not affect the reference in the calling code.

The solution is to create mutable FWD wrapper classes for each data type.

Shared Variables

In Progress 4GL, named variables can be shared between code at different levels of the call stack, without passing the variables as parameters. Somewhere in the call stack the variable is created as DEFINE NEW SHARED VARIABLE (optionally with a GLOBAL context). All other locations more deeply nested in the stack will access that same variable using a DEFINE SHARED VARIABLE statement. All usage of the variable modifies the same instance.

This is a similar behavior to that of INPUT-OUTPUT or OUTPUT parameters. The idea is that all modfications must be visible to all code that has access to the shared variables. This is not possible with primitives or with the Java object wrappers.

The solution is to create mutable FWD wrapper classes for each data type.

Mutable Wrappers

The best solution to the above design problems is to implement custom classes (corresponding to each Progress data type) to handle all of the following:

  1. Stores the data in a form that provides an exact match with the Progress data.
  2. Stores the state of whether this instance is really equal to the unknown value.
  3. Stores any other state necessary to properly process the data type (e.g. case-sensitive for character).
  4. Implements all of the operators and replacements for the Progress built-in functions such that the 100% Progress compatible results are generated. For some types (e.g. integer and logical) this is primarily a matter of handling the unknown value in a transparent manner. For other types, there are other behaviors to implement. For example, with decimal there is the added requirement for proper rounding/truncation to be implemented after each operator and function.
  5. Is mutable.
  6. Provides support to taking a snapshot and for restoring that snapshot to allow UNDO to be fully and transparently implemented.

All of these custom classes will reside in the com.goldencode.p2j.util package. They will all share a common ancestor class of BaseDataType which will be abstract to define a common set of interfaces. Each will have a class name the same as the lowercase data type name (e.g. class com.goldencode.p2j.util.integer for the integer data type). This is unique (does not conflict with the java.lang.*), is easy to read and looks more like a primitive is being used. The down side is that this is an exception to the normal Java-recommended naming rules for classes.

In a perfect world, the exact look of (arithmetic and logical) expressions could be maintained if Java supported operator overloading. Since that is not possible, methods (with intuitive names) will be written to substitute for all operators. Unwrapping will only be needed in those places where the expression result requires a primitive type. Assignments and many method calls will use the object form returned as an expression result. However, when expression results are used directly with a Java language construct (e.g. if/else, array subscripts) or in a Java-provided method that has primitives in the signature, the primitives must be used. In these cases, the custom wrapper object will be unwrapped.

Each of these classes will internally track whether the variable is set to the unknown value. This means that all variables will have a valid (non-null) reference. As long as the conversion code is written with this in mind, null pointer exceptions will be eliminated (from expressions).

By passing mutable references to these FWD wrapper classes as parameters to the methods that replace Progress 4GL functions or procedures. This will allow INPUT-OUTPUT and OUTPUT parameter behavior to be duplicated exactly. To support mutability, all of the classes will provide an assignment-type interface and setters to allow modification of the wrapped data.

CHARACTER

The replacement class is com.goldencode.p2j.util.character. If no initial value is specified in the Progress 4GL source code, the default will be the empty string "".

Progress 4GL uses a single data type to handle both single character values and strings. This will be maintained so that method signatures and expressions require no refactoring.

The data storage inside the character class is handled by java.lang.String. This is robust enough to store any character data that can be stored or processed in the 4GL.

CLASS

Not supported.

COM-HANDLE

This data type contains a reference to an OCX object on the Windows platform. It allows 4GL code to make direct calls to OCX methods and access/change OCX properties.

FWD has a placeholder class com.goldencode.p2j.util.comhandle which is only a stub at this time. It has no real functionality. It will allow some 4GL code to convert and compile, but the runtime backing is not yet there.

DATE

The Progress 4GL date type is based on a julian day number. It is most similar to the "Chronological Julian Day Number" which is a whole number that specifies the number of days since a base date in the Gregorian calendar of 01/01/4712 BC or 01/01/-4713 (which is the date with 0 as its day number). The Progress 4GL implementation is 1day off. It has a base date of 12/31/-4714 for day 0.

The replacement class is com.goldencode.p2j.util.date. If no initial value is specified in the Progress 4GL source code, the default will be the unknown value.

Date processing has been made compatible with Progress including the implementation of controllable Y2K windowing (the start year defaults to 1950). This is equivalent of using the -yy startup parameter or the session:year-offset attribute. For details on how to configure this in FWD, please see the chapter entitled Progress 4GL Runtime Compatibility Options in the book FWD Runtime Installation, Configuration and Administration Guide.

The order of the date fields when reading from a string or when writing to a string defaults to 'mdy' format, but this can be changed using a directory setting. That is equivalent to the -d startup option or the session:date-format attribute in Progress. For details on how to configure this in FWD, please see the chapter entitled Progress 4GL Runtime Compatibility Options in the book FWD Runtime Installation, Configuration and Administration Guide.

The FWD implementation is dependent upon the Gregorian calendar and the Progress 4GL seems to have no concept of different types of calendars (e.g. lunar...) so this is OK. The two implementations (Progress and J2SE) appear to be quite compatible including how they handle the missing days from 10-05-1582 through 10-14-1582. Both implementations allow these dates to be instantiated and both generate a date that is equivalent to 10-15-1582 through 10-24-1582 respectively. This leads to apparent logical contradictions such as 10-14-1582 comparing as greater than 10-15-1582.

The Progress date has no notion of a time of day and the Java java.util.Date is inherently built with this notion (and the notion of a timezone). When being created from any Java generated date source (e.g. an instance of the Date class or the default constructor which accesses the current system time), this timezone specific data must be taken into account and neutered in the resulting stored date. Likewise, whenever using this data, no timezone is used unless one is converting this instance into an instance of java.util.Date, in which case the timezone can be overridden. By default, the JVM timezone will be used for interpreting timezone aware inputs AND for generating any java.util.Date output.

DATETIME

The datetime datatype is composed of a date and the time elapsed from midnight computed in milliseconds. In fact, the implementation puts datetime as the (only) derived class from date having one extra field for time part.

The replacement class is com.goldencode.p2j.util.datetime. If no initial value is specified in the Progress 4GL source code, the default will be the unknown value. datetime will use the time information from a Java java.util.Date if built with the corresponding constructor but, like date, will ignore the extra information about the timezone.

DATETIME-TZ

The next class in date hierarchy is datetimetz. It is composed of a date, the time elapsed from midnight computed in milliseconds and a time offset in minutes from UTC.

The replacement class is com.goldencode.p2j.util.datetimetz. If no initial value is specified in the Progress 4GL source code, the default will be the unknown value. datetimetz will use the time information from a Java java.util.Date and the extra information about the timezone.

DECIMAL

The Progress 4GL decimal data type is signed base-10 number with both integer and fractional portions. Each decimal value has a maximum of 50 significant digits. Up to 10 of those digits can be to the right of the decimal point. Thus the largest value is 99999999999999999999999999999999999999999999999999 and the minimum value is -99999999999999999999999999999999999999999999999999. If all of the 10 possible digits of precision are used to the right of the decimal point, then only 40 digits can be used on the left. Overflows and underflows both cause the same runtime error "Decimal number is too large (536)".

The internal math and representation used by the 4GL runtime is not known. However, some tests of its behavior indicate that there are no obvious signs of the use of binary floating point (e.g. IEEE 754) for either the mathematics or the representation. Certainly, the maximum size of the decimal type is by itself a key reason that standard floating point math is not being used (no common binary floating point implementations provide 50 significant digits). The 4GL implementation has many features in common with fixed point decimal systems.

Floating point math such as IEEE 754 approximates the representation of some real numbers using binary representation of the significant digits (the significand) and a base-2 exponent. This cannot accurately represent all possible decimal numbers. The maximum error introduced by this approximation is known as the epsilon value for the implementation. The epsilon for a given implementation can be calculated programmatically. If no epsilon value is found, then that may suggest that the math system is not binary floating point or that the rounding behavior of the implementation largely eliminates the inaccuracies inherent in the floating point system.

An epsilon is the smallest floating point value that can be added to another floating point value such that the result can be differentiated as not equivalent by the native environment's operators. As with everything else about Progress 4GL decimal support, the Progress and Java differences are substantial.

In Java, the epsilon is defined (by the language specification) as being equal to 2 -52 or 2.22044604925 X 10 -16 . This is an exceedingly small number, especially in comparison with the smallest possible Progress 4GL decimal fraction which is .0000000001. The following 4GL code will identify the epsilon for Progress:

def var possibleEpsilon  as decimal init 1.0.

def var epsilon          as decimal init 0.0.

def var mantissaBits     as integer init 0.

repeat.

 if ((1.0 + possibleEpsilon) = 1.0) then leave.

 epsilon = possibleEpsilon.

 possibleEpsilon = possibleEpsilon / 2.0.

 mantissaBits = mantissaBits + 1.

 display possibleEpsilon format "99999999999.999999999999999" mantissaBits.

end.

message "Epsilon = " + string(epsilon).

message "Mantissa Bits = " + string(mantissaBits).

It turns out that this program will generate an infinite loop. This is because once the possibleEpsilon is reduced down to 0.0000000001 (the smallest representable Progress decimal fraction), the result of the possibleEpsilon / 2.0 sub-expression will always be 0.0000000001 instead of 0.00000000005. Since a difference of 0.0000000001 can be detected as different from any other decimal value, another way of looking at this result is that there is no epsilon value that is "visible" to the programmer. There may be an internal Progress epsilon value but due to the fact that Progress rounds all intermediate results (after each operator runs), the result is always something that cannot be smaller than 0.0000000001 unless it is 0.0@! This means that:

0.0000000001 / 2.0 results in 0.0000000001

0.0000000001 / 3.0 results in 0.0

The important result here is that epsilon testing (the "normalization" of floating point comparisons to detect if something is really different or not) is not an issue for Progress applications because of the rounding/truncation behavior. This suggests that the 4GL decimal is based in fixed point or a close facsimile to fixed point math.

The Java primitive double can easily handle the smallest values that can be represented by a Progress decimal. The Java double is based on a 64-bit IEEE 754 floating point value where the maximum value is 1.7976931348623157 * 10 308 and the minimum value is 4.9 * 10 -324 . There is a problem in duplicating Progress behavior with small numbers near its limit of precision. For example:

0.0000000001 / 2 * 2 = 0.0000000002

In Java this would be calculated as 0.0000000001.

The Progress 4GL rounding behavior for very small numbers is not limited to just the operators but rather it seems that it has an internal floating point representation that is precise to 11 digits. Every time an external representation is converted into this form (whenever accessed from an application via an operator, assignment, etc.), it is always truncated to the lesser of 10 digits (or the value of the decimals option for that variable) and the truncation uses rounding to determine the least significant digit.

Given this rounding implementation on the Java side, the normal epsilon testing is not necessary AND it is reasonably easy to implement the Progress "decimals" option which allows the programmer to specify an arbitrary number of digits of precision that is less than the default (and maximum) 10 digits.

For example:

define var n1 as decimal.
n1 = 0.000000000149 + 0.000000000001.
message string(n1) string(0.00000000005) string(0.00000000015).

This prints:

 .0000000001 .0000000001 .0000000002

Another way to show this, in Progress 4GL, the following expressions are all true:

0.000000000149 + 0.000000000001 = .0000000001
0.00000000005 = .0000000001
0.00000000015 = .0000000002

In this case, the important rounding occurs before the operation rather than after. Any Progress 4GL decimal variables which are sensitive to the boundary between the 10th and 11th digit of precision (to the right of the decimal point) would cause problems in a Java floating point implementation. For example it is possible that the result of a calculation is something like 2.999999999999996 which in Progress would be represented as 3.0 but which is directly represented in Java.

Even though the Java double can have a big exponent values, its significand is limited to 53 bits or ~16 decimal digits. This means that whole number of 17 digits or more can't be saved in double without precision loss. Subtraction of two big numbers that are close to each other can give an incorrect result.

Some of these problems can be somewhat improved by duplicating the Progress 4GL rounding/truncation behavior in the Java environment. But it is not possible to eliminate all differences between Progress 4GL decimal and the Java floating point support.

The conclusion is that Java IEEE floating point support is not suitable for use in replacing the Progress 4GL decimal type. More details of how IEEE 754 floating point works and the two major limitations can be found in the DECIMAL portion of the Constants section of this chapter. The first limitation is that the Java double can only reliably store less than 17 significant digits, whereas the Progress 4GL decimal can reliably store up to 50 significant digits. The second limitation is that the Java double represents many valid real numbers in an inexact manner, causing flawed comparisons, error prone arithmetic and data loss/corruption. The Progress 4GL decimal type does not suffer from these weaknesses.

The replacement class is com.goldencode.p2j.util.decimal. If no initial value is specified in the Progress 4GL source code, the default will be 0.0. Internally, this class will use the java.math.BigDecimal and the scale (which is similar in concept to the 4GL DECIMALS specification) will be managed carefully. The BigDecimal class can handle over 50 significant digits as well as all the scale values (from 0 through 10) that set the number of digits to the right of the decimal point. BigDecimal aware math and comparisons will need to be implemented as well as the proper conversion to/from other types. The result should match the Progress 4GL behavior exactly.

In addition, the com.goldencode.p2j.util.decimal class extends (inherits as a parent class) from com.goldencode.p2j.util.NumberType. This is the same parent class as is used in com.goldencode.p2j.util.integer which means that Java method signatures which can take either a decimal or integer type can be specified as NumberType.

HANDLE

This class is really just a container that holds a backing object. It provides unknown value processing, comparison/assignment operator support and the valid-handle() built-in function implementation. By default there is no contained object, which is the equivalent of a handle being assigned the unknown value.

In Java, the contained object is always one which implements the com.goldencode.p2j.util.WrappedResource interface. A WrappedResource is an object that can be checked for validity (using the isValid() method) and if it is unknown (using the isUnknown() method). Most importantly, the object itself has some other functionality which is not directly accessible from the handle, but must be dereferenced for access

Dereferencing is the act of obtaining the contained object. The method used to dereference the object is Object get(). Since the get() method returns the non-specific java.lang.Object type, the specific type of the contained object will need to be determined on a case by case basis.

As in Progress, the type of the contained object is not known until it is assigned at runtime. That means that there are ways to write 4GL code in which the same handle variable contains different types of resource depending on the control flow of the program at runtime. As such, each dereferencing location in the 4GL code makes an implicit assumption about the contained object's type. In the Java equivalent, each dereferencing location contains a cast to convert the contained resource's type to the equivalent type expected by the Progress 4GL code at that location.

Since different types of contained object can be assigned to the same handle instance, Java generics cannot be used to eliminate the need for a cast at the time of dereferencing.

One of the most common cases of handle usage is to call a method or get/set an attribute value. The handle gets dereferenced when used as a referent for methods and attributes. For example, the following 4GL code:

if hndl:data-type = "Date" then

looks like this in FWD:

if (isEqual(((GenericWidget) hndl.get()).getDataType(), "Date")
{
   ...

INTEGER

The Progress integer is a 32-bit signed integral value whose maximum value is 2147483647 and minimum value is -2147483648. Overflows do not occur. An attempt to wrap around to the minimum or maximum value will automatically promote any intermediary value to int64 or attempted to be stored in a variable or table field an error will be raised. This is not an exact match to the Java primitive int also because for the case where the unknown value is assigned or tested on this variable. For this reason, the Java int cannot be used to duplicate all the functionality of a Progress 4GL integer.

The FWD replacement is com.goldencode.p2j.util.integer which is derived form com.goldencode.p2j.util.int64 class and having storage restrictions to 32-bit values.

INT64

The Progress int64 is a 64-bit signed integral value whose maximum value is 9223372036854775807 and minimum value is -9223372036854775808. Overflows wrap around to the minimum value and underflows wrap around to the maximum value. This is an exact match to the Java primitive long, except for the case where the unknown value is assigned or tested on this variable. For this reason, the Java long cannot be used to duplicate all the functionality of a Progress 4GL int64.

The FWD replacement is com.goldencode.p2j.util.int64 which internally uses a Java long for the value and a Java boolean for tracking if the instance is equivalent to the unknown value.

In addition, the com.goldencode.p2j.util.int64 class extends (inherits as a parent class) from com.goldencode.p2j.util.NumberType. This is the same parent class as is used in com.goldencode.p2j.util.decimal which means that Java method signatures which can take either a decimal, int64 or integer type can be specified as NumberType.

LOGICAL

The Progress logical has multiple "modes" yes/no, true/false and a user defined value1/value2. However, all of these are the exact equivalent of the Java boolean type which has only 2 states, true or false, except for the case where the unknown value is assigned or tested on this variable. For this reason, to properly duplicate the Progress logical type, the com.goldencode.p2j.util.logical class has been created. This class stores two Java boolean members, one for the value and one for determining if the instance is the unknown value.

There are cases in the 4GL where a logical that is set to the unknown value will act the same as a logical that is set to false. An example is when the logical is used for control flow purposes:

if my-logical then ...

But this only occurs in some cases. It is important to note that setting a logical to the unknown value is something very different/distinct from setting it false. The logical operators respond differently to having the unknown value as an operand:

Operator Left Operand Unknown Right Operand Unknown Both Operands Unknown
EQ false false true
NE true true false
LT ? ? false
LE ? ? true
GT ? ? false
GE ? ? true
AND (logical type operands only) false if right operand is false, ? if right operand is true false if left operand is false, ? if left operand is true ?
OR (logical type operands only) true if right operand is true, ? if right operand is false true if left operand is true, ? if left operand is false ?
NOT (logical type operands only) n/a ? n/a

Since the Progress logical really does have 3 states, it must be implemented in a wrapper class.

LONGCHAR

Not supported.

MEMPTR

The Progress memptr type allows a variable to represent a single contiguous portion of memory. That memory can represent anything, but at its core it is simply an array of bytes. Progress does not provide direct access or dereference of bytes in this memory area. There are no operators that provide access. For example, although the memory area is essentially an array of bytes, one cannot directly dereference those bytes using the subscript operator []. Rather, all access is through an API defined by getter and setter built-in functions. These functions invariably take a 1-based index position in the memory area from which to read data or to which to write data.

Since the data in a memptr is essentially hidden away behind this interface, it is convenient to provide a replacement API using Java methods, allowing the data storage to be completely hidden (and implementation-specific). The Progress implementation of a memptr is likely to be a true pointer to a location in memory which is the starting position (byte 1) of a single contiguous area. In the Java-based runtime, this is not backed by a pointer to memory. Instead, it is backed by a byte array.

The Progress memptr is very similar from the 4GL code usage to a Progress variable of type raw. In fact, most of the API works interchangeably with either data type. Since there is so much commonality in Progress, it naturally leads to the fact that the Java implementation is largely located in a single class named com.goldencode.p2j.util.BinaryData. The actual class that implements the memptr-specific behavior is com.goldencode.p2j.util.memptr. This class has BinaryData as its parent class, and in that manner it inherits and leverages common code.

The memptr class implements by default, an uninitialized byte array. The converted 4GL code must allocate memory explicitly. If used before this allocation occurs, an error will be raised. The allocation is a fixed size and it cannot be dynamically changed. There is no explicit limit on length (although the system implicitly limits its length since there is a finite amount of memory). When done using that memory, explicit deallocation is achieved by setting the size to 0. When used, it does not automatically extend its size. In other words, when you write past the current size, an error will occur.

All of these memptr-specific behaviors are duplicated in the FWD implementation.

RAW

The Progress raw type allows a variable to represent a single contiguous portion of memory. That memory can represent anything, but at its core it is simply an array of bytes. Progress does not provide direct access or dereference of bytes in this memory area. There are no operators that provide access. For example, although the memory area is essentially an array of bytes, one cannot directly dereference those bytes using the subscript operator []. Rather, all access is through an API defined by getter and setter built-in functions. These functions invariably take a 1-based index position in the memory area from which to read data or to which to write data.

Since the data in a raw is essentially hidden away behind this interface, it is convenient to provide a replacement API using Java methods, allowing the data storage to be completely hidden (and implementation-specific). The Progress implementation of a raw is likely to be a true pointer to a location in memory which is the starting position (byte 1) of a single contiguous area. In the Java-based runtime, this is not backed by a pointer to memory. Instead, it is backed by a byte array.

The Progress raw is very similar from the 4GL code usage to a Progress variable of type memptr. In fact, most of the API works interchangeably with either data type. Since there is so much commonality in Progress, it naturally leads to the fact that the Java implementation is largely located in a single class named com.goldencode.p2j.util.BinaryData. The actual class that implements the raw-specific behavior is com.goldencode.p2j.util.raw. This class has BinaryData as its parent class, and in that manner it inherits and leverages common code.

The raw class implements by default, a 0-sized byte array. There is no explicit allocation or deallocation of memory, but rather the array automatically extends its size when you write past the current end of the memory area. The maximum size of the memory area is 32 kilobytes. The data can be easily persisted and restored to/from a string representation which will maintain all binary data unchanged inside a BASE64 encoding. This makes a raw most suitable for use in exporting data to files or databases.

All of these raw-specific behaviors are duplicated in the FWD implementation.

RECID

In Progress a recid is a unique identifier for a specific database record. The Progress documentation states this is a 4 byte value (32-bits). It is obtained by using the RECID built-in function for a specific buffer, which will return the identifier for the current record in that buffer or unknown value if there is no record in the buffer. That recid can be used in queries (to obtain that specific record) or otherwise compared with other recid instances.

The Java version of a recid is implemented by an instance of the com.goldencode.p2j.util.recid class, which has a parent class of com.goldencode.p2j.util.integer, which represents a 32-bit whole number. The entire recid implementation is in its parent class except that the recid class has a different default format string (same as in Progress).

In Java, the 32-bit recid value is obtained from the 64-bit id column of the table associated with the buffer. Each converted table has a primary key which is the id column. This is not application data, but is used internally by the persistence layer. The id is a 64-bit whole number which uniquely identifies the specific row in that table. Of great importance is that this identifier is unique across the entire database, not just within that table. The identifier is assigned automatically at row insertion and it will never be null in the database. So long as that record isn't deleted, the id will remain the same.

Since a recid is a 32-bit value where the internal FWD id is a 64-bit value, there is a potential for loss of data when the 64-bit value is truncated to 32-bits. In any case where the upper (most significant) 32-bits of the id column value contains real data, the recid will not contain the correct value.

This issue is somewhat ameliorated by the fact that the persistence layer tracks unused id values and reuses any previous values that are freed up on deletion. This greatly increases the chances that all active id values can fit inside a 32-bit integer without losing data. However, in a case where the database does have more than 4 billion rows, the recid implementation will potentially have problems. While the specific Progress implementation is not known, it is likely that this same limitation exists in the Progress 4GL (it is a 32-bit identifier after all).

In Progress, dumping or exporting database tables does not include the recid values in the dump or export. This means that reloading any dumped or exported data will potentially change the recid values. Developers are cautioned not to write code that depends upon specific recid values.

When data is migrated to FWD, the exported/dumped data is imported and upon each row insertion, new id values are assigned. Since there is no recid data in the dump files, all new id values have no relation to the original Progress recid values. This means that any recid values that were somehow used again in the Java system (for example, as read from some flat file in which the 4GL application recorded the values) will fail to match the proper record.

The Progress behavior in reallocating recid values is not documented and should not be relied upon since it is an arbitrary implementation detail that is known to be subject to change. It is possible that application code has been explicitly or implicitly written with this dependency. Any such reliance upon Progress-specific reallocation behavior is dangerous since the reallocation algorithm is not known or documented and the FWD internal implementation is virtually assured to be different.

ROWID

In Progress a rowid is a unique identifier for a specific database record. The Progress documentation states this is a "byte string" of variable length, depending on the database or data-server in use. It is obtained by using the ROWID built-in function for a specific buffer, which will return the identifier for the current record in that buffer or unknown value if there is no record in the buffer. That rowid can be used in queries (to obtain that specific record) or otherwise compared with other rowid instances.

The Java version of a rowid is implemented by an instance of the com.goldencode.p2j.util.rowid class. This stores the 64-bit rowid value as a java.lang.Long which is obtained from the 64-bit id column of the table associated with the buffer. Each converted table has a primary key which is the id column. This is not application data, but is used internally by the persistence layer. The id is a 64-bit whole number which uniquely identifies the specific row in that table. Of great importance is that this identifier is unique across the entire database, not just within that table. The identifier is assigned automatically at row insertion and it will never be null in the database. So long as that record isn't deleted, the id will remain the same.

Since both the rowid and the internal FWD id are 64-bit whole numbers, this implementation is safe and clean. There is no potential for loss of data when there are more than 4 billion rows in the database (see the RECID section above).

The persistence layer tracks unused id values and reuses any previous values that are freed up on deletion. This means that id values only stable until the record is deleted. Then even if the record is recreated, it may get a different id.

In Progress, dumping or exporting database tables does not include the rowid values in the dump or export. This means that reloading any dumped or exported data will potentially change the rowid values. Developers are cautioned not to write code that depends upon specific rowid values.

When data is migrated to FWD, the exported/dumped data is imported and upon each row insertion, new id values are assigned. Since there is no rowid data in the dump files, all new id values have no relation to the original Progress rowid values. This means that any rowid values that were somehow used again in the Java system (for example, as read from some flat file in which the 4GL application recorded the values) will fail to match the proper record.

The Progress behavior in reallocating rowid values is not documented and should not be relied upon since it is an arbitrary implementation detail that is known to be subject to change. It is possible that application code has been explicitly or implicitly written with this dependency. Any such reliance upon Progress-specific reallocation behavior is dangerous since the reallocation algorithm is not known or documented and the FWD internal implementation is virtually assured to be different.

WIDGET-HANDLE

All instances of this type are treated as instances of the HANDLE type. See that section above for details. This conversion is done very early (in the FWD parser itself) and after that point, there is no further reference to WIDGET-HANDLE as a different thing from HANDLE.

Variable Definitions

Summary

There are 10 language constructs in the Progress 4GL which have the result of defining named variables in business logic. The following table summarizes the FWD support for each:

4GL Language Feature Description Supported
ASSIGN Database Trigger Parameter A database trigger that is invoked when the associated field is assigned, passing the old field's value as a named parameter. Specified as a TRIGGER PROCEDURE or in an ON statement. No
Constructor Parameter Object-oriented 4GL extensions allow class definitions. Class definitions may contain a CONSTRUCTOR statement (a special method called to create a new instance of the object). Parameters to the constructor are local variables that are scoped to that method. No
DEFINE PARAMETER Specifies a parameter for an external procedure or internal procedure. Yes
DEFINE PROPERTY A readable and/or writable data member of a Progress 4GL class. No
DEFINE VARIABLE   Yes
Format Phrase with AS or LIKE   Yes
Function Parameter Specifies a parameter for a user-defined function. Yes
MESSAGE SET/UPDATE with AS or LIKE   Yes
Method Parameter   No
Property Setter Parameter   No

ASSIGN Database Trigger Parameter

A database trigger that is invoked when the associated field is assigned, passing the old field's value as a named parameter. Specified as a TRIGGER PROCEDURE or in an ON statement.

Not supported.

Constructor Parameter

Object-oriented 4GL extensions allow class definitions. Class definitions may contain a CONSTRUCTOR statement (a special method called to create a new instance of the object). Parameters to the constructor are local variables that are scoped to that method.

Not supported.

DEFINE PARAMETER

This statement specifies a parameter for an external procedure or internal procedure. This statement determines the data type, mode and ordinal position in the procedure's signature. In particular, the ordinal position is determined by the order of the DEFINE PARAMETER statements, where the 1st DEFINE PARAMETER statement in the procedure is the 1st parameter and so forth. To invoke the procedure, the corresponding parameter(s) must be specified in the RUN statement (with the same type, mode and order as defined in the procedure itself).

At a high level, Progress 4GL has 7 main forms of this statement. The following summarizes FWD support for these forms:

Form Description Supported Notes
DEFINE <mode> PARAMETER <parm_name> { AS | LIKE } <primitive_type> [variable_options]. Defines a parameter in which the caller will pass a reference to a variable (INPUT-OUTPUT or OUTPUT mode) or a value of an expression (@INPUT* mode). Yes The following are NOT supported:
     DLL and UNIX shared library access (RETURN mode, data types)
     data types which are not yet supported by FWD (see the Variables section of this chapter)
     EXTENT (array) parameters
     ActiveX control event procedures
DEFINE <mode> PARAMETER <parm_name> AS [CLASS] <class_name> [EXTENT <num_elements>]. Defines a parameter that is an instance of the specified class. No  
DEFINE PARAMETER BUFFER <buffername> FOR [TEMP-TABLE] <table> [PRESELECT]. Defines a parameter in which the caller will pass a reference to a buffer (always INPUT-OUTPUT mode) that accesses a temp-table or database table. Yes See Part 5 of this book for details.
DEFINE <mode> PARAMETER TABLE FOR <temp_table_name> [table_options]. Defines a parameter in which the caller will pass a temp-table by reference or by value. Yes See Part 5 of this book for details.
DEFINE <mode> PARAMETER TABLE-HANDLE <temp_table_handle> [table_options]. Defines a parameter in which the caller will pass a handle to a temp-table. No See Part 5 of this book for details.
DEFINE <mode> PARAMETER DATASET FOR <dataset_name> [table_options]. Defines a parameter in which the caller will pass a Pro-Dataset by reference or by value. No See Part 5 of this book for details.
DEFINE <mode> PARAMETER DATASET-HANDLE <dataset_handle> [table_options]. Defines a parameter in which the caller will pass a handle to a Pro-Dataset. No See Part 5 of this book for details.

This section of the book will only describe how the common or "variable" form of this statement is converted. When the CLASS form is supported, this section will also describe that support. All other forms are database-related and thus are described in Part 5 of this book.

The supported modes are INPUT, INPUT-OUTPUT and OUTPUT. RETURN is not supported (in Progress 4GL it is only used for DLL access and FWD does not provide DLL access yet). All data types which are supported in FWD as variables can be used as parameter types. The use of both AS and LIKE are supported.

The following is a simple 4GL example showing parameters of each supported mode type and the core options:

define input        parameter txt1  as character.
define input-output parameter num2  as integer.
define output       parameter dat3  as date.
define input        parameter txt4  as character  case-sensitive.
define input-output parameter dec5  as decimal    decimals 2   no-undo.
define output       parameter log6  as logical    initial true.

If this code is in an external procedure, the resulting Java code will look like this (the comments have been added by hand to make it easier to understand):

public void execute(final character _txt1, final integer num2, final date dat3,
                    final character _txt4, final decimal dec5, final logical log6)
{
   externalProcedure(new Block()
   {
      // these member variables are copies of input parameters that were passed in, any edits
      // will not be "visible" in the calling code

      character txt1 = new character(_txt1);

      character txt4 = new character(_txt4);

      public void init()
      {
         // all parameters are UNDO by default; each undoable variable must be registered here
         // and all NO-UNDO variables must be excluded
         TransactionManager.register(txt1, num2, dat3, txt4, log6);

         // output parameters must be initialized to their default value or to the value given
         // in an initializer option (for date variables the default is the unknown value)
         dat3.assign(date.instantiateUnknownDate());
         log6.assign(new logical(true));

         // options other than initializers
         txt4.setCaseSensitive(true);
         dec5.setPrecision(2);

         // any INPUT-OUTPUT or OUTPUT parameters that are NO-UNDO will be configured here
         TransactionManager.deregister(new Undoable[]
         {
            dec5
         });
      }

      public void body()
      {
         // all parameters can be accessed here by name as needed
      }

   });
}

Parameters are emitted the same way for both internal procedures and external procedures. The above example is of an external procedure, but for an internal procedure, only the method name would be different (instead of execute() it would be named a valid Java name based on the 4GL internal procedure name) and the externalProcedure() call would be changed to an internalProcedure() call.

All parameters are final. Since the replacement for 4GL assignment in FWD calls a method (usually BaseDataType.assign()), there are no conditions in which a named parameter reference must be assigned a different reference using the Java assignment operator (=). This means that final can be used for all parameters. The primary requirement for doing this is to allow all inner classes to be able to directly refer to these parameters. Since all the 4GL block behavior is implemented using anonymous inner classes, this simplifies the resulting code.

Each data type will be directly mapped from the 4GL type to the FWD replacement type.

The FWD conversion uses its 4GL to Java name conversion routines to create a unique Java variable name for each parameter, based on the original 4GL parameter name. All references to that parameter will likewise use that same converted name. For details on how parameter names convert, please see the Naming section of the Other Customization chapter of the FWD Conversion Handbook. Parameter naming works the same as variable naming.

INPUT-OUTPUT and OUTPUT parameters must by nature be shared references with the calling code. This means that the converted code will directly use the reference passed in as a parameter. These parameter references will use the same Java name as is named in the method signature for the internal or external procedure.

INPUT parameters must by nature be a one-way transfer of data from the caller to the called code. This means that the called code cannot directly refer to the same reference as is given in the method signature. Just as in Java, in the 4GL it is valid to modify an INPUT parameter. This means that the called code must have its own local copy of the parameter so that changes are not visible in any reference provided by the caller. To achieve this, the parameter name in the method signature will be the converted Java name prefixed by an underscore (_) character. In the above example, the 4GL INPUT parameter txt1 is named _txt1 in the method signature. Then inside the anonymous inner class (see new Block() { … } above), there will be a "properly" named member variable of the same type. This is the local copy of the INPUT parameter which can be directly referenced by the converted code. Each local copy is initialized from the matching parameter passed in. So in this example, txt1 is initialized from the _txt1 passed in reference. Since the local copy is a member of the anonymous inner class, it effectively has the same scope as the rest of the parameters.

All other options and initialization code will emit inside the Block.init() method. This is a method that is only called once, it is never executed again (even during the equivalent of a 4GL "retry") and it is called before the Block.body() is executed. This makes it perfect for initialization code.

All procedure parameters are enabled for UNDO by default (in FWD terms they are "undoable"). All undoable parameters will be listed in a TransactionManager.register() method call (it takes a variable argument list) and any NO-UNDO parameters will be excluded from that call.

INPUT-OUTPUT and OUTPUT parameters can optionally be specified as NO-UNDO. The NO-UNDO parameters will be passed in an Undoable[] to TransactionManager.deregister(). The reason this is necessary is that by definition, INPUT-OUTPUT and OUTPUT parameters already exist in a block somewhere in the calling code. This means that they may already be registered for UNDO support in the calling code. To properly implement NO-UNDO, it is necessary to de-register those instances from the UNDO lists for the called code, while leaving the UNDO lists for the calling code unchanged. This is why it is not enough to just avoid the register() method call.

Any initialization for parameters (using the INITIAL option) will be emitted in the init() method. These initializers will emit as assign() method calls using the literals specified in the INITIAL option. Since OUTPUT parameters must also have a default value assigned, if there is no explicit INITIAL option, any OUTPUT parameter will be initialized to its default value. For all variable types (except date), the default value is implemented by the default constructor for the wrapper class. For date, the default constructor creates an instance with today's date (the same as the TODAY built-in function). But since the 4GL date instance will default to unknown value, to properly initialize it, the date.instantiateUnknownDate() static method is used. INPUT and INPUT-OUTPUT parameters are not initialized unless there is an explicit INITIAL option.

User-interface specific options are removed from the replacement business logic but their settings will be honored in any frames or user-interface processing that reference that parameter.

Other options that are supported are CASE-SENSITIVE, NOT CASE-SENSITIVE and DECIMALS. These options are set into the parameter instances in the init() method, just as described in the section below entitled Data Type Options.

Options summary:

Option Description Supported Notes
CASE-SENSITIVE The character parameter will be compared and sorted on a case-sensitive basis. Yes See Data Type Options below.
COLUMN-LABEL When this parameter is used as a widget in a frame, this defines the descriptive text column label for the widget. Yes See Part 6 of this book.
DECIMALS Sets the number of digits to the right of the decimal point for this decimal parameter. Yes See Data Type Options below.
EXTENT Defines an array parameter with a specific number of elements. No See Data Type Options below.
FORMAT Specifies the pattern used to format the output of this parameter when used as a widget in a frame. Yes See Part 6 of this book.
INITIAL Defines an explicit initial value for the parameter. Yes See Data Type Options below.
LABEL When this parameter is used as a widget in a frame, this defines the descriptive text label for the widget. Yes See Part 6 of this book.
NO-UNDO UNDO support is disabled for the parameter. Yes See UNDO below.
NOT CASE-SENSITIVE The character parameter will be compared and sorted on a case-insensitive basis. Yes See Data Type Options below.

DEFINE PROPERTY

Defines a readable and/or writable data member of a Progress 4GL class.

Not supported.

DEFINE VARIABLE

This statement creates a new variable or accesses an existing variable instance for the local procedure. This statement determines the data type and name of the variable. It can optionally specify certain configuration.

At a high level, Progress 4GL has 2 main forms of this statement. The following summarizes FWD support for these forms:

Form Description Supported Notes
DEFINE <sharing_flags> VARIABLE { AS [HANDLE TO] <primitive_type_name>@|@LIKE <field> } [options]. Defines a variable in the local procedure or function OR imports a reference to a shared variable that was defined elsewhere. Yes The following are NOT supported:
     data types which are not yet supported by FWD (see the Variables section of this chapter)
     HANDLE TO
DEFINE <access_modifiers> VARIABLE AS [CLASS] <classname> [options]. Defines a variable which is a reference to an instance of a valid class (object oriented 4GL extension). No  

This section of the book will only describe how the non-shared form of this statement is converted. For details on how the <sharing_flags> are implemented, see Shared Variables below.

All of the primitive data types which are supported in FWD as variables can be used in the AS clause. The use of both the LIKE is fully supported. With LIKE the data type and configuration (options) of the field are copied into the newly defined variable as if they were explicitly defined in the source code. All other processing is the same as with the AS clause.

The following is a simple 4GL example:

define variable txt1 as character.
define variable num2 as decimal.
define variable dat3 as date.

If this code is in an external procedure, the resulting Java code will look like this (the comments have been added by hand to make it easier to understand):

public void execute()
{
   externalProcedure(new Block()
   {
      // variables are members of this anonymous inner class

      character txt1 = new character("");

      decimal num2 = new decimal(0);

      date dat3 = date.instantiateUnknownDate();

      public void init()
      {
         // by default in the 4GL, all variables must have UNDO support
         TransactionManager.register(txt1, num2, dat3);
      }

      public void body()
      {
         // variables can be referenced here
      }
   });
}

Non-shared variables that are unreferenced by any other 4GL code in the same external procedure, are considered dead code and may be removed by the conversion process (they will not appear in the Java output).

Variables are always created as members of a class. Some variables are created as data members of the business logic class (the main class for the file) and other variables are created as data members of the anonymous inner class of type Block to which the variable is scoped. In the above example, the variables are all created as members of the inner class that corresponds to the external procedure. For details on how the location of the variable definition is chosen, see the section below entitled Scoping.

Each 4GL primitive data type that has a supported replacement in FWD will emit as a new instance of that replacement class. The section above on each data type describes which types are supported and the Java wrapper class they convert into.

Each 4GL variable name will be converted to a valid Java variable name. All references to that variable will likewise use that same converted name. For details on how variable names convert, please see the Naming section of the Other Customization chapter of the FWD Conversion Handbook.

Variables are always initialized and the value will be something non-null. There are 3 approaches that can be used. First, the default constructor for the type can be used. The default constructor is almost always equivalent to initializing to the unknown value, except for the date class. Second, a constructor can be used that accepts a Java literal (a String, an int, ...) that encodes a specific value for the variable. This value can either be the default value for the type (where no INITIAL option is provided) or an explicitly defined value (through the INITIAL option). Third, a special factory method can be used to return a new instance. For example, this is done to create a date instance with the unknown value. For more details on variable initialization, see Initialization below.

User-interface specific options are removed from the replacement business logic but their settings will be honored in any frames or user-interface processing that reference that parameter. Non-user-interface options are set into the variable instances in the init() method, as described in the section below entitled Data Type Options.

Options summary:

Option Description Supported Notes
BGCOLOR Background color in GUI. Yes See Part 6 of this book.
CASE-SENSITIVE The character variable will be compared and sorted on a case-sensitive basis. Yes See Data Type Options below.
COLUMN-LABEL When this variable is used as a widget in a frame, this defines the descriptive text column label for the widget. Yes See Part 6 of this book.
CONTEXT-HELP-ID Help file index. No See Part 6 of this book.
DCOLOR Display color in CHUI. Yes See Part 6 of this book.
DECIMALS Sets the number of digits to the right of the decimal point for this decimal variable. Yes See Data Type Options below.
DROP-TARGET Drag and drop support. No See Part 6 of this book.
EXTENT Defines an array variable with a specific number of elements. Yes See Arrays below.
FONT Font for the widget in GUI. No See Part 6 of this book.
FGCOLOR Foreground color in GUI. Yes See Part 6 of this book.
FORMAT Specifies the pattern used to format the output of this variable when used as a widget in a frame. Yes See Part 6 of this book.
INITIAL Defines an explicit initial value for the variable. Yes See Data Type Options below.
LABEL When this variable is used as a widget in a frame, this defines the descriptive text label for the widget. Yes See Part 6 of this book.
MOUSE-POINTER Mouse pointer for the widget. No See Part 6 of this book.
NO-UNDO UNDO support is disabled for the variable. Yes See UNDO below.
NOT CASE-SENSITIVE The character variable will be compared and sorted on a case-insensitive basis. Yes See Data Type Options below.
PFCOLOR Prompt-for color in CHUI. Yes See Part 6 of this book.
TRIGGERS phrase Widget-specific trigger definition. No See Part 6 of this book.
VIEW-AS phrase Sets the type of widget and type-specific configuration (by default type is FILL-IN). Yes See Part 6 of this book.

Format Phrase with AS or LIKE

Inside any statement that accepts a format phrase, the 4GL programmer can define a new variable "on the fly". This concept is similar to the inline creation of a new variable in a MESSAGE SET/UPDATE statement (see MESSAGE SET/UPDATE with AS or LIKE below). The syntax can take 2 forms:

AS <primitive_data_type>

Or:

LIKE <database_field>

No explicit variable options can be specified (e.g. CASE-SENSITIVE or INITIAL). In the case of a LIKE clause, the data type and configuration of the field are used as the pattern on which to create the variable.

The effective result of this 4GL construct is the same as if a matching DEFINE VARIABLE statement had been specified in the 4GL code just above the statement containing the format phrase.

This simple example:

DISPLAY invoice AS integer LABEL "Invoice".

is equivalent to this 4GL code:

DEFINE VARIABLE invoice AS INTEGER.
DISPLAY invoice LABEL "Invoice".

The use of LIKE works the same way, with the exception that the core configuration (e.g. CASE-SENSITIVE, DECIMALS, EXTENT, INITIAL...) will be the same as the field on which the variable is patterned. This has the same effect as if a DEFINE VARIABLE statement with these same values was explicitly coded in the 4GL.

For details on the Java conversion, please see the DEFINE VARIABLE section above.

Function Parameter

This statement specifies a parameter for a user-defined function. This statement determines the data type, mode and ordinal position in the function's signature. In particular, the ordinal position is determined by the order of the parameters listed in the FUNCTION statement. To invoke the function, the corresponding parameter(s) must be specified in the function call (with the same type, mode and order as defined in the FUNCTION statement itself).

At a high level, Progress 4GL has 7 main forms of this statement. The following summarizes FWD support for these forms:

Form Description Supported Notes
<mode> <parm_name> AS <primitive_type> [EXTENT <num_elements>] Defines a parameter in which the caller will pass a reference to a variable (INPUT-OUTPUT or OUTPUT mode) or a value of an expression (INPUT mode). Yes The following are NOT supported:
     data types which are not yet supported by FWD (see the Variables section of this chapter)
     EXTENT (array) parameters
<mode> <parm_name> AS [CLASS] <class_name> [EXTENT <num_elements>] Defines a parameter that is an instance of the specified class. No  
BUFFER <buffername> FOR <table> [PRESELECT] Defines a parameter in which the caller will pass a reference to a buffer (always INPUT-OUTPUT mode) that accesses a temp-table or database table. Yes See Part 5 of this book for details.
<mode> TABLE <temp_table_name> [table_options] Defines a parameter in which the caller will pass a temp-table by reference or by value. Yes See Part 5 of this book for details.
<mode> TABLE-HANDLE <temp_table_handle> [table_options] Defines a parameter in which the caller will pass a handle to a temp-table. No See Part 5 of this book for details.
<mode> DATASET <dataset_name> [table_options] Defines a parameter in which the caller will pass a Pro-Dataset by reference or by value. No See Part 5 of this book for details.
<mode> DATASET-HANDLE <dataset_handle> [table_options] Defines a parameter in which the caller will pass a handle to a Pro-Dataset. No See Part 5 of this book for details.

This section of the book will describe how the common or "variable" form of this statement is converted. When the CLASS form is supported, this section will also describe that conversion. All other forms are database-related and thus are described in Part 5 of this book.

The supported modes are INPUT, INPUT-OUTPUT and OUTPUT. All data types which are supported in FWD as variables can be used as parameter types. The 4GL syntax and the Java converted code are very similar to that in the DEFINE PARAMETER statement. The following is a list of the key differences (all of these match up with differences in how the 4GL works):

  • All function parameters are always NO-UNDO. This means that function parameters are never added to the TransactionManager.register() method call. More importantly, INPUT-OUTPUT and OUTPUT mode function parameters are always added to a TransactionManager.deregister() method call to ensure that any preexisting UNDO reference to those variables in the calling procedure is temporarily removed for the scope of the function.
  • There are never any options that can be specified for a function parameter. EXTENT is possible in later 4GL versions but is not supported yet in FWD.
  • Since there is no INITIAL option (and no alternative 4GL syntax for an explicit initializer), there is no way to set the initial value of a function parameter. OUTPUT mode parameters do get initialized, but as in the 4GL, they are always initialized to the unknown value.
  • In some cases, the mode can be left unspecified in the 4GL code. Any unspecified mode is the same as an INPUT mode parameter.

The following is a 4GL example:

function example returns integer (input        txt1  as character,
                                  input-output num2  as integer,
                                  output       dat3  as date):
   return 0.
end.

This is the resulting Java code:

public integer example(final character _txt1, final integer num2, final date dat3)
{
   return integerFunction(new Block()
   {
      character txt1 = new character(_txt1);

      public void init()
      {
         TransactionManager.deregister(new Undoable[]
         {
            num2,
            dat3
         });
         dat3.assign(date.instantiateUnknownDate());
      }

      public void body()
      {
         returnNormal(0);
      }
   });
}

Please see the DEFINE PARAMETER section above for more details on why the Java code emits in this manner.

MESSAGE SET/UPDATE with AS or LIKE

This form of the MESSAGE statement (with the SET or UPDATE clause) implements an editing operation, which provides a quick way for prompting the user for a single value. It is possible to use an AS or LIKE clause with this edit operation, such that a variable is defined as part of the same statement. This concept is similar to the inline creation of a new variable in a format phrase (see Format Phrase above).

The syntax can take 2 forms:

MESSAGE … { SET | UPDATE } AS <primitive_data_type> [edit_options]

Or:

MESSAGE … { SET | UPDATE } LIKE <database_field> [edit_options]

The edit options include a FORMAT string and the AUTO-RETURN option. Both of these options only serve to modify or control the editing operation, so they are user-interface features (see Part 6 of this book) and thus have no impact on the variable definition itself. No explicit variable options can be specified (e.g. CASE-SENSITIVE or INITIAL). In the case of a LIKE clause, the data type and configuration of the field are used as the pattern on which to create the variable.

The effective result of this 4GL construct is the same as if a matching DEFINE VARIABLE statement had been specified in the 4GL code just above the MESSAGE statement.

This simple example:

MESSAGE UPDATE invoice AS integer FORMAT "999999" AUTO-RETURN.

is equivalent to this 4GL code:

DEFINE VARIABLE invoice AS INTEGER.
MESSAGE UPDATE invoice FORMAT "999999" AUTO-RETURN.

The use of LIKE works the same way, with the exception that the core configuration (e.g. CASE-SENSITIVE, DECIMALS, EXTENT, INITIAL...) will be the same as the field on which the variable is patterned. This has the same effect as if a DEFINE VARIABLE statement with these same values was explicitly coded in the 4GL.

For details on the Java conversion, please see the DEFINE VARIABLE section above.

Method Parameter

This is part of the class support in v10 4GL. User-defined methods can be created as part of a class and each method can have a custom parameter list.

Not supported.

Property Setter Parameter

This is an optional method that can encode logic that is executed when a property is assigned (see DEFINE PROPERTY above). There cannot be more than one parameter for this special setter method.

Not supported.

Shared Variables

Old style Progress 4GL often uses shared variables to communicate data between procedures. FWD provides fully support for this mechanism.

In the 4GL, there are 3 forms which can create or access shared variables. Each of these is a form of the <sharing_flags> in the DEFINE VARIABLE statement (see the section entitled DEFINE VARIABLE above):

4GL Syntax Purpose Java Initialization Code
NEW SHARED Creates a new non-global instance of a shared variable in the current scope. This will "hide" global shared variables and non-global shared variables of the same name (which are defined in an enclosing scope). This shared variable will cease to exist when this scope exits. SharedVariableManager.addVariable(String name, Object var)
NEW GLOBAL SHARED Creates a new instance of a shared variable in the global scope. This variable will exist until the user's session exits, since it is not associated with any single program scope. SharedVariableManager.addVariable(ScopeLevel.GLOBAL, String name, Object var)
SHARED Accesses ("imports") a reference to an already existing shared variable (either shared or global). The instance will be found based on a lookup process that tries to find the variable matching the given name in the current scope, and in each enclosing scope in turn, returning the first found instance. Once all scopes are checked, the global scope is checked. If no instance is found an error will occur. SharedVariableManager.lookupVariable(String name)

The effect of these flags is to change the initialization code for the associated Java variable. All other processing of the DEFINE VARIABLE remains as described in the DEFINE VARIABLE section above. For example, this 4GL:

define new shared        variable txt1 as character.
define new global shared variable num2 as decimal.
define shared            variable dat3 as date.
define new shared        variable txt4 as character initial "Hello World!".

This is the resulting Java code:

public void execute()
{
   externalProcedure(new Block()
   {
      character txt1 = SharedVariableManager.addVariable("txt1", new character(""));

      decimal num2 = SharedVariableManager.addVariable(ScopeLevel.GLOBAL, "num2", new decimal(0));

      date dat3 = SharedVariableManager.lookupVariable("dat3");

      character txt4 = SharedVariableManager.addVariable("txt4", new character("Hello World!"));

      public void init()
      {
         TransactionManager.register(txt1, txt4);
         TransactionManager.registerUndo(true, num2);
      }

      public void body()
      {
         // variables can be accessed here
      }
   });
}

The initialization of each variable is replaced. For non-shared variables, the initialization occurs with a default constructor, a constructor which takes a literal value parameter or a factory method (e.g. date.instantiateUnknownDate()). With shared variables, the normal initialization is either moved inside a SharedVariableManager.addVariable() method call or the initialization is completely replaced (in the case of importing a reference to an existing shared variable). In each case where a new instance is created, the same use of a constructor or a factory method (not shown above) will occur. The result of that construction will be passed as the 2nd or 3rd parameter to addVariable() depending on if the variable is to be shared in the global scope or not. In the lookupVariable() case, no new instance is needed so there is no use of a constructor or factory method. Explicit and default initialization works just as it normally would (see the Initialization section below). The only exception is that in the lookupVariable() case, since there is no new instance, the value will be the same as it has been previously assigned by the calling code.

UNDO registration works as with non-shared variables, with two differences:

  • Global shared variables must be registered using TransactionManager.registerUndo(boolean, Undoable) or TransactionManager.registerUndo(boolean, Undoable[]). This form of UNDO registration allows a first parameter which is a boolean flag that should be set to true if the instance(s) are global variables. This special form is needed since UNDO processing must be directly associated with a specific scope except in the case of global shared variables, which require that UNDO processing is associated with the global scope.
  • Non-new shared variables (an "import" of an existing shared variable) are treated as if they were NO-UNDO because the original creation of the variable has already handled the registration (if it was needed). The UNDO registration is active no matter how deep the call stack reaches (including through multiple levels of procedure calls), so there is no reason to re-register the imported variable references. In fact, re-registering the variables might cause undefined and unwanted behavior. Notice that in the example above, the dat3 variable does not appear in a registration method call.

Support for shared variables is handled via a shared variable manager. This runtime class is called SharedVariableManager . It is based on the ScopedSymbolDictionary and is designed to exist in a per client/session basis (stored in the security context). This allows all transactions to access these shared variables but for this access to be done maintaining context specific state. This approach is a replacement for features that would normally be provided via thread local variables which can't be used in the transaction server.

Whenever a new external procedure scope is started, if new shared variable references are added, the corresponding Java source code will add a scope. Likewise this same Java method will use a finally {} clause to delete the scope on exit. Global variables are added to the global scope and regular shared variables are added to the current (top) scope. Such additions only occur if the definition is defined with the NEW keyword. This allows "downstream" methods to access the most current instance of a shared variable by executing a lookup using a given name and when the method that defines a new shared (non-global) variable ends, the scope must be deleted (deleting all references to variables defined in that scope). Any process that accesses a shared variable gets the shared variable manager out of the current security context and then does a lookup of the variable name and assigns the reference to the resulting local instance.

Initialization

Variables created with DEFINE VARIABLE get initialized in the same line of code as the variable declarations itself. Since these variable instances are always data members of the containing business logic class or of an anonymous inner class (of type Block), that is the same location in which the initializer will be found. The initializer will differ depending on whether there is a default value or an explicit initializer that was specified with the INITIAL option.

For procedure parameters, initialization occurs as an assign() method call during the Block.init() method of the anonymous inner class in which the parameter is a member. See the DEFINE PARAMETER section above and the Assignment section below for details.

Function parameters work the same way, except they are always initialized to the unknown value (they cannot have an explicit initializer specified with the INITIAL option).

The reason that parameters (both procedure and function) need to be initialized in the Block.init() method is that the replacement Java variable is also a parameter in the method's signature list. Java doesn't provide a mechanism to initialize method parameters inside the signature itself, so in FWD the Block.init() method is used. This location is guaranteed to be executed once (and only once) and that execution will occur before the Block.body() method will ever run. This means that the initialization is guaranteed to occur before the first "real" reference to the value of that variable.

The following table shows the initializers used for each type, depending on the situation:

Type Default Value INITIAL Option (Explicit) Unknown Value
character "" (empty string) new character(String) new character()
date ? (unknown value) date.fromLiteral(String) OR
date.today() (used for the 4GL TODAY function)
new date()
datetime ? (unknown value) datetime.fromLiteral(String) OR
datetime.now() (used for the 4GL TODAY function)
new datetime()
datetime-tz ? (unknown value) datetimetz.fromLiteral(String) OR
datetimetz.now() (used for the 4GL TODAY function)
new datetimetz()
decimal 0 new decimal(double) OR
new decimal(int)
new decimal()
handle ? (unknown value) n/a new handle()
integer 0 new integer(int) OR
new integer(double)
new integer()
Int64 0 new int64(int) OR
new int64(double)
new int64()
logical false new logical(boolean) new logical()
memptr uninitialized byte array (not the same as unknown value!) n/a memptr.instantiateUnknown()
raw zero length byte array (not the same as unknown value!) n/a raw.instantiateUnknown()
recid ? (unknown value) n/a new recid()
rowid ? (unknown value) n/a rowid.instantiateUnknown()

For those data types which allow the INITIAL option, the constant which is the parameter for that option will be rendered as the proper type and emitted as a parameter to the associated variable's constructor. The only exceptions are the 4GL TODAY and NOW functions used as a "constant" for the date and datetime / datetimetz respectively, which are equivalent to the today and now static methods of the respective classes.

Scoping

Progress 4GL has certain top-level block types. Top-level blocks are those which can be named or invoked directly. FWD supports external procedures, internal procedures, user-defined functions (non-forward) and triggers as top-level blocks. Inside top-level blocks, there can be "inner blocks" such as DO, REPEAT and FOR. These inner blocks can be nested arbitrarily.

Progress 4GL variables can be declared in any top-level block and it is possible to declare variables in one block (e.g. the external procedure) and then to access those variables from other top-level blocks in the same file. Even if the variable is declared deep inside some nested inner block in the external procedure, it can be accessed by any other code in the entire file so long as that code exists sequentially later in the file than the declaration.

In Java, the scope of a variable is largely determined by where in the program the variable is declared. If it is declared as a data member of the outermost class, then everything in the class (including inner classes) can access that variable. If a variable is declared inside a specific method or inner class, then the visibility and lifetime of that member will be limited to that scope.

The FWD approach is to duplicate the 4GL behavior when needed, but to do it in a Java way. This means that the FWD conversion must analyze the usage (all references) of each variable throughout the entire file. As a general rule, the scope of a variable is set to current inner block so long as it contains all references to that same variable instance. That location will be an anonymous inner class of type Block and the variable will be a data member of that inner class. This handles many common cases of variable declaration.

There are cases in which variables are accessed across top-level blocks or which in Java (due to how the converted code must be written to duplicate 4GL behavior) will have a need to be accessed from methods of the business logic class as well as from inner classes that only exist at the top-level of the business logic class. In such cases, the variable is "promoted". Promotion is the idea that a variable needs to be a data member of the outermost class (the main or business logic class for the file, which contains everything else) because it simply must have entire-file scope.

It is important to note that the primary reason for a top-level inner class in the converted Java code is to provide some kind of callback expression or logic. For example, validation expressions for user-interface widgets are registered and evaluated by the FWD runtime (just as in the 4GL). But to do this in Java, an inner class is created which contains a method that provides that expression evaluation. The class definition for the inner class exists inside the topmost level of the business logic class. Instances of that inner class are created and passed to the FWD runtime. Inside those instances, the validation expression can reference any data that is in scope in the 4GL business logic at the point that validation expression was defined. This means that variables can be directly accessed from that expression. In that case, those variables would be promoted so that they are visible to both the validation expression (which is now an inner class) as well as to the rest of the business logic class.

The following describes the reasons to promote:

  • If the variable is defined in one top level block and then used in another top level block (without a local redefinition of the same name).
  • Any variables that were defined in a nested block of the current top-level scope where that defining block does not enclose the current location. In this case, the "simple" approach of promoting is taken. A better approach would be to find the nearest inner block which encloses both the definition and all references (if such a thing exists, which is not guaranteed). This optimal approach is not yet implemented.
  • Any variables that are part of a validation expression (or validation message expression).
  • Any variables used in accumulate expressions.
  • Any variables in client-side WHERE clause expressions (see Part 5 of this book).
  • Any variables in query substitution expressions (see Part 5 of this book).
  • Any variables in an embedded assignment expression (see Part 6 of this book, UPDATE or SET statement).
  • Any variables in an expression in a FORM HEADER statement (see Part 6 of this book).
  • Any variables referenced in a BY clause in a query (see Part 5 of this book).
  • Any variables in a dynamic COLUMN, ROW, DOWN or TITLE expression (see Part 6 of this book).
  • Any variables in a complex expression or an array reference (use of the subscript operator) in a PUT or EXPORT statement. A complex expression is one that is not a simple literal or variable reference. See the Streams chapter of Part 4 of this book.

It is possible to have more than one variable of the same name, which all need to be promoted. To resolve this, one of the variable definitions will keep the standard converted name and the other definitions will have a Ref<num> (where num is 2 or greater) appended to the end of the name, making the definition and all references unique. The same conversion logic that calculates promotion, also disambiguates variable names that would conflict in Java but which did not conflict in the 4GL.

References

Variable references in the 4GL convert differently depending on context.

Those references which are the left operand of the assignment operator (=) will have their value assigned to the result of the expression that is the right operand of the assignment operator. For details on how this is converted, please see the Assignment section below.

References to EXTENT variables (array references) use a subscript operator ([]) in the 4GL. That operator obtains a specific element from the array. For details of how this is converted, please see the Arrays section below.

Some text in 4GL programs appear to be variable references, but instead are actually references to a specific widget (of the same name and patterned off the corresponding variable) in a frame. These widget references are part of the static user-interface 4GL code that is re-factored into frame definitions. As such, it is removed from the business logic. For details on widget references, see Part 6 of this book. The key point here is that these are not actually variable references, so they do not emit into Java variable references.

All other variable references emit into Java as standard Java variable references. Of course, the converted variable name is used as the Java name, so that it matches the Java variable declaration. A 4GL variable named my-date-var might be converted to a Java name of myDateVar. For example, this 4GL:

if my-date-var > 12/31/1999 then

Would look like this in Java:

if (myDateVar._isGreaterThan(new date("12/31/1999"))

The key point here is that the simple reference is emitted in the same expression location using the converted name.

The handle data type emits like a regular variable reference, but in some use cases handles require an additional "unwrapping" of the contained Java object reference inside. To unwrap the contained instance, the handle.get() method is called and the result is cast to the type needed. For details, see the Expressions chapter in Part 4 of this book.

4GL WHERE clauses convert to HQL expressions that are String literals. Any variables 4GL WHERE clause must be passed into the query as a list of "query substitution parameters". The variable reference will emit as normal, but it will emit into an array of these parameters instead of inside the HQL expression. Inside the HQL expression, instead of the variable reference, there is a ? character, which indicates that a query substitution parameter must be substituted. For details, please see Part 5 of this book.

Data Type Options

Generally, the Java replacement code for core data type options are emitted in the Block.init() method of the anonymous inner class of which the variable is a member. The exception to this rule is for variables that are defined as data members of the business logic class itself (instead of inside any specific anonymous inner class). This can happen due to scoping requirements (a variable that must be accessed from the entire class). For data members of the business logic class, the Java options code is emitted in the Block.init() of the Block instance (an anonymous inner class) which is passed to the BlockManager.externalProcedure() method.

Case-sensitivity is implemented in the com.goldencode.p2j.util.character class. Decimal precision is implemented in the com.goldencode.p2j.util.decimal class. For both of these options, Progress 4GL has an unusual behavior. Shared variables that are imported (DEFINE SHARED VARIABLE...) can temporarily override the CASE-SENSITIVE and DECIMALS configuration of the shared instance. Then when the scope of the importing procedure is complete, the original configuration values are restored. To implement this difference, the conversion emits different method calls for setting the same configuration value.

The following describes the conversion:

4GL Syntax Standard Java Syntax Import Shared Variable Java Syntax
CASE-SENSITIVE var.setCaseSensitive(true); var.setTemporaryCaseSensitive(true);
DECIMALS <num> var.setPrecision(num); var.setTemporaryPrecision(num);
EXTENT <num> See Arrays below. See Arrays below.
INITIAL <literal> See Initialization above. See Initialization above.
NO-UNDO See UNDO below. See UNDO below.
NOT CASE-SENSITIVE var.setCaseSensitive(false); var.setTemporaryCaseSensitive(false);

For options relating to the user-interface, please see Part 6 of this book. The following user-interface options can be used in data type definitions and are documented there: BGCOLOR, COLUMN-LABEL, CONTEXT-HELP-ID, DCOLOR, DROP-TARGET, FONT, FGCOLOR, FORMAT, LABEL, MOUSE-POINTER, PFCOLOR, VIEW-AS phrase, TRIGGERS phrase.

Assignment

Scalar (non-array) variable assignment takes one of two forms. If the right operand of the assignment operator is the unknown value literal (?), then the BaseDataType.setUnknown() method will be called. Otherwise, the value is set via the BaseDataType.assign().

The key point here is that the variable reference is NOT modified, only the variable's value is modified. This allows use of Java features like the final modifier to be used in parameters. It also reduces the need for null checking or other more complex expression re-factoring, since the reference is always non-null and does not change. This approach also matches the Progress 4GL semantic which is to set the value rather than to change the reference to point to a new instance (which is the Java assignment operator's behavior).

This 4GL code:

txt1 = ?.
num2 = 0.
dat3 = 12/31/1999.

Results in this Java code:

txt1.setUnknown();
num2.assign(0);
dat3.assign(date.fromLiteral("12/31/1999"));

All variables inherit from BaseDataType. This parent class is abstract and requires these methods to be present in each subclass. The setUnknown() method requires no variants, but it is implemented in the subclasses since each subclass may have a unique mechanism to detect if the instance is equivalent to the unknown value. The assign() method is different. An abstract version of assign(BaseDataType, boolean) exists in BaseDataType and it is used in some cases. In addition, BaseDataType implements the Undoable interface (but doesn't implement the assign(Undoable), leaving that method abstract). But more importantly, each subclass implements its own set of assign() methods. This enables each type to provide direct assignment support for all the primitive Java types that makes sense, in addition to a version that takes the different wrapper types. For example, the decimal type (which is a child class of NumberType, which in turn is a child class of BaseDataType) has these 6 assign() methods:

public void assign(BaseDataType, boolean) /* casts the 1st parm to NumberType and calls the next */
public void assign(NumberType, boolean)
public void assign(double)
public void assign(int)
public void assign(NumberType)
public void assign(Undoable)

For the assignment of array elements, see Arrays below.

For attribute assignment, see the Attributes section of the Expressions chapter of Part 4 in this book.

For database field assignment, see Part 5 of this book.

Assignment can also occur using the ASSIGN language statement. See the next section for details.

ASSIGN Statement

The ASSIGN language statement provides a way to group assignments into a batch. Due to the internal 4GL runtime implementation, this form of assignment is more efficient than assigning values individually. The FWD equivalent has no such difference in efficiency. The result in FWD is the same as individual assignments except there is a batch notification for database fields which duplicates the transactional behavior of the Progress 4GL ASSIGN statement. Please see Part 5 of this book for more details on batching.

All forms and options of this statement are fully supported.

Syntax

There are 2 main forms:

4GL Purpose Supported Notes
ASSIGN record [EXCEPT field_list] [NO-ERROR]. Fields in the record are assigned the corresponding values from the associated frame's screen buffer. Yes  
ASSIGN [[INPUT] FRAME frame | BROWSE browse] { lvalue [ = expression ] [WHEN expression] } … [NO-ERROR]. In this form, there is a list of variables, database fields and attributes which are assigned EITHER from the corresponding screen-buffer OR from an explicit assignment expression in the statement itself. Yes  

The 1st syntax form (the record form) is fully supported including the EXCEPT list. This is handled at conversion time by expanding the record reference into an complete list of fields in that table (minus any excluded fields). When the expansion is complete, it is as if the fields has been explicitly defined in the 4GL source code. Everything else works just like the 2nd form of the ASSIGN statement.

For example, this 4GL code (assume that "tablename" is a database table with fields named f1, f2, f3 and f4):

ASSIGN tablename EXCEPT f4.

is logically equivalent to the more verbose form:

ASSIGN tablename.f1  tablename.f2  tablename.f3.

Since the conversion process transparently handles converts the 1st syntax form into the 2nd syntax form, all output will be converted as documented for 2nd syntax form.

The 2nd syntax form has 2 different logical modes. The first logical mode could best be called the "implicit screen-buffer assignment". The second logical mode is best called "normal assignment".

Options:

4GL syntax Description Supported
[ FRAME frame | BROWSE browse ] lvalue The name of the variable or database field to be assigned from screen-buffer value or the evaluated expression. The variable/field name must be qualified by a frame or browse name if the widget name is ambiguous (it exists in more than one frame). Yes
expression The expression to evaluate. The resulting value is assigned to the variable or database field. Yes
WHEN expression The assignment of the preceding variable or database field only occurs if the expression used in the WHEN option evaluates to TRUE (at assignment time). Yes
record Specifies the name of the record buffer to implicitly assign from the screen-buffer. All of the fields of the buffer will be processed just as if it each database field was listed explicitly. Yes
EXCEPT field ... This is an optional list of database fields to exclude from assignment. Yes
NO-ERROR Specifies that any errors that occur in the attempt to set the value are suppressed. See the Transactions chapter for more details on "silent error mode". Yes

Implicit Screen-buffer Assignment

In implicit screen-buffer assignment, the purpose is to assign changes from the screen-buffer back into the associated variables (or database fields). Both of the syntax forms shown above, can implement this logical form of the statement.

Here is an example:

define variable num as integer.
define variable txt as character.
define variable flag as logical initial true.

set num txt.
assign num txt when flag.

The resulting Java code (ASSIGN statement only):

frame0.assignScreenValue(new Element(num, frame0.widgetNum()));
if ((flag).booleanValue())
{
   frame0.assignScreenValue(new Element(txt, frame0.widgetTxt()));
}

The idea is that this form of the statement is a shorthand that can be specified instead of explicitly coding the assignment operator and an expression for obtaining the screen-buffer value of the associated widget.

The commonly used syntax is to specify explicit widget lists (which can be either variables or database fields) in the statement. This is shown in the example above and it corresponds with the syntax variant 2 in the above table.

Array expansion can also be used in this syntax variant, instead of explicitly listing each element of the entire array (or a range of the array). The array expansion form effectively gets expanded into the same result as the explicitly defined list.

This implicit screen-buffer assignment mode can also be used with the "record" syntax form (1st syntax form in the above table), which just gets expanded into the 2nd syntax form.

This is a user-interface feature. More details and examples regarding this can be found in the Editing chapter of Part 6.

Normal Assignment

The normal assignment logical form of the ASSIGN statement is that which just is a list of variable, database field or attribute references, each followed by an assignment operator and an expression which evaluates to a compatible type. Such a form is converted the same way that normal ("standalone") assignments are converted.

For example, this 4GL code:

define variable num as integer.
define variable txt as character.
define variable flag as logical initial true.

assign num = 14
       txt = "whatever" when flag.

This results in the following Java code (ASSIGN statement only):

num.assign(14);
if ((flag).booleanValue())
{
   txt.assign("whatever");
}

This is the same as if it had been coded as separate assignment operations. In Java there is no penalty for separating the code in this manner. That performance difference is something that is an unfortunate consequence of the Progress 4GL implementation, which FWD does not duplicate.

The WHEN clause that can be used in the ASSIGN statement is fully supported. In both examples above, it simply creates a conditional test around the Java assignment code to ensure it is only assigned if the test expression evaluates to true.

NO-ERROR is also supported. For details, see the Transactions chapter.

Arrays

Progress arrays (EXTENT variables) are very similar in nature to Java arrays. A variable definition can be an array if it is explicitly specified using the EXTENT keyword or if the variable is defined LIKE another variable or field that is defined with extent that is more than 1.

Progress 4GL and FWD also support EXTENT database fields. For details on EXTENT fields, please see Part 5 of this book.

FWD only supports extent variables when explicitly created from the DEFINE VARIABLE statement (with an EXTENT option) or when implicitly created using a LIKE clause. Interestingly, the LIKE clause can be used in the Format Phase and the MESSAGE SET/UPDATE variable definitions. FWD supports those.

In later versions of the 4GL, DEFINE PARAMETER and Function Parameters support the EXTENT option. While FWD does support such parameters, FWD does not support extents for parameters at this time.

Definition and Initialization

4GL array definition and initialization map closely to Java array variable definitions and initializers. Some helper methods are used to fully duplicate the implicit initialization behavior that occurs when not all elements of the array have explicitly specified initialization constants.

There are 3 forms that can be seen. The differences between these forms is in how many initialization constants are specified in the INITIAL option (or if there is an INITIAL option at all).

One form is the no INITIAL option form. 4GL code:

define variable txt1  as character  extent 3.
define variable dat2  as date       extent 3.

Here is the resulting Java code:

character[] txt1 = new character[3];

date[] dat2 = new date[3];

public void init()
{
   assignMulti(txt1, new character(""));
   assignMulti(dat2, new date());
   TransactionManager.register(txt1, dat2);
}

There are 2 differences with normal variable initialization. First, an array constructor is used to create an array of the proper type and size (new character[3] for a character variable of extent 3). Second, the ArrayAssigner.assignMulti() static method is used to assign all elements of the new array. Each element will be assigned a duplicated instance of the 2nd parameter (empty string for the txt1 and the unknown value date for dat2). This point is key: each element will be a different instance but all of those instances will be equivalent in value.

The initialization is done in the init() method, which ensures it is done once and only once before the variable is referenced by user code (in the body() method).

Scoping and UNDO is done the same way as for scalar (non-array) variables. TransactionManager.register() method has special processing for when one of the parameters is an array of Undoable instances. It loops through the array and registers each element in the list.

The next form of array definition is the "complete" INITIAL option form. 4GL code:

define variable txt5  as character  extent 3   initial [ "hello", "world", "!" ].
define variable bool6 as logical    extent 3   initial [ yes, no, true ].

This results in the following Java code:

character[] txt5 = new character[]
{
   new character("hello"),
   new character("world"),
   new character("!")
};

logical[] bool6 = new logical[]
{
   new logical(true),
   new logical(false),
   new logical(true)
};

public void init()
{
   TransactionManager.register(txt5, bool6);
}

In this case, every element in the array has an explicit initializer constant in the INITIAL option. This means that the Java in-line initializer can be used instead of needing to emit additional initialization code in the init() method.

The last form is the "partial" INITIAL option form. 4GL code:

define variable num3  as integer    extent 10  initial [ 14, 30 ].
define variable dat4  as date       extent 10  initial [ ?, 01/01/2000, 12/31/1999 ].

This results in the following Java code:

integer[] num3 = new integer[10];

date[] dat4 = new date[10];

public void init()
{
   num3[0] = new integer(14);
   assignMulti(num3, 1, new integer(30));
   dat4[0] = new date();
   dat4[1] = date.fromLiteral("01/01/2000");
   assignMulti(dat4, 2, date.fromLiteral("12/31/1999"));
   TransactionManager.register(num3, dat4);
}

In the 4GL, this form will initialize each explicitly defined element of the array to the given value and all non-specified values will be initialized to the same value as the last explicitly specified element. FWD duplicates this behavior exactly. In the example above, the dat4 array has 10 elements but only 3 explicit initializers are specified in the INITIAL option. The first (da4[0]) and second (dat4[1]) elements are initialized using the corresponding explicit initializers. Then the third and all subsequent elements (dat4[2] through dat4[9]) are initialized with separate copies of the same value using the assignMulti(dat4, 2, date.fromLiteral("12/31/1999")) method call. The 2nd parameter to that call forces the assignment starting index to 2.

All three of these definition and initialization forms can also be used in conjunction with shared variable support. Here is 4GL example code:

define shared            variable txt1  as character  extent 3.
define new shared        variable dat2  as date       extent 3.
define new global shared variable dec3  as decimal    extent 3.
define shared            variable num4  as integer    extent 10  initial [ 14, 30 ].
define new shared        variable dat5  as date       extent 10  initial [ ?, 01/01/2000, 12/31/1999 ].
define new global shared variable txt6  as character  extent 10  initial [ "hello", "world" ].
define shared            variable txt7  as character  extent 3   initial [ "hello", "world", "!" ].
define new shared        variable bool8 as logical    extent 3   initial [ yes, no, true ].
define new global shared variable num9  as integer    extent 3   initial [ 0, 1, 14 ].

And here is the resulting Java code:

character[] txt1 = SharedVariableManager.lookupVariable("txt1");

date[] dat2 = SharedVariableManager.addVariable("dat2", new date[3]);

decimal[] dec3 = SharedVariableManager.addVariable(ScopeLevel.GLOBAL, "dec3", new decimal[3]);

integer[] num4 = SharedVariableManager.lookupVariable("num4");

date[] dat5 = SharedVariableManager.addVariable("dat5", new date[10]);

character[] txt6 = SharedVariableManager.addVariable(ScopeLevel.GLOBAL, "txt6", new character[10]);

character[] txt7 = SharedVariableManager.lookupVariable("txt7");

logical[] bool8 = SharedVariableManager.addVariable("bool8", new logical[]
{
   new logical(true),
   new logical(false),
   new logical(true)
});

integer[] num9 = SharedVariableManager.addVariable(ScopeLevel.GLOBAL, "num9", new integer[]
{
   new integer(0),
   new integer(1),
   new integer(14)
});

public void init()
{
   assignMulti(dat2, new date());
   assignMulti(dec3, new decimal(0));
   dat5[0] = new date();
   dat5[1] = date.fromLiteral("01/01/2000");
   assignMulti(dat5, 2, date.fromLiteral("12/31/1999"));
   txt6[0] = new character("hello");
   assignMulti(txt6, 1, new character("world"));
   TransactionManager.register(dat2, dat5, bool8);
   TransactionManager.register(true, dec3, txt6, num9);
}

The SharedVariableManager is used to import shared variable references or create new shared variables. This is done just as explained in the Shared Variables section above. One difference is that any explicit or implicit initializers are dropped (not implemented) for any import of a shared variable using lookupVariable()). This is the same behavior as the 4GL, it is just more obvious in the resulting Java. For new variables, the initializers are the same as for non-shared arrays. The other difference to note is that UNDO registration is only done for the new shared array variables and is not done for the imported shared variables.

References

The referencing of array elements is also very similar between Java and Progress (both use numeric expressions inside postfixed square brackets to index the element). The biggest difference: Progress 4GL indexes arrays using a 1-based index. Java uses a 0-based index.

At first glance, it seems that the 1-based versus 0-based indexing difference can be handled during conversion. The Java subscript operator [] could be directly mapped to the Progress subscriipt operator []. Constant subscript expressions could be emitted directly into the source code by decrementing 1 from the 1-based constant, to provide the proper 0-based version.

For non-constant expressions, a method could be used to calculate the index or the expression could be re-factored to subtract 1.

The problem with these two approaches is that they do not allow for the proper duplication of Progress 4GL control flow and error handling when indexes are out of range (the result would be an IndexOutOfBoundsException in Java). If a FWD runtime method is used to calculate the index, then it could raise the proper error condition, but so long as the Java [] operator is used with the result, then with use of NO-ERROR (silent error mode) array access will still fail with an IndexOutOfBoundsException. This occurs because the in silent error mode, the error condition is suppressed and the evaluation of any expression continues.

To eliminate this issue, static methods ArrayAssigner.subscript(T[] array, one_based_index) are used to hide the 1 to 0 based index calculation, the actual dereferencing of the array and all associated error handling. This allows proper duplication of the 4GL behavior. The trick is that the reference to the array is passed as the first parameter to the subscript() method. This allows the method to do range checking since the index must be between 0 and (array.length - 1). The second parameter is the 1-based index which must be decremented and bounds-checked.

For details on how array elements are dereferenced when they appear as the left operand of an assignment operator, see Assignment below.

For details on array field references, see Part 5 of this book.

For details on frame widget array references cases, see Part 6 of this book.

Assignment

Just as there are control flow and error handling issues with dereferencing array elements inside normal expression processing (see References above), these same issues exist when assigning the value of an array element. For this reason, ArrayAssigner static methods are available to match the proper behavior. Here is a summary:

4GL Syntax Java Syntax Purpose Supported
array[index] = ? assignSingleUnknown(array, index) Set a single element to the unknown value. Yes
array[index] = expression assignSingle(array, index, value) Set a single element to the given expression value. Yes
array = ? assignUnknown(array) Set all elements of the array to the unknown value. Yes
array = array2 assignMulti(array, array2) Set all elements of array to the correspondingly indexed elements in array2. Yes

Here is 4GL example code:

define variable num1  as integer    extent 10  initial [ 14 ].
define variable num2  as integer    extent 10  initial [ 30 ].

num1[1] = ?.
num1[2] = num2[1].
num2    = ?.
num2    = num1.

In Java this the resulting code:

integer[] num1 = new integer[10];

integer[] num2 = new integer[10];

public void init()
{
   assignMulti(num1, 0, new integer(14));
   assignMulti(num2, 0, new integer(30));
   TransactionManager.register(num1, num2);
}

public void body()
{
   assignSingleUnknown(num1, 1);
   assignSingle(num1, 2, subscript(num2, 1));
   assignUnknown(num2);
   assignMulti(num2, num1);
}

Data Type Options

Data type options for arrays are different, since they must be configured in every element of the array, rather than for the array itself. This means that static methods are used in the BaseDataType subclass in which the option is implemented. The first parameter is the reference to the array itself. This allows the static method to iterate though all element of the array and set the option on each one.

The following describes the conversion:

4GL Syntax Standard Java Syntax Import Shared Variable Java Syntax
CASE-SENSITIVE Not supported. Not supported.
DECIMALS <num> decimal.setPrecision(decimal[] array, int num); Not supported.
NOT CASE-SENSITIVE Not supported. Not supported.

The unsupported options can be easily enabled with the addition of static methods for setting the option and minor changes to conversion rules for variable definition.

Unsubscripted Array Reference Expansion

Unsubscripted array variable references are possible in certain conditions in the 4GL. They are not legal inside expressions, but can be used in the following language statements:

DEFINE FRAME
FORM
CHOOSE … FIELD
DISPLAY
DEFINE BROWSE … DISPLAY
COLOR
ON widget-list
SET … TEXT
UPDATE … TEXT
PROMPT-FOR … TEXT
ENABLE … TEXT
SET
UPDATE
PROMPT-FOR
ENABLE
DISABLE
ASSIGN
ACCUMULATE
EXPORT
IMPORT

The usage is a syntax shortcut. It is used instead of specifying a list of all elements, each using the subscripting syntax. The syntax is to use the array variable name by itself, with no subscript at all. This form can't be used inside normal expressions but in the above language statements it is implicitly expanded to be the same as a list of those elements.

Here is a 4GL code example:

define variable num as integer extent 3.

set num.
assign num.

The key here is that the num array is expanded as if the 4GL had an explicit list like this:

SET num[1] num[2] num[3].

This results in the following Java code (SET statement only):

FrameElement[] elementList0 = new FrameElement[]
{
   new Element(subscript(num, 1), frame0.widgetNumArray0()),
   new Element(subscript(num, 2), frame0.widgetNumArray1()),
   new Element(subscript(num, 3), frame0.widgetNumArray2()),
};

frame0.set(elementList0);

FWD fully supports this syntax, by detecting and expanding the implicit references during conversion.

Range Subscripting Syntax Expansion

The same language statements (see Array Expansion above) that support the array expansion syntax, also support the specification of a range of elements using a FOR construct inside the subscript operator (after the first numeric expression). This form can't be used inside normal expressions but is implicitly expanded to be the same as a list of those elements.

The general syntax is:

array_var[numeric_expr FOR num_literal]

This syntax can't be used everywhere, but it is known to work anywhere an unsubscripted array reference will work. This means it is something that can and should be expanded. FWD does this during conversion, except instead of expanding the entire array, it only expands the specific referenced range.

Range expansion in FWD only works if the numeric_expr is a literal. The end of the range specification (the number after the FOR keyword) must always be a numeric literal in the 4GL. When the start of the range is specified as a constant, the expansion can be statically handled. The conversion can know exactly the list of array elements that must be expanded.

FWD has no support for array subscripts with ranges that use a non-constant expression as the start index. For example, myvar[ 2 + j FOR 3 ] is something that can only be evaluation at runtime, so the conversion cannot know in advance which start element is being referenced. The dynamic nature of this syntax has implications (for example, it could dynamically change a frame definition). As such, it is unsupported in FWD.

The following is an example of valid 4GL range expansion:

define variable num as integer extent 5.
update num[2 FOR 2].

This would be the equivalent of specifying this UPDATE statement:

update num[2] num[3].

Here is the resulting Java code (UPDATE statement only):

FrameElement[] elementList0 = new FrameElement[]
{
   new Element(subscript(num, 2), frame0.widgetNumArray1()),
   new Element(subscript(num, 3), frame0.widgetNumArray2()),
};

frame0.set(elementList0);

Limitations

This summarizes the limitations currently present in the array support in FWD.

Arrays are fixed size. The v10 support for arrays that have their length determined at runtime is not available.

Use of the range support (FOR clause) must have a start index which is a constant.

There is no support for arrays as a parameter (this is a v10 feature for procedures and functions). Since arrays cannot yet be used as parameters, there is no support in TransactionManager.deregister() for arrays.

UNDO

By default the following 4GL syntax creates variables that must be registered for UNDO support:

4GL Language Feature UNDO Disabling Syntax Supported
DEFINE PARAMETER Use of NO-UNDO option in the statement itself or in the LIKE variable's definition. Yes
DEFINE VARIABLE Use of NO-UNDO option in the statement itself or in the LIKE variable's definition. Yes
Format Phrase with AS or LIKE Use of NO-UNDO option in the statement itself or in the LIKE variable's definition. Yes
MESSAGE SET/UPDATE with AS or LIKE Use of NO-UNDO option in the LIKE variable's definition. Yes

Although Function Parameters are fully supported in FWD, the 4GL always makes function parameters NO-UNDO. This is matched in FWD behavior with a lack of a registration.

The wrapper classes have very little idea about the concept of UNDO (except for the fact that they implement the Undoable interface which provides the necessary API for capturing a snapshot and restoring that snapshot.

In Progress, all of the above variable definition mechanisms by default result in an UNDO variable. However, in Java the default behavior has been reversed. By default a new instance of a wrapper class (e.g. new integer(14)) is NO-UNDO. Instead, in Java, an explicit registration of an Undoable instance must be made to force a given instance to be UNDO.

UNDO behavior is implemented by registering the associated variable with the TransactionManager for UNDO support. This must be done in the init() method of the block in which the variable is defined.

The following TransactionManager registration methods are used:

Method Usage
registerUndo(Undoable) Used to register a single variable for UNDO support starting in the current block.
register(Object ...) Used to register a list of variables for UNDO support starting in the current block.
registerUndo(true, Undoable) Used to register a single global variable for UNDO support in all blocks.
register(true, Object ...) Used to register a list of global variables for UNDO support in all blocks.

For examples of this, please see the corresponding variable definition sections above. For details on forcing parameters to be NO-UNDO (TransactionManager.deregister()), see DEFINE PARAMETER above.

More details on the UNDO implementation can be seen in the Transactions chapter.


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