Project

General

Profile

Object Oriented Classes and References

Introduction

Starting in OpenEdge v10, it became possible to write 4GL code structured as objects. This object oriented approach provides subset of OO support in traditional OO languages, including the ability to define a class and create instances (individual objects) of those classes. When creating new OO 4GL code, that code must be put into .cls source files using new OO syntax for defining classes, methods, properties and so forth.

These new classes will exist in the source tree of your project, along with the traditional procedural 4GL code, but the OO code will be organized into packages of hierarchical directories. Any 4GL code that references these classes will either use a fully qualified (package + classname) name or will use the non-qualified class name. Either way, when such references are encountered, the parsing of the referencing source file is paused while an attempt is made to find the class. In v11 partially qualified names can be referenced, but this is not supported by FWD yet. If the referenced class cannot be found, then the parsing of the current file will fail. Conversely, if the class is found, then that class is parsed and any referenced classes are searched for and parsed until the entire graph of referenced classes has been loaded. At that point the parsing of the original source file can continue.

Considering that the code is integrated into the rest of the project sources, the Progress approach was to search through the PROPATH to find fully qualified matches OR to check if an unqualified class name matched the class name portion of an explicitly specified USING statement OR to substitute the unqualified name into any USING specifications that have a wildcard and search the PROPATH for a match.

Most of the 4GL language still can only be accessed using existing non-object syntax. However, there is a small amount of functionality which has been created in built-in OO 4GL classes (e.g. Progress.Lang.Object) that can be accessed and subclassed just like other classes in the application. When classes need to be found, these built-in classes are also searched. The difference is that these classes don't reside in the application directories or PROPATH.

Starting in OpenEdge v11, this same OO syntax can be used to access .NET objects. Those .NET objects can be in .NET assembies or can be from the built-in .NET classes. Searches for classes must handle these additional possible matches.

It is this class resolution or searching process that needs special attention and configuration in order to properly parse OO 4GL code. This chapter provides guidance on how to configure the conversion project to handle these cases.

Support in FWD

Since FWD v3.1, FWD provides parsing and analytics support for OO 4GL and 4GL code referencing .NET objects. This support was improved in subsequent versions. As of FWD v4.0, full conversion and runtime support is available for OO 4GL code including the ability to directly reference Java classes. .NET classes are not supported at conversion time.

The work for OO 4GL support was largely completed in #3751 and the remaining items are in process in #4373 (final development tasks for core OO 4GL support) and in #4384 (support for a range of common built-in OO 4GL classes). There is quite a lot of detail available in these tasks in regard to the implemention of both the conversion and the runtime support.

Configuration and Setup

The following are extra setup and configuration steps needed before trying to parse your OO 4GL code.

  1. Place the .cls files into the source directories as you would for any other 4GL code.
  2. Update the PROPATH for the project to include any paths needed to properly find the corresponding OO classes. This requirement is the same as would be needed for compiling OO 4GL code in OpenEdge. See the PROPATH related portions of Configuration Reference and Conversion Hints.
  3. Make sure that .cls files are processed as part of the conversion list. See Running the Command for details on how to specify the list of source files (procedures and OO classes) to convert. It is important to note that in addition to the specifications for .w and .p files, you must also add .cls files. A common specification for this is (*.[pPwW]|*.cls).
  4. Add the skeleton classes.
    • Make sure to set oo-skeleton-path configuration parameter in the p2j.cfg.xml.
    • Check out or download the latest skeletons project and place it in the location specified by the oo-skeleton-path.
  5. In FWD v3.2 and earlier, you must generate the class map before parsing. In FWD v3.3 and later, this step is no longer needed.
  6. Enhance the skeletons as needed to allow your OO 4GL code to parse.

With the above items properly completed, entire OO projects should parse and convert. The analytics will likewise work and there are many reports to explore the OO 4GL and .NET usage of the project.

Skeleton Classes

What is a Skeleton Class?

A "skeleton" class is a 4GL .cls source file that has the naming and member structure (data and methods) of a real class but has no actual implementation in any of the methods. For an example, see Contents.

Why Skeleton Classes Are Needed

For those using the OO features of Progress 4GL, it is common to access to the built-in 4GL OO classes. Examples of built-in classes are Progress.Lang.Object and Openedge.Web.Webhandler.

Part of the OO 4GL features is the ability to access .NET classes. These .NET classes may include Microsoft's .NET system classes, your own custom .NET classes or 3rd party .NET classes.

This means that in addition to the OO 4GL code in your project, there are 3 other kinds of classes that can be referenced in 4GL code:

  • built-in 4GL OO classes (e.g. Progress.Lang.Object)
  • classes in .NET assemblies (3 rd party code and/or your own .NET code)
  • built-in .NET classes (e.g. System.Object).

For OO 4GL code that is part of your 4GL project, there are 4GL procedures or OO 4GL classes (.cls) which have the OO 4GL code itself. If the PROPATH is configured properly, then all OO 4GL code in the project will be found during the initial parsing of the project. The parser recursively parses the OO 4GL code in your 4GL source files including any of your .cls files to understand all the internal features of those classes. That allows the parser to resolve all of those references. The parser uses the propath to find the .cls files. The built-in OO 4GL classes and the .NET classes are different because there is no 4GL source code in your project for these other classes/interfaces.

When the conversion tools parse your 4GL project, any references to these built-in classes or .NET classes will be referencing code that the project does not contain. In each of these cases, the class names can be referenced and within those classes and interfaces there are methods, properties and other OO features that can be accessed directly. In other words, these are external references which will need to be resolved at parsing time. Without some knowledge of the naming and structure of those external classes, the 4GL code of your project cannot be parsed.

In Progress, the OpenEdge compiler has access to the built-in classes AND on Windows it can also access any of the .NET assemblies or built-in classes that are installed on that system.

In FWD, we do not have access to the built-in 4GL OO classes. The tools may or may not be running on a Windows platform, but regardless of that fact, FWD does not directly inspect .NET assemblies or classes.

At this time, the parser handles these external dependencies by skeleton classes that have a "stub" interface that matches the external API of the corresponding classes. This external API includes the names, data types and API structure of all public or protected members. For example, every public or protected data member or enum is defined. Every public or protected method is defined as an empty method with the correct signature (return date type, method name, parameter signature).

These skeleton classes can be recursively parsed just like the application's own .cls files. This “trick” makes it possible to easily extend the set of skeleton files whenever the application fails parsing because of a reference to an unknown built-in 4GL OO class or unknown .NET assembly or built-in class.

As part of FWD, a set of skeleton classes is available at Skeleton Classes Download. This will reduce or eliminate the need to create skeletons from scratch. These are not complete and you may find a need for additional skeletons or changes to the existing skeletons. If you care to contribute your changes back to the FWD project, please contact mailto:.

Configure the Location

The root directory for the built-in 4GL OO and buillt-in .NET files is specified by the oo-skeleton-path configuration parameter in the p2j.cfg.xml. The most common specification for this path is ./abl/skeleton/.

Place the Skeletons

All the skeletons must be placed in the location specified by the oo-skeleton-path. As noted above, you can check out or download the latest skeletons project with a large number of the skeletons already written for you. The entire set of files must be placed in a hierarchy inside this oo-skeleton-path directory. For example:

./abl/skeleton/oo4gl/Progress/Lang/Object.cls
./abl/skeleton/oo4gl/Progress/Lang/ProError.cls
./abl/skeleton/oo4gl/Progress/Lang/Error.cls
./abl/skeleton/oo4gl/Progress/Lang/SysError.cls
./abl/skeleton/oo4gl/Progress/Lang/Class.cls
./abl/skeleton/oo4gl/Progress/Lang/ParameterList.cls
./abl/skeleton/oo4gl/Progress/Util/EnumHelper.cls
./abl/skeleton/dotnet/System/Object.cls
./abl/skeleton/dotnet/System/MarshalByRefObject.cls
./abl/skeleton/dotnet/System/ICloneable.cls
./abl/skeleton/dotnet/System/EventHandler.cls
./abl/skeleton/dotnet/System/Runtime/Serialization/ISerializable.cls
./abl/skeleton/dotnet/System/ComponentModel/Component.cls
./abl/skeleton/dotnet/System/ComponentModel/IContainer.cls
./abl/skeleton/dotnet/System/ComponentModel/IComponent.cls
./abl/skeleton/dotnet/System/ComponentModel/IsynchronizeInvoke.cls

Built-in 4GL OO classes are in an oo4gl subdirectory and then have directories for the package structure. The built-in .NET classes are in a dotnet subdirectory and then have directories for the package structure. All packages and filenames match the case-sensitive version documented by Progress or Microsoft.

Any .NET assemblies that are not built-in to .NET are considered “application” code and the skeletons for those must be located in a ./assemblies/ directory off the project root ($P2J_HOME). This is an example:

./assemblies/MyApp/Something/Useful1.cls
./assemblies/MyApp/Something/Useful2.cls
./assemblies/AnotherApp/Else/Useful3.cls

Again, the package pathing is duplicated below the ./assemblies/ directory and each directory there must match the package components in name and case.

Contents of a Skeleton Class or Interface

FWD provides some existing skeletons as they have been coded so far, but these may be incomplete. Please see Downloads for the latest skeletons available.

The following is an example of ./p2j/skeleton/oo4gl/Progress/Lang/Object.cls:

class Progress.Lang.Object:

   define property next-sibling as Progress.Lang.Object
      get.

   define property prev-sibling as Progress.Lang.Object
      get.

   constructor public Object():
   end constructor.

   method public Progress.Lang.Object Clone():
   end method.

   method public logical Equals(input other as Progress.Lang.Object):
   end method.

   method public Progress.Lang.Class GetClass():
   end method.

   method public character ToString():
   end method.

end class.

Notice that this is an empty implementation (hence the name "skeleton"), based on the documented API for the class. It is just enough to satisfy any parsing requirements without any real functionality being duplicated. When you create or modify any skeletons, make sure that the access control and data types match exactly with the API for that class.

The .NET classes are handled the same way, even though in .NET the actual interface would be quite different (since it would be done in C# or some other language that is not the 4GL). However, FWD still uses the 4GL .cls approach to “fake out” the parser into thinking these classes actually exist.

Enhancing the Skeletons

As parsing failures are found, the existing skeletons will need to be expanded to include any classes or interfaces that are referenced but not already present. It is also possible that existing skeletons will need features added, as they may be incomplete.

If there are 3rd party classes which are not included in the skeletons (or are included there but are not complete), then you will need to enhance the skeletons to add that support before references to those classes can parse. This is true for built-in OO 4GL classes as well as any .NET classes or other 3rd party OO dependencies which are not present in source code for this project.

Generating .NET Skeletons

Usage of .NET classes from 4GL code can pull in hundreds or even thousands of classes in the object graph. Many of the most common skeletons for these classes already exist in the skeletons project. If parsing reports missing .NET classes or interfaces, starting in FWD v4 there is the "skelgen" tool for generating .NET skeletons.

The skelgen tool is made up of 2 4GL programs which can be found in tools/skelgen/process-object-graph.p and tools/skelgen/skelgen.p. Together this is a program that uses 4GL reflection to capture an entire .NET object graph as valid skeletons. We only ever use this to generate .NET skeletons.

1. Create a list of .NET classes which need to be captured. The list should be a simple text file named root-list.txt and it should contain one fully qualified .NET class name per line. For example:

System.GC
System.Windows.Forms.ControlPaint
System.IO.Directory
System.Net.FtpWebResponse
System.Net.WebRequestMethods

Notice that there are no path separators (/ or \). These are names as we would see them in a 4GL program.

2. Create the list of existing .NET skeletons and name it the ignore-list.txt. This will do it:

cd skeleton/dotnet/
find . -type f | sed "s|^\./||" | sed "s|\.cls$||" | tr '/' '.' > ignore-list.txt

3. Copy these files to a dev system that can run 4GL GUI code from source:

process-object-graph.p
skelgen.p
ignore-list.txt
root-list.txt

Place them all in the same directory.

4. Find the assemblies path for the installation. On the 4GL dev system, there should be a set of installed .NET assemblies and an assemblies.xml that maps those .NET modules. You need to know the directory in which the assemblies.xml resides. For the example below, assume the file is d:\some\path\to\dotnet\stuff\assemblies.xml
so the directory is d:\some\path\to\dotnet\stuff\.

5. Determine the correct path to the prowin.exe (64-bit OE) or prowin32.exe (32-bit OE) for the 4GL dev system. This must be a properly configured/licensed OE installation with full 4GL development capabilities. The path might be something like d:\oe117_64\bin\prowin.exe. We will use this as an example below.

6. Change directory to the location where the skelgen .p and .txt files were copied. In this example, we will use x:\skelgen\.

x:
cd \skelgen\

7. Open up the procedure editor using a command line like this:

d:\oe117_64\bin\prowin.exe -debugalert -assemblies d:\some\path\to\dotnet\stuff\

Notice the -assemblies parameter. You must pass the proper value for your dev system.

8. Open the process-object-graph.p in the procedure editor. Run it using F2. It will take a while to run.

The programs start with a list of classes to be captured (root-list.txt) and it will capture those. But as part of capturing those classes, it must also capture all classes that it references and all the classes which the referenced classes reference and so on. This means it captures an entire "graph" of classes, starting with the root list. It takes a while to run and when there are no classes left to capture, it is done. Since we've previously captured many classes, we have an ignore list to avoid re-capturing those. This speeds up the process.

After it runs, you will find the *.cls and skelgen.log files which are the skelgen output. That output will be in the same directory as the programs and .txt files. All of the .NET classes to be captured must already be available on your system in order to capture them. In the skelgen.log, if there are any classes that cannot be found, there is probably something wrong with the path the assemblies. It would be best to rerun with the problem corrected.

Interestingly, the current process fails to generate skeletons for a number of standard .NET classes, especially those in the collections packages. Those will have to be manually handled.

9. Move the files into the skeletons project.

cd abl/skeleton/dotnet/
mv <path_to_generated_dotnet_skels>/*.cls .
ls *.cls | sed "s|\.cls$||" | tr '.' '/' | xargs dirname | sort | uniq | xargs mkdir -p
for fname in $(ls *.cls); do target=$(echo "$fname" | sed "s|\.cls$||" | tr '.' '/' | sed "s|$|\.cls|"); mv $fname $target; done

10. At that point you can test them out. These can be sent to Golden Code for review and to be included in the skeletons project.

Class Map

Starting in FWD v3.3 the class map is no longer needed. The parser automatically calculates the class map in that version and later.

In order to resolve references to application classes (.cls source files) from within the same application, FWD uses a configuration file to map class names to relative filenames. This map is not used for lookup of built-in 4GL OO classes (e.g. Progress.Lang.Object), it is not used for lookup of classes in .NET assemblies and it is not used for lookup of built-in .NET classes (e.g. System.Object).

Before you can parse an application containing these 4GL OO class references you must create this class mapping configuration file. Since any 4GL OO .cls source file must by nature reference other objects, this is also needed to parse .cls source files.

The class mapping file must be placed in ./cfg/class_map.xml.

Format

The format is as follows:

<?xml version="1.0"?>
<map>
   <entry key=”fully_qualified_package_and_class_name1” value=”relative_filename1” />
   <entry key=”fully_qualified_package_and_class_name2” value=”relative_filename2” />
   <entry key=”fully_qualified_package_and_class_name3” value=”relative_filename3” />
</map>

Each entry will have a key and a value that forms the mapping.

The key in the map is the fully qualified class name without the .cls and including the full package hierarchy with / characters as path separators.

On case-sensitive file systems, make sure that the filename matches the actual filename's case exactly.

The value is the relative filename for the .cls file, from the root of the project (including the .cls extension).

Example

Assume you have the following 4GL OO files:

Filename Package (Qualifier from CLASS statement) Class Name
/src/my_app/some/package1/subA/MyObject1.cls package1/subA MyObject1
/src/my_app/some/package1/subB/MyObject2.cls package1/subB MyObject2
/src/my_app/other/MyObject3.cls   MyObject3
/src/my_app/what/package2/MyObject4.cls package2 MyObject4

Since the search algorithm is based on the PROPATH in the 4GL, the PROPATH must include the relative portions of the paths that are not part of the package names. In this example, the PROPATH will have to be configured including the following ./src/my_app/some/:./src/my_app/other:./src/my_app/what:

This will have to be configured in the p2j.cfg.xml in the propath global configuration option.

The resulting ./cfg/class_map.xml should look like this:

<?xml version="1.0"?>
<map>
   <entry key="package1/subA/MyObject1" value="./src/my_app/some/package1/subA/MyObject1.cls"/>
   <entry key="package1/subB/MyObject2" value="./src/my_app/some/package1/subB/MyObject2.cls"/>
   <entry key="MyObject3"               value="./src/my_app/other/MyObject3.cls"/>
   <entry key="package2/MyObject4"      value="./src/my_app/what/package2/MyObject4.cls"/>
</map>

On a Linux or UNIX system, the value must match the case of the filename exactly.

Tool for Generation of the class_map.xml

Starting in FWD v3.1, there is a tool called GenerateClassMap for creating the class_map.xml by inspective the file system. Run it from the project root directory, like this:

java -classpath p2j/build/lib/p2j.jar:.: com.goldencode.p2j.uast.GenerateClassMap ./abl/some/path/to/the/root/of/the/class/hierarchy/ ./abl/another/package/root/path/

The arguments are a kind of propath list, with each arg value being treated as a root package from which all contained .cls are
relative. You must pass at least 1 value. If all the OO classes are rooted at ./abl, then that would be a valid argument.

WARNING: if any classes are found, this will overwrite your cfg/class_map.xml file with no prompt! Back up any version you intend to save!


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