Professional Documents
Culture Documents
Forget what you think you know about COBOL. A 50+ year old
language it may well be, but COBOL applications still reign in the world
Visual
of enterprise IT. With billions of transactions executed every day and
often running behind the scenes, COBOL systems touch many aspects
of our daily lives. Your mission: to start a new era of innovation powered
Visual COBOL
by modern tools that bridge COBOL systems to the world of Java and
.NET. Brought to you by the leader in COBOL development tools, this
COBOL
book is written for the COBOL, Java, and .NET developer.
Key features include:
+ A simplified, real-world example to illustrate key concepts
+ An explanation of the .NET and Java object models for the
COBOL developer
+ An introduction to COBOL for the Java or .NET developer
A DEVELOPER S GUIDE
+ A complete reference to the new syntax for Visual COBOL
TO MODERN COBOL
+ A free student development tools license integrated within
Visual Studio and Eclipse
12 12
P R E S S PRESS
boxtwelve
A DEVELOPER S GUIDE
TO MODERN COBOL
Copyright 2017 by Micro Focus. All rights reserved.
No part of this publication may be reproduced, stored in a retrieval system or transmitted in any form or by any
means, electronic, mechanical, photocopying, recording, scanning or otherwise, except as permitted under
Sections 107 or 108 of the 1976 United States Copyright Act, without either the prior written permission of
the Publisher, or authorization through payment of the appropriate per-copy fee to the Copyright Clearance
Center, Inc. 222 Rosewood Drive, Danvers, MA 01923, (978) 750-8400, fax (978) 646-8600. Requests to
Micro Focus for permission should be addressed to the Legal Department, Micro Focus, 700 King Farm Blvd,
Suite 125, Rockville, MD 20850, (301) 838-5000, fax (301) 838-5034.
Micro Focus, Net Express, Server Express, Visual COBOL, COBOL Server, and Micro Focus Server are either
registered trademarks or trademarks of Micro Focus in the United States and/or other countries. Other product
and company names mentioned herein may be the trademarks of their respective owners.
The example companies, organizations, products, domain names, e-mail addresses, logos, people, places,
and events depicted herein are fictitious. No association with any real company, organization, product, domain
name, e-mail address, logo, person, place, or event is intended or should be inferred.
The book expresses the authors views and opinions. The information contained in this book is provided without
any express, statutory, or implied warranties. Neither the authors, Micro Focus, Box Twelve Press, nor their
resellers or distributors will be held liable for any damages caused or alleged to be caused either directly or
indirectly by this book.
This document is provided as-is. Information and views expressed in this document, including URL and other
Internet Web site references, may change without notice. Some examples depicted herein are provided for
illustration only and are fictitious. No real association or connection is intended or should be inferred.
ISBN-13: 978-0692737446
ISBN-10: 0692737448
CHAPTER 1
CHAPTER 2
CHAPTER 3
Getting Started 13
CHAPTER 4
CHAPTER 5
Writing a Class 45
CHAPTER 6
CHAPTER 7
CHAPTER 8
CHAPTER 9
C H A P T E R 10
C H A P T E R 12
C H A P T E R 13
C H A P T E R 14
C H A P T E R 15
Statements 304
C H A P T E R 16
CHAPTER 1
CHAPTER 2
CHAPTER 3
Getting Started 13
Objects and the Rental Agency Example ............................................................................................... 13
Running the Leases Example........................................................................................................................... 18
Aims of the Visual COBOL Language ......................................................................................................29
Summary............................................................................................................................................................................ 31
CHAPTER 4
CHAPTER 5
Writing a Class 45
A Simple Date Class.................................................................................................................................................45
Namespaces....................................................................................................................................................................57
Summary ...........................................................................................................................................................................60
CHAPTER 6
CHAPTER 7
CHAPTER 8
CHAPTER 9
C H A P T E R 10
C H A P T E R 11
C H A P T E R 12
C H A P T E R 13
C H A P T E R 14
C H A P T E R 15
Statements 303
Attach .............................................................................................................................................................................. 304
Create................................................................................................................................................................................ 305
Declare............................................................................................................................................................................. 305
Delete................................................................................................................................................................................ 306
Detach.............................................................................................................................................................................. 307
Goback............................................................................................................................................................................. 307
If............................................................................................................................................................................................. 308
Inspect.............................................................................................................................................................................. 308
Invoke................................................................................................................................................................................. 310
Perform Simple Loop............................................................................................................................................ 310
Contents ix
Perform Using..............................................................................................................................................................311
Perform Varying (Collections)......................................................................................................................... 312
Raise................................................................................................................................................................................... 314
Read Collection.......................................................................................................................................................... 315
Reset Collection........................................................................................................................................................ 315
Rewrite Collection.................................................................................................................................................... 316
Set......................................................................................................................................................................................... 316
Sort....................................................................................................................................................................................... 317
String................................................................................................................................................................................... 319
Sync.................................................................................................................................................................................... 320
Try Catch..........................................................................................................................................................................321
Unstring............................................................................................................................................................................324
Write....................................................................................................................................................................................325
Summary..........................................................................................................................................................................326
C H A P T E R 16
Index 343
Acknowledgements
This book would not have been possible without the talented and tireless efforts of the following
individuals and groups.
To the Micro Focus COBOL development teamthank you for your collective commitment
to the Visual COBOL product.
To Mark Conway, a long-time advocate of this bookthank you for your continued support.
To Peter Lawthank you for your content contributions in support of this book.
To Ed Aireythank you for leading this project and without whose project management
efforts and focus on our goal, we might never have seen a printed page.
To Brian Pinnell, Evan Williams, and Cindy Nelson thank you for your legal expertise, sage
guidance, and support from the very beginning.
To David Groomthank you for your design guidance and creative contributions.
Thanks to the consultative, editorial, and publishing support of Box Twelve Communications
in bringing this book to fruition.
And last but not least, a special thanks to Grace Hopperthe mother of COBOLfor her
innovative spirit that inspires us all.
1
2
There is some degree of truth in both these viewpoints. Elements of most COBOL business
systems usually do contain some pretty old codebasesthe origins of which could well date back
to a time when the Beatles were entertaining audiences the world over. Look deeper, and youll see
that, actually, the code has been through progressive rounds of modernizationrenovated to meet
new business needs every few years. COBOL applications are often a blend of old and modern,
both at the same time.
Visual COBOL is the next station on a long and illustrious journey for the COBOL language and
the applications that have been created with it. And although there is a plentiful supply of examples
that illustrate how COBOL has adapted to its surroundings throughout the years, Visual COBOL
offers application developers something brand-new: a way to connect COBOL applications with
other languages and systems that tears down many of the physical and artificial barriers that have
existed in the past.
This is big news even by COBOL standards. And its even bigger news for the Java and .NET
community creating twenty-first-century IT built on COBOL systems.
code. If youre wondering what a million lines of code looks like, think about 15 copies of War and
Peace side by side. And then scale up from here for the COBOL systems that are 5, 10, 20, or
even 50 million lines and counting.
Throwing all this away to start on a multiyear rewrite exercise is usually something that never
ends well for anyone who ever tried.
So where does this get us to in our understanding of why COBOL is still here today? Well, we
know COBOL applications are quite big relative to other types of software and theyre valuable
to the businesses that use them, too. Even though technology has moved on, the cost and risk
of replacing them doesnt add up at all. But the business still has evolving IT needsapplication
software is never really done and finished.
Businesses arent sitting there suffering, unable to move this monolithic COBOL system forward,
stuck with outdated technology. In the 50+ years COBOL has been in business, it too has moved
on and kept pace with technology trends. If it hadnt, we certainly wouldnt have a story to tell here
today. By keeping apace, COBOL developers have been able to adapt existing applications to sup-
port new business requirements. From character mode user interfaces to graphical applications,
from desktop to web, from application to service to mobile to cloud, and so on. COBOL and the
applications written in it have continued to evolve and adapt.
So, is that the complete picture? Well, almost. COBOL applications are often big and intertwined
with other systems, making it difficult to remove them even if you wanted toand why would you
want to when theyre actually doing a great job and the technology itself continues to evolve to
meet new demands?
But theres one other secret to its success.
Very often, it is the hardware platform that is the trigger for IT change, and often, cost or IT agility
is the driving force behind this. Existing applications that cant move to the new hardware have no
place in the new IT landscape and are destined to be replaced by a new order.
That is, of course, unless the application can jump ship. For COBOL, application portability
was built in from the very beginning. New hardware could come and go as often as needed and the
COBOL applications could simply carry on. So, too, could the business.
COBOL rarely forces you to throw away anything youve created. Just because your IT systems
now run on Linux, or Windows, or your preferred platform of choice for new IT is in .NET or the Java
Virtual machine (JVM), companies can still take all of that existing codebase that they value and have
invested in over the years and run it on the new platform with minimal upheaval. This isnt entirely
a factor of COBOL the language, although it has always been designed with portability in mind.
It is as much due to Enterprise software vendors like Micro Focus that make it possible to reuse a
decades-old application on new operating platforms and devices with the least change and risk to
the application and the business that uses it.
In a real-world example, consider the last time you checked your bank statement. Nowadays,
that could have just as easily been achieved using your smartphone or Internet browser. You could
also have used an ATM at a hole-in-the-wall or the old-fashioned approachand visited a branch
of the bank and talked to a real human being. What we need to keep in mind throughout all of these
interactions is that the bank didnt need to create brand-new COBOL systems for each one of
these use cases. Instead, they reused the existing programs to support the new use case. Sure,
they stuck Java or .NET front ends on to elevate the user experience, but at the core of the system
is a COBOL applicationthe same one theyve been using for years. For banks, insurers, financiers
Here Today, Here Tomorrow 5
around the world, and great many other organizations, this adaptable nature of COBOL has been
the key to its ongoing success.
But lets not stereotype COBOL. Sure, the financial industry has come to depend on the reliability
and performance of COBOL applications that have been fine-tuned and optimized for performance
and zero defects over the decades. Banking, insurance, payroll, pensions, point-of-salemuch of
the worlds finances do indeed run through COBOL systems. As do logistics, aircraft maintenance
schedules, livestock genetic history, holiday bookings, car assembly lines, hospital supplies, CRMs
and ERPs the world over, and the IT systems of many businesses that were founded before the
year 2000.
COBOL has managed to achieve something quite remarkable. It has transcended technology
trends for over 50 years and continues to support business worldwide. It has future-proofed itself.
So, before you put finger to keyboard and type a line of code, keep in mind the COBOL youre
writing today could well have a lifetime longer than you!
CHAPTER 2
7
8
the most important concepts available in Visual COBOL is the ability to compile a COBOL program
either to Java bytecodeto run under the Java Virtual Machineor to Microsoft intermediate language
(MSIL)to run under the .NET Common Language Runtime (CLR).
This unique capability is helping organizations around the world breathe new life into their COBOL
applications. This book was written to help you, the developer, whether youre a Java, .NET, or CO-
BOL developer, get the most from Visual COBOL.
of the potential these platforms have to offer, object-oriented programming is a must. So, if youre
new to object-orientation, we introduce the concepts along with the language.
The rest of the book leads you through the fundamental concepts of the Visual COBOL language
and how to interact with what JVM and .NET have to offer.
Book Structure
The intention of Visual COBOL: A Developers Guide to Modern COBOL is to provide a good
reference guide to the extensions to the COBOL language that we have named Visual COBOL, and
also to impart a good understanding of how the language should be used. To achieve this, we have
split the bulk of the book between a hands-on How To style guide and a more rigorous Reference
section. We have not documented the entire COBOL languagethere is plenty of material available
(including the Micro Focus documentation) that already does this. We have tried to explain concepts
throughout, and point out the few differences in behavior between Visual COBOL on the .NET and
JVM platformsas well as explain the reasons.
One of the problems with documenting any programming language is that it can be very hard to
provide meaningful examples and explanations that dont require knowledge of parts of the language
you havent yet learned. The hope is that even where an example is using constructs we havent yet
10
explained in detail, they are reasonably easy to follow, and we try to provide cross-references so
that if you want to find more information, you can.
This book is divided into three sections:
Foreword: Introductory material, including this chapter.
Developing with Visual COBOL: The concepts of Visual COBOL explained with lots of
code examples.
The Visual COBOL Reference: A formal definition of the Visual COBOL language, with lots of
code snippets and some runnable examples. This is not a full definition of the entire COBOL
language, only the Micro Focus set of extensions that enable effective programming on the
.NET and JVM platforms. A full COBOL Language Reference for the procedural COBOL
language is included in all Micro Focus COBOL Product Documentation.
COBOL programmers with no or little experience of object-oriented languages will probably
want to read the Developing with Visual COBOL section in order, but Java and .NET program-
mers might want to read just Chapter 3, Getting Started, then skip through to the chapters on
interoperation where we explain some basic COBOL concepts that might help them when working
with legacy code.
The Visual COBOL Reference section, which starts at Chapter 12, defines the Visual COBOL
language syntax and semantics. It also has lots of code examplessome are just snippets, but a lot
of them are short but fully working programs you can run from an IDE or through the command line.
Prerequisites
To be able to run the examples in this book, you will need one of the products from Micro Focus that
includes the Visual COBOL compiler. There are two families of Visual COBOL productsthose
based around Microsoft Visual Studio and those based around Eclipse. As well as commercially
licensed products from Micro Focus, you can also download Visual COBOL Personal Edition, which
is free for noncommercial use. Go to www.microfocus.com/visualcobolpe to see the full range of
Visual COBOL products.
The examples have been compiled, built, and run using Visual COBOL 2.3 update 2. You can
follow most of the book with earlier product releases, but be aware that there might be particular
syntax that might not always work with earlier versions of the product.
Examples
The examples in the tutorial part of the book are available online (apart from some of the short
code snippets) and supplied with Visual Studio and Eclipse project files so that you can open and
build them with whichever Visual COBOL product you have installed. To download them, go to
http://www.microfocus.com/visualcobolbook.
Some of the examples consist of several dependent projects, in which case there is a single
solution file for Visual Studio to open them all together. For Eclipse, you will need to import all the
projects into the same workspace. To make life easier, each example is in a separate folder numbered
by chapter. A lot of the chapters deal with a single sample application, called the Rental Agency,
which is extended and enhanced progressively during each chapter.
Examples 11
Most of the code in this book has been written as cross-platform code. This means the same
source files compile and run the same whether you are using Visual COBOL for .NET or JVM, and
we have supplied the examples as a single set of sources you can use with either platform. However,
JVM has one requirement that .NET does not. In Java, a source file has to have the same name as
the class it defines, and must appear in a subfolder structure that matches its package name (this
term is explained in the Namespaces section in Chapter 5). Although the COBOL compiler does
not enforce this convention, the Eclipse IDE works much better if you organize your sources this
way. Accordingly, we have organized our projects so that the source files are stored according to
the Java convention, but the Visual Studio projects (.cblproj files) are stored in the same directory
as the source files so that in Visual Studio you dont have to navigate through several folders to see
the sources for a project.
CHAPTER 3
Getting Started
This chapter provides a gentle introduction to Visual COBOL with a simple procedural COBOL
program that uses a set of objects that have already been defined, showing how Visual COBOL
bridges between traditional COBOL applications and object-orientation and managed platforms.
This chapter also shows you how to use Visual Studio or Eclipse to build and run the examples
used in this book.
In this chapter, youll learn about:
Objects
Visual Studio and the debugger
Eclipse and the debugger
on a web server. Along the way, we will cover topics like cross-platform programming (dealing with
the differences between the JVM and .NET platforms), interoperation with procedural COBOL, and
interoperating between COBOL and C# or Java.
This means the data item is a reference to something that must be of type RentalProperty
(meaning there is a class somewhere called RentalProperty that defines it). Our example declares
two lists inside working-storage. A list can hold any number of objects of a particular type (lists
grow as you add new elements to them).
program-id Leases.
working-storage section.
01 today type IDate.
01 nextWeek type IDate.
01 rentalProperties list[type RentalProperty].
01 leases list[type Lease].
01 aLease type Lease.
01 aHouse type RentalProperty.
01 anApartment type RentalProperty.
01 landlord type Landlord.
01 person1 type Tenant.
01 person2 type Tenant.
procedure division.
*>----------CREATE DATA-------------<
*> Create people
set landlord to new Landlord(Gritpype Thynne
gritpype.thynne@examples.com)
set person1 to new Tenant(Jules Bona jules.bona@examples.com)
set person2 to new Tenant(Neddie Seagoon neddie.seagoon@examples.com)
*> Create properties
create rentalProperties *> a list for storing the properties.
set aHouse to new RentalProperty(new Address(15 Lee Terrace,
Lewisham,
London, SE14 7TT))
set aHouse::MonthlyRent to 950
Objects and the Rental Agency Example 15
*> cancel the first lease and remove it from the list
invoke leases[0]::Cancel()
display size of leases *> shows 1 as one lease has been removed.
goback
end program.
The first part of the program sets up some data to use, creating some people (two tenants and
a landlord) and some properties to rent out. The new keyword creates an object of the specified
type (this is also known as instantiation). The Landlord and Tenant classes used here both
require you to specify two strings as arguments at creation timethese are the name and email
address of the person, respectively. When you instantiate an object using the new keyword, you
call a special method in the class known as a constructor.
This adds the object pointed to by aHouse to the end of the rentalProperties list. Before you
can use a list, you must instantiate it using the create statement, for example:
create rentalProperties
Once all the data has been set up, we have some short code showing different things we can
do with the objects now created. The first thing we do is to iterate through our list of properties and
display some information about them, using:
Objects and the Rental Agency Example 17
This assigns the data item nextProperty to each element in the rentalProperties list in turn.
Weve actually declared nextProperty inside the perform varying statement (nextProperty
as type Rental Property)this is another example of how Visual COBOL enables you to write
more concise code and declare data items where they are needed rather than needing to declare
everything in a working-storage section. To display some useful data about each RentalProperty,
weve used a method called DisplayValue(), which simply returns a string summarizing basic
information about the property.
The next section of code leases a property to a tenant. Our Lease object has a start date, an
end date, and is associated with a single property and a tenant. We are using our own date object,
called IDate, to represent dates. IDate is actually an interface rather than a class, which is explained
in Chapter 8. But for now, all you need to know is that we can get IDate objects from something
called the DateFactory.For example, to get todays date:
Our IDate objects have some useful methods built in; for example, you can get the date for one
weeks time by adding 7 days to the current date:
When you display a date, it defaults to displaying it in a short format appropriate to your current
locale (so for locales set to US English, dates will appear in the form mm/dd/yyyy, whereas for UK
English and most European locales, dates are shown in the form dd/mm/yyyy). All this functionality
is built in to IDate and is easily accessed from any client application.
To lease a property, we simply construct a lease object with the start date, length of lease in
days, the property we want to rent, and the tenant:
This code constructs the lease; part of the construction code for Lease also sends a message to
the RentalProperty passed in to let it know it has been leased. The next section of code displays
our RentalProperty objects againthe ones that have been leased now show as Leased rather
than Free. The last thing this program does is to cancel our first lease, and delete it from the list
of leases.
This short program illustrates the following:
The continuity between COBOL programs and Visual COBOL: Although this program
has been written as a traditional procedural program, it is using objects and classes. Any
COBOL program that can be compiled with the Micro Focus COBOL Compiler can be
compiled as Visual COBOL, and can then be extended using newer syntax.
The power and simplicity using object-orientation brings to application
programming: Each of the classes used in this example contains the logic appropriate to
manipulating the data it represents. For example, our IDate objects can be used to construct
new dates a fixed number of days ahead, and also know how to display themselves sensibly
18
for a given locale. All this code is available to your application as soon as you declare and
construct a date object, and is available through methods on the object itself.
In subsequent chapters, you will learn how to write the classes that represent these objects, and
we will progressively explain more advanced features of Visual COBOL. The next section shows
you how this example is built, run, and debugged in Visual Studio (for .NET) or Eclipse (for JVM).
We assume that you have already installed one of the Visual COBOL products
listed in Chapter 2, in the Prerequisites section, and carried out any steps
needed to license the product.
Running the Leases Example 19
Leases
RentalAgency
ExamplesSupport
Table 3-1 Default shortcut keys for Visual Studio debugger functions
Step Out Shift+F11 Step out of the current method, function, or call back to the caller
Start Debugging/Continue F5 Run the application until the next breakpoint, or to completion
You can explore Leases using the debugger. The following steps provide a starting point to help
you get familiar with the Visual Studio debugger.
1. Press F10. This starts the debugger and puts execution on the first statement of the
program (set landlord to new Landlord()). Visual Studio opens some new panes
as it goes into debug mode. You should see tabbed windows along the bottom for Autos,
Locals, Watch1, and Call Stack.
2. Click on the Autos tab if it isnt already selectedyou should see the data item for land-
lord in there, with the value null (see Figure 3-4). The Autos window shows variables
used in the current and previous statement so the information available changes as you
run the program. If you want to track a variables value for longer, you can add a Watch for
it (right-click on it and click Add Watch).
22
3. Press F10 to execute the first statement, and the Autos window changeslandlord now
has the value {MicroFocus.COBOL.Examples.Lettings.Landlord}. The statement we just
executed has instantiated a new Landlord object and assigned it to data item landlord.
4. Click on the arrow to the left of landlord to expand it. This enables you to see the data
inside the object. The first item in the list is MicroFocus.COBOL.Examples.Lettings.Per-
son. This is because Landlord inherits from the Person class, and when created, has
storage allocated for all the data defined by the Person class.
5. Expand MicroFocus.COBOL.Examples.Lettings.Person. This inherits from System.Object
(everything is ultimately descended from here). But it also defines three fields: Name,
Email, and Address. Address is another object and it hasnt been assigned a value yet,
so it shows null. But Email and Name are strings and show the values passed in through
the constructor (see Figure 3-5).
Running the Leases Example 23
You can step through the whole program this way, looking at how the values in objects change
as the program executes.
5. Click Next to move to the Import Projects page. Select the directory where you down-
loaded the Leases example. The Projects pane should show the three projects for the
example (see Figure 3-6).
6. Click Finish to import the projects into the workspace. Eclipse will build the projects
immediately; the default behavior for Eclipse is that projects are built whenever a change
is made.
Previously in this chapter, in the section entitled Running the Leases Example, we described
the dependencies between the projects (refer to Figure 3-1).
To see how the dependencies are represented in Eclipse:
1. Right-click on the Leases project and click Properties. The Properties dialog box opens.
2. Expand Micro Focus in the left-hand pane, and click JVM Build Path. The Projects tab
shows ExamplesSupport and RentalAgency as required projects (see Figure 3-7).
Running the Leases Example 25
We can now run the application from Eclipse. To do this, we need to create a launch
configuration. The simplest launch configurations describe the project and file needed to run an
application, but they can include extra information to set up the environment the application runs in.
To create a launch configuration to run the Leases example:
1. Click Run > Run Configurations to display the Run Configurations dialog box.
2. From the list on the left, click COBOL JVM Application, and then click the New
Launch Configuration button (the leftmost icon above the list of configuration types).
This displays the dialog box shown in Figure 3-8.
26
3. In the Name box, name the configuration Leases. In the Main class box, enter Leases.
4. Click Run to save the configuration and run the application. Output from the application is
displayed in the Console pane at the bottom of the main Eclipse window (see Figure 3-9).
Execute the next statement without stepping into any methods, functions,
Step Over F6
or calls included in the statement
Step Return F7 Step out of the current method, function, or call back to the caller
Breakpoint
3. To start the debugger, click Run > Debug History and then click Leases to start the
Leases Run configuration under the Eclipse debugger. Click Yes when the Confirm Per-
spective Switch dialog box opens to switch to the Debug perspective. Execution stops
where we put the breakpoint, on the first statement of the program (set landlord to
new Landlord()).
4. One of the panes opened by the Debug perspective is labeled Variablesclick the
Variables tab if it is not visible. You should see the data item for landlord, with the value
null. If landlord isnt displayed in the Variables pane, make sure the first Leases.cbl in
the Debug pane is selected (see Figure 3-11). The Debug pane displays the different ap-
plication threads and the Call Stack (this application has only one thread).
5. Press F6 to execute the first statement, and the Variables pane changes. landlord now
has the value Landlord (id=348). The id=348 here is the reference to the object, and
the value could be different when you execute this. The statement we just executed has
instantiated a new Landlord object and assigned it to data item landlord.
6. Click the arrow to the left of landlord to expand it. This enables you to see the data
inside the object. You can see Name contains the the name string we passed into the con-
structor, and if you expand Email (another type of object), you can see the email string.
Aims of the Visual COBOL Language 29
The Address field shows null. Address is another object, which is contained inside the
Landlord class, but it hasnt yet been assigned a value (see Figure 3-12).
You can step through the whole program this way, looking at how the values in objects change
as the program executes.
Dialects
Although there have been a succession of COBOL standards over the years, first overseen by ANSI
(American National Standards Institute) and later by ISO (International Organization for Standard-
ization), there are also a large number of COBOL dialects introduced by different vendors over the
years. The Micro Focus COBOL Compiler supports all the major COBOL dialects and many of
the lesser-known ones.
The dialect used is selected by setting compiler directives, but by default the Micro Focus com-
piler uses the Micro Focus dialect. The Micro Focus dialect is quite permissive and is largely case
insensitive, so you can write your programs in lowercase. Visual COBOL is a further development
of the Micro Focus dialect, with full support for object-oriented programming.
The Visual COBOL dialect itself has evolved a long way since the earliest version in 2002, and
has gradually enabled a much more compact code style by simplifying or dropping many of the
headers used in earlier versions, while remaining backward compatible with the older syntax.
Listing 3-2 illustrates both evolution and backward compatibility by showing two methods P and
Q, which take the same arguments and return the same result. They also both declare a local vari-
able ta string. Method Q is implemented with the latest syntax, and method P is implemented
with an earlier version of the syntax you might see in older Visual COBOL code. Method Q uses
fewer headers and declares the method arguments inline with the method-id header, making it
more concise and easier to read. But the program shown in Listing 3-2 will compile exactly as it is;
the Visual COBOL compiler is completely backward compatible even when two different forms of
syntax are used inside the same class.
This book only documents the latest version of the syntax, and we wont be showing code in the
form of method P again in this book. You might see code that looks like method P if you are working
with applications written using older versions of Visual COBOL products.
Listing 3-2 Two identical methods, comparing old and new syntax
class-id MicroFocus.COBOL.Examples.A.
end class.
Summary 31
This example not only illustrates the latest syntax, but also shows how Visual COBOL syntax has
been gradually refined and simplified to make it easier to work with, while still looking like COBOL
code. The syntax needed for object-oriented code is enabled in the compiler when you use the
ILGEN or JVMGEN directives (this is explained in more detail in Chapter 4). But you can also com-
pile legacy programs in any Micro Focussupported dialects by using ILGEN or JVMGEN together
with other dialect directives.
Summary
In this chapter, we provided a first introduction to the Visual COBOL language, and to an example we
will be using again later in this book, with a simple program that created and used some objects that
were already defined. We also took a look at the two Visual COBOL IDEs, Visual Studio and Eclipse,
to give a brief guide to how you can build, run, and debug all the examples provided with this book.
CHAPTER 4
We havent provided you with a download of the code for this chapter because
the programs are very short. The first one is only one line long. This is also the
only chapter where you will compile and run code from the command lineall of
the other examples are compiled and run from either Visual Studio or Eclipse.
33
34
4. Visual COBOL for Visual Studio builds a file called HelloWorldProcedural.exe. You can
run HelloWorldProcedural.exe by entering into the command prompt:
HelloWorldProcedural
Although weve now written our first working Visual COBOL program, this is strictly procedural
code; we have not used any object-oriented constructs in it. This program could, in fact, be compiled
to native code and would run just the same. More important, we can take any existing procedural
COBOL program, and Visual COBOL can compile it as managed code, enabling it to interoper-
ate easily with other managed code, whether written in Visual COBOL or in another language like
Java or C#.
One of the easiest ways to modernize existing procedural applications is to write a modern
object-oriented API using Visual COBOL. You can then recompile your procedural code to man-
aged code, and use the Visual COBOL API as a wrapper around it. You now have an API to your
existing procedural application that is easily consumed from other languages.
In the next section, we will write Hello World again, this time as a class. Later in this chapter,
we will explain what we mean by managed code.
A class, unlike a procedural program, does not have an obvious start point. The convention ad-
opted by many languages is that the start point of an object-oriented application is a public static
method named Main. Main takes a single argument, which is an array of strings. Listing 4-2 shows
the HelloWorld class. For comparison, Listing 4-3 and Listing 4-4 show the equivalent C# and
Java versions; all three programs look similar in that they all create a class called HelloWorld with
a public static Main method (and no fields). But only the COBOL one can run either as Java
bytecode or .NET code by recompilation.
Dont worry if terms like public, static, and method dont mean anything to you
yet. They will be explained as we work through the examples in the book. We
also explain all the language constructs formally in the Reference portion of this
book, which starts at Chapter 12.
You can compile and run the Visual COBOL version of Hello World in Listing 4-2 by using the
same instructions provided in the previous section.
end method.
end class.
using System;
namespace CSharpHelloWorld
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(Hello World);
}
}
}
36
{
public static void main(String[] args)
{
System.out.println(Hello World);
}
}
We refer to CLR and JVM bytecode collectively as managed code. This book is concerned
with Visual COBOL code, which can only be compiled as managed code. Managed code uses the
instruction set of an intermediate abstract machinethe abstract machine is a specification and
software implementation rather than a piece of hardware.
The CLR and JVM both consist of the abstract machine that the bytecode runs on and a run
time that provides support services. JVM and CLR are implemented differently and their bytecode
instruction sets are not compatible with each other; however, they are similar in concept. Both sup-
port features like classes, methods, objects, garbage collection, and metadata.
These features are all exploited by Visual COBOL code, which can be compiled to run on either
of these managed code run times. The diagram in Figure 4-1 shows the COBOL source dialects
and executable types supported by the Visual COBOL Compiler. The Visual COBOL Compiler can
compile any COBOL code as either native or managed code, and depending on the executable
format chosen, this code executes as native code, CLR bytecode, or Java bytecode. This means
that procedural COBOL code can execute on any platform, although once compiled as .NET or
Java bytecode, it can interoperate much more easily with languages like C#, Visual Basic, or Java.
COBOL code using the Visual COBOL dialect, which is a modern object-oriented type-safe lan-
guage, can only be compiled as managed code. Visual COBOL is suitable for:
Writing programs that must be able to run on both the Java and .NET environments (just
recompile)
Modernizing existing COBOL applications by providing object-oriented interfaces more
easily consumed by other applications
Writing the type of business applications COBOL has supported for over 50 years
38
ILGEN JVMGEN
Directive Directive
Executable
Code Formats Native Code CLR Bytecode Java Bytecode
.NET .JVM
Managed Code
Figure 4-1 Code formats and run-time environments supported by Visual COBOL
The class is the most fundamental type. It defines behavior (methods) and data (fields), and when
you construct (instantiate) an instance of a class, you have an object. Each time you instantiate an
object, the JVM or CLR allocates memory for the fields in the object, and those fields are accessible
to the instance methods of the object. A value type is similar to a class, except that memory for a
value type is allocated differently, and it has a different copy semantic (value types are explained in
more detail in the Value Types and Reference Types section in Chapter 13).
Delegates, enumerations, and attributes are specialized types of classes designed for particular
purposes. For example, a delegate is the basis of Visual COBOLs callback mechanism and can be
thought of as a type-safe function or procedure pointer.
Interfaces are different from other types; you cant explicitly create an instance of an interfaceyou
would always create an instance of a type that implemented that interface. An interface is a contract
specifying a list of methods and properties. A class can specify that it implements a particular in-
terface, in which case it must implement all the methods and properties specified by the interface.
In Chapter 8, we work through defining and implementing an interface.
The different types and the syntax for defining them are explained more formally in Chapter 13.
However, as we work through the examples in this part of the book, you should get a clear idea of
the different types and how to work with them.Throughout this book, we often refer to types when
discussing concepts, even when the example is using classes.
Inheritance
Visual COBOL supports single inheritance of classes and allows multiple implementations of inter-
faces. This is the same model used by Java and .NET languages like C#. Single inheritance means
that a class can have one single, immediate parent. However, that parent can also have a parent, and
the inheritance chain can be any length. Ultimately, all classes inherit from the root class, known as
object. A subclass inherits all of the methods and data from its parent, although depending on the
access modifiers on fields and methods, it might not have direct access to all of them.
When a class implements an interface, it means that it provides an implementation for all the
methods and properties of the interface.
By default, all methods in Visual COBOL are assumed to be virtual unless they are explicitly
marked with the final modifier. A virtual method can be overridden in a subclass. This is the same
way Java implements methods; in C#, methods have to be explicitly marked as virtual before they
can be overridden.
Listing 4-5 shows classes A, B, and C. C inherits from B and B inherits from A. Class C overrides
method P() from Class A. The code at the end of the listing creates one object of each type and
invokes method P() or Q(). The diagram in Figure 4-2 shows the inheritance hierarchy down the
left-hand side using Unified Modeling Language (UML). Down the right-hand side of the diagram
are the three objects, one of each type, which are created. When method P() is invoked on objects
of class A or B, the method inherited from class A is executed, but when invoked on an object of
type C, the method from class C is executed. Dont worry about the syntax or class structure at the
moment; the only intention of this code is to illustrate the inheritance model.
JVM and the CLR for COBOL Programmers 41
class-id A public.
01 field1 binary-long.
method-id P public.
end method.
end class.
method-id Q.
end method.
end class.
method-id P override.
end method.
invoke a1::P()
invoke b1::P()
invoke c1::Q()
invoke c1::P()
end method.
end class.
42
object-id 1
a1 type A
A b1 type B field 1
field 1 c1 type C
method P( )
method P( )
invoke a1::P()
B object-id 2
field 2 invoke b1::P() field 1
method Q( ) field 2
method P( )
method Q( )
invoke c1::Q()
invoke c1::P()
C
object-id 3
method P( ) override
field 1
field 2
method P( ) override
method Q( )
Type Safety
Java, C#, and Visual COBOL are all type-safe languages, and most type checking is performed
at compile time. Type safety means that if a method expects an argument of a particular type, then
the argument supplied must be compatible with that type. A compatible argument would be one
of the following:
One of the specified type
One that inherits from the specified type
One that has an implicit conversion to the type (see the section entitled Type Compat-
ibility in Chapter 6)
One that implements the specified type (when the specified type is an interface)
Compile-time type checking catches a lot of programming errors that would otherwise surface
at run time. The downside of compile-time type checking is that it can mean writing more code,
particularly interfaces, in order to write code that is both type safe for the compiler and that avoids
circular dependencies between packages and libraries.
It is possible to cast objects of one type into objects of another type; some casts can be made
implicitly (where the compiler can see that this is an allowable operation at compile time), and some
casts must be made explicitly and further type checking is carried out at run time to see whether
JVM and the CLR for COBOL Programmers 43
or not the operation is allowed (you can see a simple example of this in the section entitled Type
Compatibility in Chapter 6).
Memory Management
Memory management is one of the most important features the managed platform run times provide.
Earlier on, we stated that when you create an object, memory is allocated for all the fields (data) in
the object. You can have many references that point to the same object. When you declare a data
item like anObject below, the data item only stores a reference to the object.
01 anObject object.
Initially, the value of anObject is null. If you tried to invoke a method on it, you would get an
error. It isnt until you instantiate the object that the memory is allocated and that anObject contains
a valid reference:
The process of clearing unused objects out of memory is known as garbage collection. The JVM
and CLR garbage collectors have both been the focus of a lot of effort to optimize their performance
over the years, and we will not cover their exact workings in this book; there is a lot of information
published on the web if you are interested.
The main things you need to know now are that:
Unused objects get deleted eventually, but you have no guarantees as to when. The garbage
collector runs in its own thread and uses a number of metrics to decide when to carry out
different operations.
If an application continually creates new objects without releasing the references to unused
ones, the application is leaking memory and may eventually crash with an out of memory error.
We introduced a new piece of syntax abovedeclare. You can use declare inside
a method to declare a new local variable and, optionally, set it to a value. If you
dont set it to a value, you must specify the type.
44
Summary
Visual COBOL is an object-oriented language that compiles to managed code. Managed code is
code that runs on either the JVM or .NET platforms. It is possible to write programs in Visual COBOL
that can be run on either of these platforms just by recompiling. Visual COBOL can compile existing
procedural COBOL or object-oriented code that exploits its new features.
CHAPTER 5
Writing a Class
In this chapter, we start writing some real Visual COBOL code, with a simple implementation of a
class for storing and working with dates. The SimpleDate class used in this chapter does not offer
all the facilities of the IDate interface used in the Rental Agency example, but it provides a good
introduction to creating your own classes and types in Visual COBOL. The IDate code is shown
later in this book as an example of cross-platform programming.
In this chapter, youll learn about:
Classes, fields, and methods
Creating projects and classes from Visual Studio or Eclipse
Properties
Exceptions
Namespaces
45
46
class-id. MicroFocus.COBOL.Book.Examples.SimpleDate.
01 year binary-long.
01 month binary-long.
01 #day binary-long.
end method.
end class.
This class also has a constructor (a method with the name New or new), which enables you to
set the values of the three fields when you instantiate it. However, it doesnt yet attempt to validate
your input, and there is also no way to get access to the fields after you have created an object.
The fields are named year, month, and #day. COBOL has a large number of reserved words
compared with most languages, and day is one of them. But you can use a reserved word as an
identifier if you prefix it with #. The # is not actually part of the identifier name, but it is needed
wherever there could be confusion between the identifier and the reserved word.
In the constructor, the arguments passed in are also named year, month, and #day. The fields
are distinguished from the arguments by being referenced with the self expression; self always
refers to the current instance and can be used for accessing other members in the class or wherever
an object needs to pass itself to another method. You can generally refer to a field just by using its
identifier, but in this particular case, self is needed to disambiguate between the local variables
defined in the method header as arguments and the fields defined as part of the class.
Instantiating objects
When you instantiate an object, memory is allocated for all the nonstatic fields defined in
the class, and you are given a reference to the object so that you can access its nonpri-
vate members (fields, methods, properties). You can create as many instances of a class
as you need to. If you debugged through the first Rental Agency example in Chapter 3,
youve already seen this in action.
Weve added a validation method to check the input passed to the constructor. If the year,
month, and day dont make a valid date, the validation routine raises an exception. An excep-
tion interrupts the usual flow of the program logic, forcing the calling code either to handle
the error or stop. There is a formal explanation of exception handling in the Try Catch
section in Chapter 15.
class-id. MicroFocus.COBOL.Book.Examples.SimpleDate.
working-storage section.
01 Year binary-long property with no set.
01 Month type Month property with no set.
01 #Day binary-long property with no set.
end-if
end method.
end-if
set result to false
end method.
enum-id Month.
*> Month constants.
78 Jan value 1.
78 Feb value 2.
78 Mar value 3.
78 Apr value 4.
78 May value 5.
78 Jun value 6.
78 Jul value 7.
78 Aug value 8.
78 Sep value 9.
78 Oct value 10.
78 Nov value 11.
78 Dec value 12.
end enum.
end class.
Lets go through the code for the SimpleDate class in a little more detail. There are formal syntax
definitions and explanations in the Reference part of the book later on (Chapters 12 to 15), but some
quick descriptions of whats here will be helpful. The class starts with a class-id header and ends
with end class. The Month enumeration is contained between the enum-id and end enum headers.
If you nest one type inside another like this, the nested types must be the last thing defined in the
class (after the fields and methods).
Going back to the top of the listing, we have the three fields defined as before, but as explained
earlier, they are now exposed as read-only properties. Inside the constructor, we have added an if
statement. The if statement must be followed by an expression that evaluates to a condition-value
(either true or false). The expression IsValid() here invokes the IsValid() method, which checks
the arguments for a date and returns true for a valid date and false otherwise. The constructor
then assigns the method parameters ( year, month , #day) to the fieldsthe compiler knows we
want to access the fields because we precede them with a reference to self. The self keyword is
a reference that always means the current objectthe one whose method is being called now. You
dont often need an explicit reference to self as the compiler can nearly always infer it, but on this
occasion, we need to use it so the compiler can disambiguate between the arguments defined in
the method header and the fields.
The arguments to a method are defined in the method-id header inside brackets. The name of
the argument comes first, followed by the keyword as and then the argument type. For COBOLs
predefined data types like binary-long (see Chapter 14 for the full list), you just need to give the
type name. For types that are not part of the COBOL Language specification, you must precede
the typename with the word type.
The optional returning clause is where you specify a data name and type for a value you want
to return from the method (condition-value is equivalent to bool in C# or boolean in Java). In
A Simple Date Class 53
COBOL, the returning value is a named local variable you can refer to inside the method. The
value returned to the caller is the value of this variable (named result in our IsValid() method) at
the time control returns from the method to the caller.
Both the argument list and returning phrase are optional. If there is no returning phrase, the
method is the equivalent of a void method in Java or C#. If there are no arguments, the brackets
following method-id are optional; whether you include or omit them is determined by your own style
guidelines. Youll also notice the separators between the arguments are just spaces; you can use
commas, but the compiler doesnt require them. You can split the method-id header across several
lines if you want toyou can put a line break anywhere you could put a space, but the period (.) at
the end of the header is mandatory.
COBOL does not require punctuation to mark the end of a statement, but periods are used to
mark the end of every header. Statements can also be split across lines as needed. The compiler
generally knows when one statement ends when it sees the next verb, header, or if statement.
For those cases where there could be ambiguity, most statements have an end- form that you
can use as the end delimeter. You can see this form used with all the conditionals and tests in the
IsValid() methodevery if has an end-if, and the evaluate statement (similar to switch in C#
or Java, but more flexible) has an end-evaluate.
The IsValid() method has very simple logicit examines the month to determine the number
of days and sets the result to false if the day passed in falls outside the range. For the month of
February, it has to call another method, IsLeapYear(), to know whether there are 29 or 28 days.
Back to the constructor (the New method): It invokes IsValid(), and if IsValid() returns false,
it raises an exception. This is exactly the same as throwing an exception in C# or Java. The flow of
execution is interrupted and returned to the point at which the method throwing the exception was
called. If the calling code is wrapped inside a try... catch block, the exception can be caught and
handled; otherwise, the exception now passes back down the stack to the previous caller. Until a
piece of code catches the exception and deals with it, the stack will continue to unwind until finally
the program terminates. Exceptions provide a very flexible and powerful mechanism for dealing with
errors and are a standard part of most modern languages. Well encounter them again later in our
examples, and the syntax is documented in detail in Chapter 15 in the Try and Catch section. If
you are completely unfamiliar with exception handling, you could look for some articles on exception
handling in C# or Java on the webthe principles are the same in Visual COBOL, C#, and Java.
54
Checked Exceptions
In Java, most exceptions are checked (apart from those descended from java.lang.
RuntimeException); if a method can throw an exception, it is part of the method definition
(the word throws followed by a list of the different exceptions the method might throw).
If you invoke such a method from Java, the compiler will require that either the calling
method adds the exception to its own list of exceptions or handles it with a catch block.
Visual COBOL does not check exceptions whether compiling for .NET or JVM.
Checked exceptions are a controversial feature; although they can promote safer
programming by forcing programmers to think about exceptions raised in code they are
calling, they can also lead to a number of programming anti-patterns in practice. Some
Java frameworks like Hibernate and Spring have effectively opted out of checked excep-
tions by using java.lang.RuntimeException rather than java.lang.Exception as the root
parent of all their exception types.
Listing 5-3 shows a short program that attempts to construct two instances of the SimpleDate
class. We have declared two local variables inside the method using the declare statement. This
statement has two formats: one that initializes the variable to the value of an expression, and the
one used here, which does not. If the declare statement does not set a value, you must specify
the type of variable you are declaring using the as clause. You can declare local variables at any
point in the method by using the declare statement. Variables are always scoped to the block of
code the declare statement appears inif you declare a variable inside a perform loop or if block,
the variable is only available inside that block.
A set statement follows the declarations. On the right-hand side of the method (after the to
keyword), we have an expression that starts with the new operator. This is followed by the name of the
type you want to instantiate. Our only constructor for SimpleDate requires three parameters, which
are supplied inside brackets after the type name. The new operator always invokes a constructor
and instantiates a new object:
Youll notice that the Month enumeration is named as SimpleDate+Month . Thats because it
is nested inside our SimpleDate class, so if you want to access the Month type from outside the
SimpleDate class, SimpleDate is part of the name. The + is the separator that indicates that one
type is nested inside the other.
We cant do a lot with our SimpleDate instance, but we can read back its properties, so we
have two display statements that show the Month property. The Month property is our Month enum,
and you can use an enumeration in any expression that expects a string, in which case the value
provided is the name of the constant as a string. However, the values in an enumeration are always
actually numeric (by default, binary-long but you can specify a different numeric type when you
define the enumeration), and you can get back the actual type by casting the enumeration back to
a numeric type with the as operator. Enumerations are covered in detail later (see Chapter 13, the
Enumerations section).
We also try to construct a date with some invalid values. There is no February 29, 1999, so when
we execute this code, an exception is raised. We have wrapped our code in a trycatch block, so
execution passes directly into the catch block, where we simply display the exception. This shows
A Simple Date Class 55
the message used when the exception was constructed. The use of exceptions and try catch is
documented in more detail in Chapter 15, in the Try and Catch section.
class-id MicroFocus.COBOL.Book.Examples.MainClass.
try
set dateX to new SimpleDate(1999 type SimpleDate+Month::Feb 29)
catch e as type Exception
display e
end-try
end method.
end class.
Listing 5-5 shows the new methods being used. The result data item is a condition-value and
is set to the value of a comparison operation (<), which will return either true or false. The display
statement will print result as either the word true or false.
Listing 5-5 Main method using the DisplayFormat() and Compare() methods
end method.
Namespaces 57
This is as far as we will take our SimpleDate class. Although it is not very complex, it already
provides useful functionality that makes it far more powerful than a simple group item for managing
dates would be. For example, any code using SimpleDate objects can assume that all objects contain
valid dates; code to do with the correctness of dates is encapsulated inside the SimpleDate classes.
SimpleDate is very limited when compared with the DateTime class provided by the .NET Frame-
work, or the libraries available for DateTime handling in Java. The native .NET and Java libraries for
DateTime handling enable arithmetic between dates and automatically format results appropriately
for an applications current locale. In Chapter 8, we will develop the IDate type, which exposes some
of this functionality, and which is one of the support classes used by the Rental Agency example.
Namespaces
Visual COBOL has the concept of namespaces, which is also found in Java and .NET. A namespace
further qualifies the name of a type to distinguish it from other types that have the same name. For
instance, you might use a third-party library that had an Address class for managing customer ad-
dresses. You might also use an email library with an Address class for managing email addresses.
But Address is only the short name for the class; the full name includes its namespace and should
always be unambiguous. When you are creating namespaces, you should include your company or
organization name to ensure that the namespace is unique.
If you look back at the first listing in this chapter (see Listing 5-1), the class-id header looks like this:
class-id. MicroFocus.COBOL.Book.Examples.SimpleDate.
Importing Namespaces
You can import a namespace into Visual COBOL with the ilusing compiler directive. You can apply
ilusing either as part of the directives associated with the entire project compilation, or you can
use $set ilusing inside a source file.
58
If you set ilusing directives inside a source file, they must appear before any code in the file, and
the directives only apply to that file. If you set the directives at the project level, they apply to all the
source code in the project. Applying ilusing directives on individual files gives better control over
the namespaces available to each of the types defined in your project, and can be useful if you are
working with libraries that have similar type names in different namespaces. Applying namespaces
at the project level is simpler, but increases the risk of type name clashes between different classes,
which is more likely in complex projects with many external dependencies. The examples in this book
have few dependencies, and we have opted for the simple approach of importing namespaces at
the project level.
To import a namespace with ilusing, put it inside parentheses directly after the ilusing directive,
for example:
ilusing(MicroFocus.COBOL.Runtime)
Use one ilusing directive for each namespace you want to import. The next two sections show
you how to set namespaces in Visual Studio or Eclipse.
You can use either convention for naming your classes; for the examples in this book, we have
followed the .NET convention, and we will refer to namespaces from now on rather than package
names (apart from when we are specifically discussing Java or the JVM).
Summary
In this chapter, we covered the basics of writing a class in Visual COBOL from scratch, and showed
you how a class encapsulates both data and behavior. We also looked at namespaces. In the next
chapter we will use classes to implement a set of business rules, as well as demonstrate how
inheritance enables you abstract common behavior into a single place.
CHAPTER 6
Inheritance, Exceptions,
and Collections
In this chapter, we take a deeper look at the Rental Agency example introduced in Chapter 3. Well
be explaining how we implement the business rules for our example using the object-oriented fea-
tures of Visual COBOL. In this chapter, youll learn about:
Inheritance
Exception handling
Collections
Person, Landlord, and Tenant are three classes that deal with the same thingpeople. The next
section shows you how to use inheritance to minimize the amount of code you need to write. First,
download the example as explained in Chapter 2 and either open the solution file in Visual Studio,
61
62
or import the project files into Eclipse depending on the Visual COBOL product you are using (the
basics of doing this are covered in Chapter 3).
This chapter concentrates on just the core Rental Agency classes themselves and how we
implement the main business logic for the application. The actual rules for the business logic are in
the Rental Agency Design Assumptions section at the end of this chapter. As you work through the
example in this chapter, you will see references to these rules. Most readers of this book will prob-
ably want to get straight into the code examples in this chapter, but if you want to see the design
brief first, check out the Rental Agency Design Assumptions section now.
Subsequent chapters look at storing the data persistently and providing a UI that enables a real
user to interact with the application.
We have made a design decision that we only want to use subclasses of Person in our Rental
Agency (so far, we have identified Landlord and Tenant), and marking the class as abstract pre-
vents Person from being used in a way we hadnt intended. A class doesnt have to be abstract in
order to be inheritable; you can inherit from any class that isnt marked as final (see Chapter 13,
the Abstract and Final Classes section for more information). Abstract classes have other uses,
like providing partial implementations of interfaces, a topic covered in Chapter 8.
The name field is a string, but the address field is an object of type Address, and the email
field is an object of type Email. The Email and Address classes are part of the ExamplesSupport
project introduced in Chapter 3. The Email class includes code to validate that an email address
is in the correct format when the object is constructed. The Address class defines all the fields
needed to work with an address, as well as providing some useful behavior that enables us to use
it as the key in a dictionary (dictionaries are covered later in this chapter).
Using Inheritance: People and Tenants 63
end class.
Because we have made the decision that the Name and Email cant be changed in the Person
class, we must set them when the object is created and so we need to provide a constructor that
takes two arguments. The complete Person listing is shown in Listing 6-2.
end class.
Inheritance
Inheritance enables you to define a new class that inherits the characteristics of another class.
When one class inherits from another, it contains the data and methods of its parent. Although a
class can only inherit directly from one parent, the inheritance chain can be any lengthclass D can
inherit from C, which inherits from B, and so on. Class D in this example would have the data and
methods from C and B as well as its own. Class D is a subclass of C, and class C is the superclass
of class D (the terms parent and superclass mean the same thing).
Listing 6-3 shows the outline of the Tenant class. The inherits phrase in the class header
specifies that this class inherits from Person.
class-id MicroFocus.COBOL.Examples.Lettings.Tenant
inherits type Person public.
01 currentLease type Lease property with no set as Lease .
end class.
64
The Tenant class also reimplements the constructor (the New() method). Unlike other class
members, constructors are not inheritedwe will explain why in the following section, but it means
the Tenant class must have its own constructor. There isnt any extra data we want to pass in when
constructing a Tenant, so this constructor takes the same two arguments as the Person class.
Because the subclass inherits all the data from the superclass, we have to call the superclass
constructor before we can write our own construction code. The following statement invokes the
superclass constructor:
The super keyword is an expression that always refers to the immediate parent class.
class-id ConsoleApplication1.Parent.
method-id New().
display parent constructor
end method.
end class.
end class.
Using Inheritance: People and Tenants 65
The only code they contain is display statements inside parameterless constructors (a constructor
that does not require any arguments is known as a default constructor). The Main method creates
an instance of Child. If you run this program, youll see this output:
parent constructor
child constructor
You can see that the Parent constructor got invoked automatically when we constructed the
Child object, even though we didnt add invoke super::New() to the Child constructor. Whenever
we instantiate an object, the constructors have to be called from the topmost parent down to build
up the complete object. The compiler treats default constructors as a special case; no data has
been passed to the constructor so nothing needs to be done other than create the object and the
reference. If the constructors are parameterless, the compiler inserts the code needed to chain the
constructorsso although we did nothing to invoke the Parent constructor, it runs anyway.
If you create a class with no constructors, the compiler will automatically insert a default construc-
tor for youso if you deleted the constructors from this example, the program would still run the same,
although nothing would be displayed on the console. However, once you add a constructor to the
class, the compiler does not generate a default constructor; if you only provide a constructor with
arguments, it is very likely that you only want an object created with the data from those arguments.
Figure 6-1 shows what happens when we construct a Tenant instance. The Person constructor
must be executed first, and the memory allocated for the fields must be defined in Person. Then,
the constructor code for Tenant is executed, and memory is allocated for the currrentLease field.
The only constructor for Person takes two arguments so the compiler wont automatically chain to
itthe compiler doesnt know what values we want passed in. This is why the first statement in the
subclass constructor has to be:
If you delete this statement, you will get the compiler error Inherited class does not have a
default constructor.
A Tenant Instance
Multiple constructors
A class can have more than one constructor, as long as each constructor has a differ-
ent method signature (the number and type of arguments requiredsee Chapter 13, the
Method Signature and Returning Value section for a more formal definition). Every con-
structor in a subclass must start with a call either to one of the superclass constructors
or to another constructor in the same class. Every constructor must ultimately call a con-
structor in the superclass, unless the superclass has a default constructor.
Type Compatibility
Type inheritance leads us to a brief discussion about type compatibility. When one class inherits
from another, instances of that type are compatible with instances of the superclassthat is, you
can use them anywhere you would use an instance of the superclass. The example in Listing 6-5
should help clarify this.
class-id Parent.
method-id One.
display Parent one
end method.
end class.
method-id Two.
display Child two
end method.
end class.
We have two classes, Parent and Child (Child inherits from Parent). Both implement method
One(), and Child also implements method Two(). Note that in Child, One() is marked as override
you must tell the compiler that you are explicitly overriding a method from the superclass.
When you run this program, the code in the Main method declares two variables of type Parent
and Child, respectively, and creates an instance of Child. You can store the Child object in the
variable of type Parent because inheritance makes Child compatible with Parent (this is an implicit
conversion). When you invoke One() on aParent, the method executed is the one in the Child
class. Although the data item is of type Parent, the object it actually points to is a Child object
and it still behaves like one.
We can also set aChild to the value in aParent, but at compile time, you cant know whether
the object in aParent is actually an object compatible with Child. Child is compatible with Parent
because it has all the characteristics of Parent, but Parent is not compatible with Child because
Child has characteristics not in Parent. To stop the compiler from flagging this as an error, we
have to use the as operatorthis tells the compiler that we are making an explicit conversion to
the Child type.
When you run the program, this conversion succeeds because aParent has a reference to
something that is actually a Child instance, and we can execute method Two().
Lastly, we create an instance of Parent, and use the as operator to explicitly convert it to a Child.
This compiles successfully, but when the program runs, this last statement throws an exception
because aParent actually contains a Parent instance, which is not compatible with Child. When
you carry out an explicit conversion, the system checks at run time to see whether the conversion
will succeed and throws an exception if it wont.
You can carry out your own check using the instance of operator as follows:
If you dont specify a visibility modifier explicitly, the compiler marks all fields as private, and all
methods and properties as public. As the definitions imply, although instances of the Tenant class
contain fields for email, name, and address, code in the Tenant class cannot access those fields
directly. Only code in the methods of Person can access those fields. All those fields are exposed
as properties (public by default), so Tenant can read the values for the Email and Name read-only
properties, but not change them, and it can read and write the value of the Address property.
class-id MicroFocus.COBOL.Examples.Lettings.Tenant
inherits type Person public.
01 currentLease type Lease property with no set as Lease .
end class.
class-id MicroFocus.COBOL.Examples.Lettings.Landlord
inherits type Person public.
end class.
Lists
In Listing 6-7, the Landlord constructor contains the statement:
create propertyList
A list is just another kind of object, and it has to be instantiated before it can be used. Visual
COBOL has syntax for working with lists, and the create verb is how you actually instantiate a list.
The Landlord class has two methods for managing RentalProperty objects. AddProperty()
enables you to add a new RentalProperty to the landlords portfolio. It uses the write verb to
add the new property to the end of the list. Lists are not fixed size; they can grow to accommodate
more elements as you add them. Elements are stored in a list in the same order that they are added.
Method GetProperties() returns an array of the RentalProperty objects owned by the landlord.
An array is fixed size once created, and behaves similarly to a table in COBOL. The method creates
an array the correct size (the size of operator gives the number of elements inside propertyList),
and then uses perform through varying to loop through the list and write the elements into
the array. Arrays are fully documented in Chapter 14.
The perform... varying through statement can be used to loop through lists, arrays, and
dictionaries. It is fully documented in Chapter 15, but you will see it frequently in the examples, too.
The loop variable here (nextProperty) is actually declared inline in the statement, but you can also
use a variable that has been declared elsewhere.
You might wonder why we dont make propertyList a read-only property instead of copying all
the data out of it into an array. The reason is that once we give out the reference to propertyList,
there is nothing to stop the code that gets it from adding or removing elements from the list. Mak-
ing the property read-only means that no one else can change the value of propertyListthat is,
make it refer to a different listbut it doesnt stop them from invoking any methods on it. By copying
the data into a separate array and giving that out instead, you preserve the integrity of the data in
a Landlord object.
The Lease Class 71
invoke tenant::StartLease(self)
set self::Id to type MicroFocus.COBOL.Examples.Support.Guid::GetNewGuid()
end method.
All but two of the fields here represent the things in Rule 8the exceptions are cancelled and Id.
The Id field is there to provide a unique identifier for every lease. All the people in the system
can be identified by their email addresses, and every RentalProperty has a unique postal address.
But there is no obvious single identifier for a lease because it is active over a finite period of time,
and outside that period, all the entities represented within it could be used for other leases. The Id
is a unique string (a GUID) assigned to each lease that gives us a way of retrieving and managing
them. The GUID is generated by one of the classes in the ExamplesSupport project.
The cancelled field is there to help support Use Case 7cancel a lease. You want to keep a
record of a lease, even after it no longer applies, so we keep the lease data but mark the lease as
cancelled. Listing 6-9 shows the Cancel(), IsCancelled(), and IsActive() methods associ-
ated with this logic. IsCancelled() and IsActive() are not symmetricala lease might never have
been cancelled, but is not active because we have passed the end date. The Cancel() method
also sends the CancelLease() method to the associated tenant, so that the data stays consistent.
There are two other methods shown in the listingExtendLease() and GetDisplayValue().
The ExtendLease() method supports Use Case 6Extend a lease. Youll notice that all the date
arithmetic and comparisons used in the Lease class are handled for us by IDate objects. The
ExtendLease() method both changes the value of the expiryDate held in the lease and returns
the new value to the caller.
Finally, GetDisplayValue() is a convenience method that provides a short string summarizing the
data in a lease object. It uses the string concatenation operator & to assemble the strings together.
method-id Cancel.
set cancelled to true
invoke #property::CancelLease()
invoke tenant::CancelLease()
end method.
*>
*> Check that the lease term is not over and the lease has not been cancelled.
*>
The RentalProperty Class 73
end class.
All of the fields are exposed as properties, but the Address and Lease properties are read-only.
The Address is read-only because it is set when the RentalProperty is instantiated and may not
be changed afterward, and the Lease is read-only because it should be changed only by the
methods that implement business logic around leasesthis is to help protect against ending up
with inconsistent data.
The methods for handling leases are shown in Listing 6-11.
74
By now, the patterns in the code should be starting to seem familiar. The LeaseProperty() and
CancelLease() methods enable you to change the rental status of the property. The IsAvailable()
method enables you to check whether a property is available for rent. The LeaseProperty() and
CancelLease() methods are both marked with the internal modifier.
This keeps all the main logic for changing the status of leases within the Lease class itself, which
invokes the appropriate methods on the Tenant and RentalProperty objects it holds whenever the
status needs to change. By restricting access to these methods from outside the Rental Agency
project, we reduce the possibility of other code outside the project (in the UI layer, for example)
from changing these properties into an inconsistent state. The final method in the RentalProperty
class is a GetDisplayValue() method like the one in the Lease class. They are similar to methods
we have already looked at in this chapter, so we wont describe them any further. Listing 6-12 shows
the completed class.
method-id CancelLease().
set currentLease to null
end method.
end class.
Dictionaries
We looked at the list type earlier in this chapter, so we will conclude with the other collection type
built in to the Visual COBOL language, the dictionary. A dictionary is a collection of key-value
pairs. Each time you add an element to a dictionary, you have to provide a unique key and the value
you want stored. You can retrieve the value later on by providing the key again.
76
You can also enumerate through all the keys, values, or key-value pairs in a dictionary using the
perform varying through statement. However, unlike a list where the order of the elements is
the same as the order they were added, a dictionary has no guaranteed ordering.
Full refererence documentation on the dictionary type is in Chapter 14, but we will show you a
simple example here, using a dictionary to store RentalProperty objects keyed by Address.
Although the most common type of key is probably the string, you can use other kinds of objects
for keys; however, they work best if they follow some simple rules. These rules are explained in
Chapter 14, in the Keys section, but to understand the example here, all you need to know is that:
Two addresses are considered equal if the first line and the postal code match (in most
countries if these two items are the same, the address is the same).
The matching is not case sensitive.
This isnt a perfect implementationtwo addresses that are the same might still fail to match if one
of them was typed in with extra spaces between words, but it works well enough for our example.
We arent going to look at the comparison code in Address in this chapter.
Listing 6-13 is a short program that stores some RentalProperty objects in a dictionary, and
then retrieves and deletes an entry. It finally uses perform varying through to retrieve all the
keys. You can also use this statement to retrieve all the values, or all the keys and values together.
The statement is fully documented in the Perform Varying (Collections) section in Chapter 15.
All the data for the program is stored in arrays, which are initialized using the table of clause.
The compiler is able to infer that Address is the type for the table of clause when setting ad-
dresses because the entries in the table do not match any other type. But there is more ambiguity
with numeric literals, so we explicitly state that the table of clause here is intended to contain
binary-long items.
After the statement in the Main() method that deletes the entry we have just retrieved, we at-
tempt to retrieve it again with the same key. Weve added the optional invalid key and valid key
clauses to the read statementin this case, the code in invalid key gets executed because the
item cannot be found. Without the invalid key phrase, trying to read against a key that does not
exist throws an exception.
end class.
Our business application is a model of the process of renting out properties to tenants, but be-
cause our primary goal is to use it as a vehicle for explaining Visual COBOL, rather than using it to
run a real business, we are going to keep it very simple indeedyou will be able to think of plenty of
scenarios that a real application would need to handle that this one wont. This section explains the
data we are modeling in the application, the simplifying assumptions, and the main use cases covered.
The model for the application follows these rules:
1. People are represented by their name and email addressyou must have these two items
to create a person.
2. People have a single postal address, which might not be set to a value.
3. A tenant can only rent one property at a time.
4. A property can only be rented to a single tenant at a time.
5. A landlord can own one or more properties for rental.
6. A property must have a postal address, which cannot be changed.
7. A property has a monthly rental price.
8. A lease represents an agreement by a tenant to rent a property between two inclusive
dates.
9. A property can only be involved in one lease at a time.
Some of the assumptions here would not work for a real applicationfor example, it isnt uncom-
mon in the real world to have multiple agreements with different tenants to lease a shared house
or apartment.
These are the main use cases the RentalsAgency covers:
1. Add a landlord to the system.
2. Add a tenant to the system.
3. Add a property to the system and register it against a landlord.
4. List the available properties.
5. Lease a property to a tenant.
6. Extend a lease.
7. Cancel a lease.
We exercised all of these use cases back in Chapter 3 when we introduced the Leases program
that made use of the Rental Agency classes. The Leases program is included with all the sample
code for this chapter so that you can step through all the code discussed in this chapter.
Summary
In this chapter, weve looked in more detail at the business logic for the RentalAgency example and
how it is implemented. Weve also looked at how class inheritance enables you to reuse code and
used the list and dictionary types provided by Visual COBOL. The next two chapters step away from
the Rental Agency example so we can cover interoperating with procedural COBOL, and how to
write programs that will work on either the .NET or JVM platform. Well return to the RentalAgency
example again in Chapter 9.
CHAPTER 7
Interoperating with
Procedural COBOL
In this chapter, we look at how Visual COBOL works with programs written in procedural code
(those not using object-oriented structures like classes). The information in this chapter should be
helpful to anyone modernizing an older application, or wanting to access such an application from
Java or .NET.
In this chapter, youll learn about:
COBOL language for Java and .NET programmers, including group items, copybooks,
andindexed files
How Visual COBOL turns procedural COBOL into classes
Smart Linkage for interoperation between Java or C# and procedural COBOL
Calling programs compiled as native code from managed Visual COBOL
79
80
COBOL can move data easily between COBOLs older native types and the newer ones that fit with
the modern type systems, which greatly simplifies any code that has to interoperate between the two.
A data definition in COBOL is generally in the following form:
This example has no usage clause and so has the default of display; this means that the data
is represented exactly by the layout described in the picture string. The usage clause enables you
to pick different ways of representing numeric data. For example, to represent a 10-digit decimal
number with two decimal places of precision in packed-decimal format:
The usage clause of comp-3 specifies that the data is stored as binary-coded decimal (each
decimal digit is represented by 4-bits, 2 decimal digits to each byte). The word usage is optional.
COBOL Records
In the previous section, we looked at some simple data items declared with a level number of 01.
However, COBOL enables the programmer to define complex record layouts, also known as group
items. Group items are explained in more detail in the section COBOL Group Items in Chapter 14.
Listing 7-1 shows a simple group item. A COBOL program can address this at any level; it could refer
to cust-record, cust-address, or cust-state. A group item definition specifies exactly the layout
of the record at the byte level, which means it also describes how the record can be stored on disc.
The exact layout of a group item can be affected by the use of compiler direc-
tives for alignment. These specify that items be laid out on boundaries that suit
the word length of the computer running the program. See the Micro Focus
documentation for more information.
01 cust-record.
03 cust-name pic x(80).
03 cust-address.
Copybooks
In the previous section, we looked at the definition of a record or group item in COBOL. Obviously,
once a programmer has defined a complex record type, he will want to reuse it. However, most
COBOL dialects do not have a typedef feature like the one in C that would enable you to do this
directly (the exception is the Micro Focus dialect). However, COBOL copybooks enable programmers
to reuse definitions.
A copybook (also known as a copy file) can contain any valid COBOL source code, and you
can insert it directly into another source file using the copy statement; the compiler treats the code
in the copybook as part of the source it is compiling. A copybook is similar to a header file in C or
C++. The simplest form of copy statement simply inserts a copybook exactly as it is directly into
a source file. But the other form of copy, known as copy...replacing, enables you to make text
substitutions at the time of replacement.
Listing 7-2 shows a copybook defining a group item, with every line starting with (prefix)-.
Listing 7-3 shows a short program that uses copy...replacing to include the copybook twice. This
has the effect of creating two group items, item-a-cust-record and item-b-cust-record. This is
the technique most existing COBOL programs use to create and reuse data definitions.
Bear in mind that procedural COBOL is not a type-safe language. The compiler will not stop
you from moving a cust-record to a cust-account, for example. Group items are represented
internally as a pointer to a contiguous area of memory and its length.
01 (prefix)-cust-record.
03 (prefix)-cust-name pic x(80).
03 (prefix)-cust-address.
05 (prefix)-cust-address-1 pic x(80).
05 (prefix)-cust-address-2 pic x(80).
05 (prefix)-cust-town pic x(80).
05 (prefix)-cust-state pic xx.
03 (prefix)-age pic 9(4) comp-X.
program-id. Customer1.
working-storage section.
copy cust-record.cpy replacing ==(prefix)== by item-a.
procedure division.
move Fred Smith to item-a-cust-name
move item-a-cust-record to item-b-cust-record
stop run.
82
Linkage Section
Although some COBOL applications are quite monolithic with a lot of global data, COBOL
applications can also be built out of smaller programs, which can be called with lists of parameters,
like functions. Many dialects also include the entry statement, which enables you to define additional
callable entry points inside a program.
The arguments taken by a program or entry point are named in the using phrase, but are defined
in the linkage section. Data items defined in the linkage section are not allocated any memory;
by default, all parameters are passed by reference. This means that the items in the linkage section
are set to the address of the parameters being passed by the calling program. COBOL can also pass
parameters by value, which means the data is copied from the calling program to the called program.
Listing 7-4 illustrates the use of the linkage section with a calling and called program, and
using the same copybook we showed earlier in Listing 7-2.
program-id. Customer1.
working-storage section.
copy cust-record.cpy replacing ==(prefix)== by item-a.
procedure division.
move Fred Smith to item-a-cust-name
call display-customer using item-a-cust-record
program-id. print-customer.
linkage section.
copy cust-record.cpy replacing ==(prefix)== by ==lnk==.
procedure division using lnk-cust-record.
display lnk-cust-name
goback.
COBOL Files
COBOL has its own data storage mechanisms, which predate relational databases and that are still
in use. COBOL supports several different file organizations, but we will only look at indexed files
in this chapter. A COBOL indexed file consists of one or more records. Part of the specification of
the file is to name one field as the primary key; you can also name other fields as secondary keys.
Defining a File
The record layout for a file is determined by a COBOL group item. The file-control paragraph
enables you to assign a data name to a file, specify the organization of the file, and also specify the
primary and secondary keys. COBOL supports several different file organizations, but they all fall
into the categories of either indexed or sequential; indexed files enable you to access records by
key and sequential files must be read in order. The file section includes the fd (file description),
which has the record layout. Listing 7-5 shows the file-control and file description for a file for storing
data for rental properties.
The data name for the file is property-file, which is shown in bold in the listing. It appears in
two places:
The file-control paragraph, which describes the organization and access mode for the file
The file description (fd ), where the group item immediately following describes the
recordlayout
The file-control paragraph also includes the assignment:
This links the data name property-file to an actual file on the file system. Visual COBOL
provides several ways you can map the external name inside a program to the actual file on the file sys-
tem; we will be using an environment variable to do this. The external propertyFile specification
means that if the program finds an environment variable with the name dd_propertyFile, it will
use the value of the environment variable as the path and name to the file. There are other ways of
configuring these mappingssee the Micro Focus File Handler documentation included with Visual
COBOL for more information.
The select statement in file-control also specifies:
The file is indexed.
File status information is found in data item file-status (this must be declared in the
programs working-storage or the compiler will give an error).
The access mode is dynamic (the file can be opened for reading or writing).
The primary key is filerec-address-key (this must be a field in the group item for the fd).
Listing 7-5 File-control and file description for the property file
program-id RentalPropertyFile.
file-control.
select property-file assign to external propertyFile
file status is file-status
84
organization is indexed
access mode is dynamic
record key is filerec-address-key
.
file section.
fd property-file.
01 filerec-Property.
03 filerec-landlord-email pic x(80).
03 filerec-lease-id pic x(32).
03 filerec-rent pic x(4) comp-5.
03 filerec-address.
05 filerec-address-key.
07 filerec-address-1 pic x(80).
07 filerec-postal-code pic x(20).
05 filerec-city pic x(80).
05 filerec-state pic x(40).
Once this information is configured in a program, there are some simple COBOL verbs that
enable you to open and close the file, and read, write, update, and delete records. The write verb
writes the contents of the filerec-property group item to the file, and the read verb reads a
record from the file into filerec-property.
File Status
Now that the file is defined, we can open and close it. But first, we need to define the file-status
data item, as shown in Listing 7-6. The select statement that declared our file in Listing 7-5 named
file-status as the data item for receiving status information.
working-storage section.
01 file-status pic 99.
After every file operation, file-status contains a 2-byte code indicating the result. Success
is 00 (two ASCII zero characters). The Micro Focus documentation has a full list of all the status
codes returned from operations.
You can open the file with any of the following statements, enabling you to read, write, or both,
respectively:
After every file operation, you should check the file status for success. Closing a file is even easier:
close property-file
Procedural Programs in the Managed World 85
In the Storing RentalProperty Information section, we are going to define a program for reading
and writing property records. It will have a single entry point so that it can be called by other programs
that want to access the file. After weve built up the program, well look at ways of calling it from
other languages. But first, we are going to examine what happens when we compile a procedural
program using Visual COBOL.
The other was functionally the same, but written as a class with a single static method, in the
same way that you might write the program in Java or C#. The class version is shown in Listing 4-2.
What were going to look at now is how Visual COBOL actually compiles the procedural version
as a class under the covers. It has to do this because the JVM and .NET platforms dont have a way
of building or running purely procedural code.
When Visual COBOL compiles procedural programs as managed code, it also has to generate
some extra code to make the managed version of the program behave as it did before, even though
it is now compiled as a class. So you can still run it on its own, and it will execute the code in the
procedure division. You can also call it from other COBOL programs using the call verb, and if
there are additional entry points and linkage items, those are all accessible, too.
In the next three sections, we first look at the disassembled versions of the class version of Hello
World, and then the disassembled version of the procedural code to see the differences. Both the
JVM and .NET platforms provide disassemblers that enable you to see what code has been gener-
ated when you compile a program.
For JVM, there is a command-line tool called javap, which you can run against a .class file to see
disassembled Java bytecode. You can also see all the metadata, so you can see where the methods
start and end, what arguments they take, and so on. Eclipse has javap built in, so if you double-click
on any .class file from the COBOL Explorer or Project Explorer panes, the disassembled file opens
in a (noneditable) document view.
The .NET platform has a tool called ildasm, which will be on the Windows path if you open a Visual
Studio Tools command prompt. It has a simple GUI, which enables you to open a .NET assembly
(.dll or .exe file), and it will show you all the types defined in the assembly in a tree view. You can
expand types to see methods, and if you double-click on anything, you can see the full disassembly.
tion for method names is that they should start with an uppercase letter. Visual COBOL enables
you to specify the method for a program, or will pick a static method for you, and generates a low-
ercase public static main() on JVM if needed to call the actual start method (see Chapter 12,
the Class Files section).
The static Main method at the bottom is the actual user code. You can double-click on any
method to see the IL code displayed in a separate window. Or, you can use the Dump action on
the File menu to write the entire IL code for the assembly out to a text file. The .custom lines at the
top are all custom attributes inserted by the Visual COBOL compiler for the benefit of the COBOL
runtime and debugger.
Overall though, there is very little code in HelloWorld that isnt written by the user. A constructor
(.ctor) method is inserted in any class where the programmer does not specify one explicitly or
you would be unable to create instances of the class. The class constructor (.cctor) method is also
inserted automatically to provide a hook for the Micro Focus COBOL runtime. If you disassemble
the C# version of Hello World (see Listing 4-3), it will look very similar to the Visual COBOL version,
apart from lacking the custom attributes and hook for the COBOL runtime.
Well only look at the .NET disassembly because as the previous two sections have shown, the
metadata and methods created by the Visual COBOL compiler are very similar on both platforms.
The ildasm view of HelloWorldProcedural.exe is Figure 7-2.
The compiler has generated the program as a class (because managed platforms dont have
a construct matching a procedural program). There is no program-id header in our one-liner, so
88
the class is named after the source file (HelloWorldProcedural.cbl). Our user code is inside the
HelloWorldProcedural() methodlike the class name, Visual COBOL has taken this from the
source filename.
As well as the constructors generated for the class version of Hello World, the Visual COBOL
compiler has also generated a number of extra methods. These methods are generated for any
procedural code that we compile to managed code with Visual COBOL, and together with the
Micro Focus runtime are how Visual COBOL is able to run existing procedural COBOL code in a
managed environment without you needing to make source code changes.
01 (prefix)-Property.
03 (prefix)-landlord-email pic x(80).
03 (prefix)-lease-id pic x(32).
03 (prefix)-rent pic x(4) comp-5.
03 (prefix)-address.
05 (prefix)-address-key.
07 (prefix)-address-1 pic x(80).
07 (prefix)-postal-code pic x(20).
05 (prefix)-city pic x(80).
05 (prefix)-state pic x(40).
You can see that the address fields are grouped under a single item, but that also (prefix)-
address-1 and (prefix)-postal-code are grouped together under a field called (prefix)-ad-
dress-key. In Chapter 6, we used the Address field as the unique key for RentalProperty objects
stored in a dictionary, and we stated that the Address class itself only carried out comparisons
using the first line of the address and the postal code. All the file records defined in this chapter
that have address components will group these two fields under an address-key field so that we
can use them as a key for file lookups.
Listing 7-9 shows part of the RentalPropertyFile programthe file definition, working-storage,
linkage, and the start of the procedure division. We arent going to reproduce the whole of
the RentalProperty listing here as the remainder of it is standard COBOL code for reading and
writing files. You can download the entire program, together with Java and C# projects that call it,
as explained in Chapter 2.
The procedure division header declares three parameters, all of which are defined in the
linkage section, two of them by copy replacing statements:
lnk-function: A numeric code determining the operation to be carried out.
lnk-property: A reference item that contains a record. The caller populates the record for
a write operation. For a read operation, the caller has to populate the lnk-address-key field
with the key to be read, and on return lnk-property will contain the record read.
lnk-status: A reference item that returns the status of the operation to the caller.
90
program-id RentalPropertyFile.
file-control.
select property-file assign to external propertyFile
file status is file-status
organization is indexed
access mode is dynamic
record key is filerec-address-key
.
file section.
fd property-file.
copy PropertyRecord.cpy replacing ==(prefix)== by ==filerec==.
working-storage section.
01 file-status pic 99.
linkage section.
01 lnk-status pic 99.
copy fileoperations.cpy replacing ==(prefix)== by ==lnk==.
copy PropertyRecord.cpy replacing ==(prefix)== by ==lnk==.
perform do-next-record
when update-record
perform do-update-record
end-evaluate
goback
.
To use RentalPropertyFile, you call it with three arguments and it carries out the specified function
and then returns. From COBOL, you could call it like this:
The COBOL program making the call would use copy replacing to create a data item
ws-rental-record to hold the record data. But what do you do if you want to access this kind of
code from Java or C#? You have two options:
Write a COBOL class to wrap the RentalPropertyFile program and provide an object-
oriented interface for consumption by a Java or C# program. The wrapper class would
be responsible for moving data from the COBOL data formats in the record group item to
managed data items like strings that can be easily used in Java or C#.
Compile RentalPropertyFile using the ilsmartlinkage directive. Visual COBOL generates
wrapper classes for you that you can invoke directly from Java or C#.
ilsmartserial: Makes all the generated classes serializable. When compiling to JVM, the
wrapper classes implement the Serializable interface, and when compiling to .NET, the
wrapper classes have the Serializable custom attribute.
The RentalPropertyFile project is compiled with the following directives:
ilsmartlinkage ilsmartnest ilcutprefix(lnk) ilnamespace(MicroFocus.COBOL)
The ilnamespace directive puts all of the generated code in the project inside a namespace
without any code changes to the project. There are known issues with referencing COBOL classes
from Java unless they are inside a namespace.
RentalPropertyFile has three linkage items referenced in the procedure division header.
Smart Linkage will generate a wrapper class for each item, and they will all be nested within
the RentalPro pertyFile class. Remember from the previous section that even though
RentalPropertyFile is purely procedural, it will be compiled into a class and the main entry point
will also be called RentalPropertyFile. So the wrapper classes generated are:
RentalPropertyFile+Function from lnk-function
RentalPropertyFile+Status from lnk-status
RentalPropertyFile+Property from lnk-property
The + sign is the COBOL separator for indicating a nested class (see Chapter 13, the Nested
Types section). The Visual COBOL compiler does not write any source files out for the wrapper
classesthey are generated directly into class files on JVM, and into the output assembly on .NET.
The autocomplete features of Eclipse and Visual Studio will prompt you with the right names for
accessors on instances of the wrapper classes, but you can also use the disassembler tools we
looked at in the previous sections if you want to see what classes, methods, and properties have
been generated.
Nested classes
Both Java and .NET enable you to nest one class inside another, as does Visual COBOL.
The Visual COBOL notation for a nested class is OuterClassName+InnerClassName, but
Java and C# both use OuterClassName.InnerClassName. Because this is a Visual COBOL
Guide, we will use the Visual COBOL notation when naming nested classes.
In the next two sections, we look at C# and Java programs that write and read a record using
RentalPropertyFile and the Smart Linkage wrapper classes. The two programs are identical apart
from syntax differences, so they are followed by a single section that explains the main points.
using System;
namespace CSharpRentalFileAccess
Calling Procedural Code from Java and C# 93
{
class Program
{
private static RentalPropertyFile rentalFile
= new RentalPropertyFile();
private const int READ = 1;
private const int ADD = 2;
private const int NEXT = 4;
private const int UPDATE = 5;
readRecord.LandlordEmail.TrimEnd(),
readRecord.Address1.TrimEnd(),
readRecord.City.TrimEnd(), readRecord.State.TrimEnd(),
readRecord.PostalCode.TrimEnd());
Console.WriteLine(formatted);
}
}
package JavaRentalFileAccess;
import com.microfocus.examples.RentalPropertyFile;
record.setState(London);
record.setPostalCode(W1X 2AA);
return record;
}
Although record and status are both instances of Smart Linkage wrapper classes, READ is
just a numeric constant. If you look back at RentalFileProperty itself, the entry point looks like this:
The property and status parameters are both passed by reference, which means their values can
be altered by the called program, so we need to use instances of the wrapper classes. In addition,
Calling Procedural Code from Java and C# 97
property is actually a complex group item, so the wrapper class enables us to read and write the
individual fields and map them between managed types and the COBOL representation, which is
essentially a byte array. But the function parameter is a numeric item, passed by value. Rather than
creating an instance of the wrapper class for it and setting the value, its much easier to pass the
numeric value.
The code that formats the retrieved records for display trims the trailing spaces from strings.
The actual fields in the COBOL records are fixed length; for example, the Address1 field is defined
in COBOL as pic x(80)80 characters. All the data is moved into the string object in the Smart
Linkage wrapper, which means any string less than 80 characters long will be padded with trailing
spaces.
If you try to set a field with a string that is too long, it will be truncated.
Run Units
Run units are an encapsulation of the Micro Focus runtime that enable you to run multiple instances
of a procedural program inside a single process. This enables you to call procedural COBOL code
in scenarios where you might want to use it from multiple instances of some other object and a
single instance of the procedural code will not workfor example, where it is going to be used from
different threads.
To use a run unit, you instantiate MicroFocus.COBOL.RuntimeServices.RunUnit, and then use
the GetInstance() method to create the instance of the program you want to use. Listing 7-12
shows the parts of the C# program that would change to use run units. The changes to the Java
program would look very similar.
using MicroFocus.COBOL.RuntimeServices;
using System;
namespace CSharpRentalFileAccess
{
class Program
{
. . .
static void Main(string[] args)
{
RunUnit<RentalPropertyFile> ru =
98
new RunUnit<RentalPropertyFile>(typeof(RentalPropertyFile)
try {
When you have finished with the instance of RentalPropertyFile , you should clean up by
invoking the StopRun() method on the run unit. In Listing 7-12, StopRun() is inside a finally
blockthis guarantees it will be executed whether or not any exceptions are thrown.
However, the RunUnit class implements the IDisposeable interface on .NET and the
Autocloseable interface on JVM, and will call StopRun() itself when a RunUnit object is disposed.
This gives us a simpler alternative. By wrapping the code that uses the RunUnit() inside a using
block in C#, or a try-with-resources block in Java, we dont need to invoke StopRun() explicitlyit
will get invoked as soon as the code in the block finishes executing.
Code excerpts for this style are shown in Listing 7-13 and Listing 7-14. The COBOL equivalent
to using a try-with-resources is using the perform using statement.
new RunUnit<RentalPropertyFile>(RentalPropertyFile.class))
{
program-id. Callable.
linkage section.
01 lnk-arg-1 pic x(20).
01 lnk-arg-2 pic s9(6) comp-5.
procedure division using by reference lnk-arg-1 lnk-arg-2.
display Hello from native code
display lnk-arg-1
display lnk-arg-2
goback.
end class.
The entry point for the native program is named Callable but before the managed program can
call the entry point, it must load the NativeLibrary executable into memory. The easiest way of doing
that is to set a procedure-pointer to NativeLibrary itself:
set pp to entry NativeLibrary.dll
The Micro Focus runtime needs to be able to locate NativeLibrary in order to load it. On Windows,
you can set the PATH environment variable to include the directory where NativeLibrary has been
built. On UNIX, you must set LIBPATH, SHLIB_PATH, or LD_LIBRARY_PATH, depending on the exact
type of OS you are using.
If you are running the projects from Eclipse, go to the Environment Variables tab for the
RunConfiguration dialog box, and add the appropriate value for the environment variable for your
operating system. Dont forget to concatenate the current value into the one you are setting. For
example, on Windows, you would set the value of PATH to be something like:
c:\source\examples\NativeLibrary\bin;${env_var:path}
Summary
In this chapter, we looked at some procedural COBOL, particularly for the benefit of Java or C#
programmers who are going to modernize legacy systems. We looked at the way COBOL defines
records, uses copybooks, and manages files. The Smart Linkage facility in Visual COBOL greatly
simplifies interoperation between older COBOL programs and languages like Java and C# by
exposing COBOL records as objects and handling the translation between COBOL types like
pic x and managed types like strings.
Finally, we looked at Visual COBOLs ability to call directly into native code. Java programmers
in particular will appreciate how much easier this is to do in COBOL than in Java using JNI. In the
next chapter we look at techniques for writing cross-platform code that runs on JVM or .NET.
CHAPTER 8
Cross-Platform
Programming
In this chapter, we start looking at some of the practical issues involved in writing code that will run
on both the JVM and .NET platforms. Although Visual COBOL itself compiles for either platform, at
some point in any real application you will inevitably want to take advantage of the libraries available
for JVM and .NET. Although they offer much of the same functionality, the APIs are not the same,
so as soon as you start working with them, you can find your application is tied to one platform.
However, it is possible to keep the main business logic of an application cross-platform while
still benefiting from the libraries available for .NET and JVM. The core Rental Agency application
classes that we looked at in Chapter 6 are written in portable code. All the code that is JVM or .NET
specific is kept inside the ExamplesSupport project. In this chapter, we look at the IDate type used
by the Rental Agency application to see how to exploit JVM and .NET libraries while still being able
to compile for either environment.
In this chapter, youll learn about:
Interfaces
Abstract classes
Conditional compilation
Extension methods
Visual COBOL also provides the inspect, string, and unstring verbs (documented in Chapter
15) to help with many common string manipulations.
However, at some point you need to write code that is specific to either the JVM or .NET platforms.
In the Rental Agency, we have hidden that code behind our own abstractions to keep it isolated to
as few classes as possible, and kept them in the separate ExamplesSupport project. There are two
techniques you can use when writing platform-specific code in the context of an application that
must compile and run on both platforms:
Use separate classes for JVM and .NET code. These classes can inherit from a common
type or implement an interface so that they can be used interchangeably from other code.
Use conditional compilation wherever you need to call a different API on each platform. You
can use compiler directives to mark sections of code so that they are only compiled on a
particular platform.
In this chapter, we use a mixture of both techniques to implement an interface called IDate,
whichrepresents a calendar date. It uses the Java java.text.Calendar class and the .NET
System.DateTime class to do the heavy lifting needed for arithmetic with dates and formatting for
different locales.
The code in the XPlatformDate project is actually included in the ExamplesSupport project used
by the Rental Agency exampleweve put it into a separate project here just to keep things simple.
The DateClient project requires XPlatformDate in order to build. If you are using Visual Studio, the
XPlatformDate.sln file loads both projects, and DateClient has a project reference to XPlatformDate.
If you are using Eclipse, import both projects into the same workspace. The DateClient project is
already set up with XPlatformDate on its JVM build path.
You can download the code as explained in Chapter 2.
interface-id COBOL.Book.XPlatformDate.IDate
implements
$if JVMGEN set
type Comparable
$else
type IComparable
$endif
.
*>
*> Return a new date object that is the date of this object moved forward
*> by the number of days passed in.
*>
method-id Add (days as binary-long) returning result as type IDate.
end method.
*>
*> Return the day of week of the current date.
*>
method-id. GetDayOfWeek() returning result as string.
end method.
end method.
end interface.
The interface code is not very long. An interface looks similar to a class, but it has an interface-
id header, and there is no implementation code inside any of the methods. There are no fields
eitheralthough it looks like IDate declares some fields, these are actually property declarations.
The Year, Month, and Day properties are part of our interface.
This interface contains some conditionally compiled code as part of the header. We want to be
able to sort dates into order, and to do this we have to implement the IComparable interface on the
.NET platform and the Comparable interface on the JVM platform.
Conditional compilation
The if/else/end compiler directives enable us to include different code for JVM and .NET.
Everything between $if jvmgen set and the next conditional compilation directive is
only compiled when compiling to JVM. From $else to $end in this case is where we put
the .NET platform code. If you wanted a conditional that selected for .NET code only, you
would use $if nojvmgen set. Compiler directives always start with a $ sign.
<<interface>> <<interface>>
IComparable Comparable
CompareTo(object) returning binary-long CompareTo(object) returning binary-long
<<interface>>
IDate
DateNet DateJvm
Figure 8-1 shows the Comparable interface for JVM and the IComparable interface for .NET,
which we have to implement to make IDate objects sortable. These two interfaces require you to
implement a single comparison method, which takes a single argument. The comparison method
has to return -1 if the current instance (the one you are invoking the method on) is less than the argu-
ment passed in, +1 if the current instance is greater than the argument, and 0 if they are the same.
In our case, we consider a later date greater than an earlier datethis will give us a chronological
sort order from earliest date to latest.
106
The logic is exactly the same whichever platform you are running on: You need to compare the
year, then the month, then the day until one is greater than the other, or they are all equal (in which
case, the two dates are the same). So we should be able to share the implementation between
the two platforms. To do this, we need to introduce a new class that sits between IDate and the
two implementation classes. We will use an abstract class for this. An abstract class can provide
a partial implementation of an interface while still forcing its subclasses to implement all the parts
of the interface not implemented in the abstract class.
The abstract class has to provide a method for every method in an interface it implements, but
you can mark any method you dont want to implement as abstract. An abstract method has no
implementation, much like a method in an interface. Because of this, you cant create instances of
an abstract classthey would be missing implementation details. The compiler flags any attempt to
use the new operator on an abstract class as an error. Subclasses of an abstract class must provide
an implementation of all the methods marked as abstract (unless of course, the subclass is also
abstract). This is also enforced by the compiler. Figure 8-2 shows the new inheritance hierarchy,
including AbstractDate.
<<interface>>
IDate
AbstractDate
Compare(object) returning binary-long
DateNet DateJvm
Listing 8-3
*>
*> Empty ctor - returns the current date.
Designing a Better Date Type 109
*>
method-id New () public.
set dtRepresentation to type DateTime::Now
end method.
*>
*> Constructor to return a specified date.
*>
method-id New (year as binary-long,
month as binary-long, #day as binary-long).
set dtRepresentation to new DateTime(year month #day)
end method.
*>
*> Constructor to create a date with the value passed in
*> as an IDate.
*>
method-id New (dt as type IDate).
invoke self::New(dt::Year dt::Month dt::Day)
end method.
*>
*> Constructor to create a date from a DateTime instance.
*>
method-id New(dt as type DateTime) private.
move dt to dtRepresentation
end method.
end class.
class-id MicroFocus.COBOL.Examples.Support.DateJvm
inherits type AbstractDate public.
end class.
AnyClientCode
<<interface>> <<interface>>
IComparable Comparable
compareTo(object) returning binary-long compareTo(object) returning binary-long
DateFactory
<<interface>>
GetDate() returning IDate
GetDate (yyyy mm dd) returning IDate IDate
GetDate() returning IDate
GetDate (yyyy mm dd) returning IDate
class-id COBOL.Book.XPlatformDate.DateFactory.
*>
*> Get todays date.
*>
method-id GetNewDate() returning #date as type IDate public static.
114
*>
*> Get a particular date.
*>
method-id GetNewDate(year as binary-long month as binary-long
#day as binary-long)
returning result as type IDate public static.
$if JVMGEN set
set result to new COBOL.Book.XPlatformDate.jvm.DateJvm (year month #day)
$else
set result to new COBOL.Book.XPlatformDate.NET.DateNet(year month #day)
$end
end method.
end class.
class-id COBOL.Book.DateClient.MainClass.
*> (Malaysian
*> Independence)
set USIndependence to
type DateFactory::GetNewDate(1776 7 4) *> US Independence
*> Day
set rights to type DateFactory::GetNewDate(1789 8 26) *> France adopts
*> Declaration of
*> Rights of man
set telephone to
type DateFactory::GetNewDate(1876 3 10) *> 1st phone call
set magnaCarta to
type DateFactory::GetNewDate(1215 6 15) *> Magna Carta
set hastings to
type DateFactory::GetNewDate(1066 10 14)*> Battle of
*> Hastings
sort dates
display Dates in order
perform varying nextDate as type IDate through dates
display nextDate::GetFormattedDate()
was a nextDate::GetDayOfWeek()
end-perform
end method.
end class.
You can see how the DateFactory enables the client code to deal only with IDate instances
and remain unaware that this has been implemented differently on the two platforms. As well as
creating a number of IDate instances, it also declares an arrray of IDate instances called dates,
and uses set content to initialize this with all the dates we have created.
MainClass loops through the array twice, before and after sorting. The sort verb gives us a
platform-independent way to sort an array, providing the objects it stores implement IComparable
or Comparable. We use perform varying to loop through the arrayweve seen this statement
already in Chapter 3 and Chapter 6 and it is fully documented in Chapter 15.
116
The display statements use GetFormattedDate() to return the date in a short format appropriate
to the current locale on your computer. So in the United States, you will see month/day/year and
in Europe, you will see day/month/year. The DateFormat and DateTime objects used on JVM and
.NET both format using the current default locale, and have methods to enable you to change the
locale to a different one. Changing the locale isnt something weve exposed through the IDate
interface, but it would be easy to implement.
Extension Methods
Extension methods enable you to add extra methods to an existing class without recompiling it or
changing it. These can be useful for cross-platform programming as they give you another way to
hide some of the differences between the two platforms. For example, if you want to force a string
to uppercase, the method is called ToUpper() on .NET and toUpperCase() on JVM. By extending
the string class, you can provide a method that is the same on both platforms.
An extension method only has access to the public members of the class you are extending, so
you cant do anything that requires access to the internal workings of the class. The ExamplesSupport
project includes a file called StringExtender.cbl, which extends the string class so that we can carry
out some common string manipulations using the same code on either platform.
Extension methods must be put inside a static class, and the first argument for an extension method
is always an instance of the class you want to extend. Extension methods are fully documented in
Chapter 13, in the Extension Methods section. The StringExtender class is shown in Listing
8-7. It implements the ToUpper() and ToLower() methods on JVM (by calling the toUpperCase()
and toLowerCase() methods). This enables Rental Agency code to invoke ToUpper() on any string
on either platform and get the expected result. It also provides methods to trim leading and trailing
spaces from a string and to see whether a string ends with a particular substring.
end class.
Summary
In this chapter, we looked at some of the issues in writing code that will compile and run equally
on JVM or .NET, and saw how to minimize the impact of platform-specific code on an application.
We also took a more detailed look at interfaces. Interfaces are an integral part of object-oriented
programming on both the JVM and .NET platforms, and have many uses beyond cross-platform
programming. We also wrote an abstract class to provide a partial implementation of an interface,
which helped us share common code between our two implementations of IDate. Finally, we used an
extension class to add behavior to string so that we could use some of its functionality consistently
on either platform. The code for the RentalAgency example we have used through most of this book,
and which we return to in the next chapter, is the same on both the .NET and JVM platforms. This
is because we have used the techniques outlined in this chapter to hide all the platform-specific
code inside a single project (ExamplesSupport). All the business logic for our application is portable
between .NET and JVM. Platform-independence is a powerful feature of Visual COBOL.
CHAPTER 9
Storage Choices
At the moment, our Rental Agency has a set of classes that manage the data and implement the
business rules described in the The Rental Agency Classes section in Chapter 6. We now need to
provide a mechanism to store the data persistently on file. The usual choice for a modern application
would be either a relational or a NOSQL database.
However, many legacy COBOL applications use COBOL indexed files to store data, so we will
use these in our Agency Store application. This avoids introducing third-party dependencies into
our examples and also keeps our focus on Visual COBOL rather than other technologies
A project called FileStorage will handle the file access using three programs, RentalPropertyFile,
LeaseFile, and PersonFile . All three programs contain very similar code, but read and write
records that will enable us to store RentalProperty, Lease, or Person objects. We introduced
RentalPropertyFile in Chapter 7 and used Smart Linkage to call it from Java or C#.
We will use an intermediate project called AgencyStore to manage saving and retrieving objects
from the indexed files. Because AgencyStore is written in Visual COBOL, we could reuse the copy-
books used to define the records and use the COBOL call verb to call the programs directly, but we
will use Smart Linkage as we did in Chapter 7 because it provides such a convenient mechanism.
We will use a simple client application called PersisentLeases to see the new persistence layer
working. This has methods to write, read, or update a few records. Command-line parameters enable
119
120
you to specify the order and type of operations. Figure 9-1 shows the projects and dependencies
for our implementation of a persistent rental agency.
The FileStorage project itself has no dependencies. This project is procedural COBOL code,
which knows how to read and write records defined in COBOL copybooks to indexed files. The only
part of our application that will interact with it is AgencyStore. This understands both the objects
in RentalAgency and knows how to call FileStorage. All interactions to retrieve and save objects
are through AgencyStore. The RentalAgency itself is almost unchanged from Chapter 6. The only
enhancement we will make is to add an event mechanism for notifications when objects change.
AgencyStore
ExamplesSupport
We will store them using the following programs inside the FileStorage project:
LeaseFile: Stores Lease objects
PersonFile: Stores both Tenant and Landlord objects:
RentalPropertyFile: Stores RentalProperty objects
The three programs are very similar, but have different record and file definitions. Lets look
at what we want AgencyStore to do first, before we look at the details. We started looking at
RentalPropertyFile in Chapter 7, but well revisit it again now.
Introducing the Agency Store 121
Method overloading
Distinguishing methods of the same name in the same type by different arguments is
known as method overloading, and is explained more formally in the Method Signature
and Returning Value section in Chapter 13.
In this chapter, we work through the basic logic for saving, finding, and loading RentalProperty
objects first. We then look at saving Landlord and Tenant objects as we are saving two different
types in one file. Then, we look at how the objects are loaded back from file. Next, we look at an
update mechanism to save objects that change after being saved, and, finally, we look at the iterators.
122
AgencyStore
Save(tenant) Tenants
FindTenant(email)
PersonFile
Save(landlord)
Landlords
FindLandlord(email)
Save(lease) LeaseFile
Leases
FindLease(key)
Save(property) RentalProperties
FindProperty(address) RentalPropertyFile
When we load the cache, we re-create the relationships between the objects. We can do this
by loading in the following order:
1. RentalProperty objects
2. Person objects
3. When loading a landlord, we can re-create the list of owned properties as all the
properties are now loaded.
4. Lease objects
5. When loading each lease, we can re-create the list of leases as we now have all the
Tenant and RentalProperty objects loaded.
01 filerec-person.
03 filerec-type pic x.
88 filerec-landlord-type value L.
88 filerec-tenant-type value T.
03 filerec-name pic x(128).
03 filerec-email pic x(128).
03 filerec-address.
05 filerec-address-1 pic x(80).
05 filerec-city pic x(80).
05 filerec-state pic x(40).
05 filerec-postal-code pic x(20).
03 filerec-lease-id pic x(32).
The second field in the record, filerec-type, is a single character indicator to say whether the
person is a landlord or a tenant. Listing 9-2 shows the parts of AgencyStore that are needed to
save a Tenant or Landlord object, along with the AgencyStore fields and constructor.
method-id New.
create landlordDictionary
create tenantDictionary
create rentalDictionary
create leaseDictionary
end method.
invoke personFile::PersonFile(read-record
personRecord fs)
invoke self::PersonToRecord(person personRecord)
if fs::Status = statusOk
invoke personFile::PersonFile(update-record personRecord fs)
else
if fs::Status = statusNotFound or fs::Status = statusFileNotFound
or fs::Status = statusIndexFileNotFound
invoke personFile::PersonFile(add-record personRecord fs)
end-if
Saving Landlords and Tenants 125
end-if
if fs::Status <> statusOk
raise new PersistenceException(Exception saving record, status
& fs::Status)
end-if
end method.
AgencyStore fields include dictionaries for the different types of objects we are managing. The
file storage programs are declared as instances of classes, as introduced in the Using Smart Link-
age section in Chapter 7. These programs (LeaseFile, PersonFile, and RentalPropertyFile)
are all declared as static fields and initialized in the declarations by using a value clause. This
ensures that there is only ever one instance of each of these objects in the process (see the The
Smart Linkage Code Explained section in Chapter 7).
The dictionary objects are not shared between instances and are created inside the constructor
(New() method).
There is a Save() method for each type of Person (Landlord and Tenant both inherit from
the Person class). Each of these methods stores the object in a dictionary of landlords or tenants,
and then invokes a Save() method, which has common code for saving a Person. The key for the
indexed file is the email address of a person.
The process to save the object in the file is as follows:
1. Check whether a record with the same key already exists in the file. If it does, we are
updating a record, not adding a new one (these are different operations).
2. Copy the fields in person into the personRecord. This is done by method
PersonToRecord(), which is explained in Listing 9-3.
The PersonToRecord() method moves the data from the object into a record. It tests the type of
object using the instance of operator, which returns true if the specified object on the left side
of the operator is compatible with the type on the right side; this sets the value of the Type field
of the record to either T or L. If the Person object is neither a Tenant nor a Landlord, the method
raises an exception. This is not an error you would expect to see in practice, but it could happen if,
for example, a new type of Person was added to the RentalAgency project, but the AgencyStore
was not updated to copeor you ran mismatched versions of the two libraries.
A Landlord object also contains the list of properties the landlord owns, but the code above
hasnt attempted to save this information. And a Tenant can also contain a reference to a Lease
objectagain, we havent saved that. The reason is that we can re-create these relationships from
the information we save with each RentalProperty and Lease object.
Each RentalProperty record includes the email address of the owner, so when we reload the
RentalProperty objects, we can use this information to add them back to the Landlord objects.
And each Lease record contains the email address of the Tenant. We will look at the process of
reassembling all this information in the section Restoring the Object Relationships.
Finding a Person
Once a RentalProperty object has been saved in the AgencyStore, we need to retrieve it again.
Listing 9-4 shows the FindLandlord() method.
The code checks whether the object already exists in the dictionary, and returns it if it does. If
the object is not in the dictionary, result will be null. This assumes that the dictionary has already
been populated by reading through the Person file (see the next section). New objects are added to
Saving Landlords and Tenants 127
the dictionary as they are saved so the dictionary should stay up to date. The FindTenant() method
works exactly the same way as FindLandlord(), but operates on a different dictionary.
The LoadAllPersons() method is marked as private to prevent it from being called outside
the AgencyStore class. As we stated in the beginning of the chapter, we need to load the different
types of objects in a specific order so that we can reinstate any relationships that exist between
objects. The separate methods to load different types of objects are called from a single public
method, LoadCache(), to ensure that they are called in the right order.
The loop repeats indefinitely until we get a file status other than statusOk, at which point we exit
the loop. The RecordToPerson() method does the work of creating a Tenant or Landlord object
from the record.
method-id LoadCache().
invoke LoadAllPersons()
invoke LoadAllRentals()
invoke LoadAllLeases()
end method.
declare nextKey =
perform until exit
declare leaseRecord = new LeaseFile+Lease()
set leaseRecord::Id = nextKey
invoke leaseFile::LeaseFile(next-record leaseRecord fs)
if fs::Status <> statusOk
exit perform
end-if
set nextKey = leaseRecord::Id
declare lease = self::RecordToLease(leaseRecord)
invoke RegisterUpdateable(lease)
set leaseDictionary[lease::Id] to lease
end-perform
end method.
method-id Cancel.
set cancelled to true
invoke leasedProperty::CancelLease()
invoke tenant::CancelLease()
invoke self::OnChange()
end method.
This would be quite a messy solution. The dependency diagram in Figure 9-1 shows that
AgencyStore is dependent on Agency (because it needs to know about the different types of objects
it has to store). But we dont want Agency objects to know how they are being storedapart from
anything else, this would introduce a circular dependency between the two projects, which makes
it difficult to build them.
Instead, we will use the Visual COBOL event mechanism. An event provides a loosely coupled
notification mechanism that something has happened. In our case, we want to introduce a
changeEvent, which our objects can fire every time their state changes. By using an event, we
dont need to introduce any special knowledge of storage mechanisms into our business logic. And
an event can be consumed by multiple listeners, so it can be used by any part of the application that
needs to know when the state of an object has changed.
There are two halves to making the mechanism work:
We have to define the event itself, and the code to fire it.
We have to create an event handler and attach it to the event on any objects we are
interestedin.
Formal syntax definitions and documentation for delegates and events are
covered in in the Delegates section in Chapter 13.
Dening an Event
To define an event:
1. Define a delegate type for the event: A delegate is an object that encapsulates a method
on a particular object (or a static method on a class). When you invoke the delegate, the
encapsulated method is executed. A delegate type defines the method signature for any
delegate methods. A delegate is like a procedure pointer, but it is type safe because it
can only invoke code with the expected type and number of parameters.
2. Define an event of your delegate type: An event is a field marked with the event keyword;
the field must be a delegate type. Visual COBOL defines most fields as private by
default; event fields are public by default.
3. Create a method that will fire the event.
Once you have defined an event, you can attach event handlers to it.
When an event fi res, it invokes all the delegates attached to it. We want our event to be a
notification for this object has changed its state. The only data we will pass with the event is the
object that has changed. So that we can handle all objects in the RentalAgency exactly the same
way, we will introduce a new interface called Updateable. Any object that implements Updateable
will have the changeEvent. Listing 9-10 shows the Updateable interface definition.
interface-id MicroFocus.COBOL.Examples.Lettings.Updateable.
01 changeEvent event type ChangeDelegate.
end interface.
Updating Saved Objects Using Events 133
Now we need a delegate that defines the shape of our event, shown in Listing 9-11.
delegate-id MicroFocus.COBOL.Examples.Lettings.ChangeDelegate
(item as type Updateable).
end delegate.
Any method that is going to be encapsulated in a ChangeDelegate must take a single argument
of type Updateable. Although you can specify a returning clause on a delegate (a delegate can be
used as a pointer to any kind of method), delegates used for events dont usually specify a returning
clause. A single event can broadcast to multiple delegates, but there is no facility for returning a
separate value from each delegate.
When OnChange() is invoked, it copies the event to a local variable. The local copy is a precaution
against event handlers being removed from changeEvent on a different thread while this method
is executing. When there are no handlers attached to an event, it is a null reference, and invoking it
would cause a null reference exception. Keeping a private local copy of the public event is sensible
defensive coding.
We now have the infrastructure for changeEvent. All of our business objects (RentalProperty,
Lease, Tenant, and Landlord) have to implement the event, and then whenever their state changes,
they invoke the OnChange() method. The code is going to be identical for all our business objects,
so we will create a new abstract class, AbstractUpdateable with the implementation and have all
our business objects inherit from that. AbstractUpdateable implements the Updateable interface.
Figure 9-3 shows the new UML class diagram for the AgencyStore project.
134
<<interface>>
Updateable
<<abstract>>
AbstractUpdateable
Landlord Tenant
The diagram shows that we need to update the class definitions for RentalProperty, Lease, and
Person to inherit from AbstractUpdateable. Listing 9-13 shows the listing for AbstractUpdateable.
class-id MicroFocus.COBOL.Examples.Lettings.AbstractUpdateable
implements type Updateable abstract.
01 changeEvent type ChangeDelegate event public.
method-id
$if JVMGEN set
toString()
$else
ToString()
$end
returning result as string override.
set result to self::GetDisplayValue
end method.
Updating Saved Objects Using Events 135
end class.
As well as providing an OnChange() method and the changeEvent field, weve taken advantage
of AbstractUpdateable to provide implementations of toString() (JVM) and ToString() (.NET)
using conditional compilation to differentiate between the two. Our t/ToString() method invokes a
method called GetDisplayValue() to fetch the actual string representation of the object. Because
this method is marked as abstract, all subclasses of AbstractUpdateable are forced to provide
an implementation.
Listing 9-14 shows the RentalProperty updated to fire the new event; we wont list all the
changed classes here as the changes are very similar in all of them.
end class.
The main changes are shown highlighted in bold. Any method that makes a change to the state
of RentalProperty now invokes the OnChange() method implemented in the abstract class. Weve
had to change the implementation of the Owner property to use the property-id syntax rather
than simply declaring the field as a property. Property-id syntax enables you to write properties
that execute some code when they are accessed; in this case, we use it to invoke OnChange()
from the property setter. See the Properties section in Chapter 13 for the syntax definition and
documentation for property-id.
Any object firing changeEvent passes itself through as the parameter (see Listing 9-12), so the
code in UpdateRecord() finds out what type of object it is, and then casts it to that type so that it
can invoke the correct Save() method. Listing 9-16 shows the Save() method for a RentalProperty
with the code to attach the event handler highlighted.
Listing 9-16 Save method with the code to attach the event handler
Weve added a RegisterUpdateable method (see Listing 9-17) to attach the event handler. This
uses the attach verb to connect the event handler to the changeEvent on an Updateable object.
Youll notice that we use the detach verb to attempt to detach the event handler before attaching it.
This is because you can actually attach the same event handler multiple times, and when the event
fires, it will get invoked once for each attachment. However, it is safe to use the detach verb even
when no attachment exists yet. Objects will pass through the Save() method each time they change,
so to avoid attaching the same object event repeatedly to our event handler, we execute detach first.
The first time an object comes through the Save() method, the detach statement has no effect,
but the attach statement attaches the handler to the object event. On subsequent occasions,
detach will remove the existing attachment before attach re-creates it. This way, each object only
gets attached to the event handler once.
138
The code for all four iterators is similar; each does a perform varying through the values held
in the dictionary for objects of that type. Listing 9-18 shows the iterator for retrieving Landlord
objects You can see the code being used in the next section.
Iterators provide one mechanism for giving access to the contents of a collection (list or dictionary)
without providing a reference to the collection itself. However, you should be aware that if you add
or remove elements from a collection while iterating over it, the iterator is rendered invalid and will
throw an exception. In multithreaded applications, this is something you need to guard against,
either preventing the collection from being modified while being iterated or creating a shallow copy
of it and iterating the copy.
For formal syntax definitions and documentation, see the Iterators section in
Chapter 13.
Putting It All Together 139
class-id MicroFocus.COBOL.Examples.PersistenceLeases.
*> CONSTANTS
01 ADDRESS_1 static type Address value
new Address(28 Acacia Avenue, Chiswick, London, W1X 2AA)
initialize only.
01 ADDRESS_2 static type Address value
new Address(119 St Geraints Rd Reading Berkshire RG60 2RT)
initialize only.
01 EMAIL_1 constant string value pliny.elder@example.com.
01 EMAIL_2 constant string value gritpype.thynne@example.com.
invoke persistence::IterateData()
when D
when d
invoke persistence::DeleteFiles()
end-evaluate
end-perform
end method.
method-id WriteData.
declare tenant1 = new Tenant(Pliny Elder, EMAIL_1 )
declare landlord1 = new Landlord(Gritpype Thynne, EMAIL_2)
set landlord1::Address to ADDRESS_2
declare rental1 = new RentalProperty(ADDRESS_1) ;
invoke landlord1::AddProperty(rental1)
invoke store::Save(tenant1)
invoke store::Save(landlord1)
invoke store::Save(rental1)
end method.
method-id ReadData.
declare retrievedLandlord = store::FindLandlord(EMAIL_2)
display retrievedLandlord
declare retrievedTenant = store::FindTenant(EMAIL_1)
display retrievedTenant
declare retrievedProperty = store::FindProperty(ADDRESS_1)
display retrievedProperty
end method.
method-id UpdateData.
declare startDate = type DateFactory::GetCurrentDate()
declare tenant = store::FindTenant(EMAIL_1)
declare house = store::FindProperty(ADDRESS_1)
declare lease = new Lease(startDate, 60, house, tenant)
invoke store::Save(lease)
end method.
method-id IterateData.
perform varying nextLandlord as type Landlord
through store::GetLandlords()
display nextLandlord
end-perform
Putting It All Together 141
end method.
method-id DeleteFiles().
01 filenamePicX pic x(512).
01 statusCode pic xx.
declare propertyFile as string
declare leaseFile as string
declare personFile as string
accept propertyFile from environment dd_propertyfile
accept leaseFile from environment dd_leasefile
accept personFile from environment dd_personfile
end method.
end class.
The main() method loops over all the parameters on the command line, executing one command
at a time until there are none left. It constructs an instance of PersistentLeases, which has one
method for each different command.
142
Before you run the program, you must set the environment variables dd_propertyfile, dd_leasefile,
and dd_personfile to point to the path and filename where you want the files created. The directory
where the files are to be written must exist before you run the program. Visual Studio and Eclipse both
provide facilities for setting the environment variables if you are running the program from the IDE.
Visual Studio uses a configuration file, which you can find in the PersistentLeases project,
called app.config. At build time, this is copied into the build output directory and given the name
PersistentLeases.exe.configyou can use this mechanism to set things like environment variables
and COBOL switches and ship it as part of an application. The app.config supplied sets the
environment variables to write to a directory called LettingsData, using relative paths so it appears
as a directory at the same level as the other source directories in the project (create the directory
before you run the program). To set the command-line arguments, open the Project Properties page
for PersistentLeases, and open the Debug tab, which has a text box for you to set command-line
arguments.
Eclipse enables you to configure the command-line arguments and variables through the Run
configuration for the application. We have supplied a Run configuration file with the PersistentLeases
project called PersistentLeases.launcher. You can see this configuration by clicking Run, Run
Configurations, and expanding the COBOL JVM Application entry on the left-hand side of the
dialog box and then clicking PersistentLeases. Like the Visual Studio project, the environment
variables have been preconfigured to point to a relative directory called LettingsData. You can set
the command-line parameters on the Arguments tab of the Run Configuration dialog box.
Try running the application with the commands:
W I U I
This creates and saves some data, displays it, and then makes an update and displays it again.
If you run the application again, with just the I command, it should load and redisplay the data.
Summary
In this chapter, we created a basic persistence mechanism for saving business objects from the
RentalAgency. We used events to notify object changes and defined iterators to loop through the
data of a particular type. When we return to the Rental Agency example in Chapters 11 and 12, we
will use the persistence mechanism shown here in a version of the application that has a full UI, and
we will also show how the event mechanism can be used to keep the UI up-to-date.
C H A P T E R 10
143
144
Java code. There isnt a visual UI designer for JVM that will create Visual COBOL code, so the
Java Swing UI section of this chapter involves a lot of Java code (which calls our Visual COBOL
Rental Agency business logic). This chapter also shows you how to handle Visual COBOL events
in JavaJava does not have direct support for an event model in the way that Visual COBOL and
.NET do. It will help if you have some familiarity with Java to follow this section.
Click the Details button to display the details for the record currently selected. The secondary
windows brought up by the New and Details buttons are not modalyou can still interact with the
Main Window when these other windows are displayed. Figure 10-2 shows two Rental Property
windows open at the same time. Once a Rental Property is already created, you can update the
rent; when you update the rent, the value changes on all the windows displaying the rent.
146
The UI uses the event mechanism we introduced in Chapter 9whenever any of the informa-
tion in our Rental Agency objects changes, an event is fired. By registering for these events in the
UI, we can use them to update the user interface. This further demonstrates the elegance of an
event-based approach; the business objects fire an event whenever they change. Any other code
can register for those events and act on them without the business logic needing to know how or
where those events are used.
To get familiar with the application, lets add a property, tenant, and landlord, and then lease
the property. The instructions are the same for the JVM and .NET versions of the user interface,
so once youve downloaded and built the latest version of the application, start up JavaLeases or
WindowsLeases and perform the following steps:.
1. The application main window opens with the Rental Properties radio button already se-
lected. Click New to create a new Rental Property.
2. In the Property View dialog box, enter an address and Monthly Rent. The combination of
Address Line 1 and Monthly Rent is the unique key for the property.
4 School Rd
Shipley
West Yorkshire
BD14 1ST
750
3. Click OK and then click the Tenants radio button to switch to the Tenant View. Click
New, enter a new tenant name and email, and then click OK:
Margo Fender
m.fender@example.com
4. Click the Landlords radio button and create a new Landlord:
Holly Spitfire
hollyspitfire@example.com
The User Interface 147
5. Select Holly Spitfire from the list box and click Details. Click the Add button on the
Landlord View to display all the properties not currently owned by anyone, and select the
property at 4 School Road. Click Done.
This is the part of the application that works in the incomplete version, as explained in the in-
troduction at the beginning of the chapter. The next set of instructions shows you how you can add
a lease, either running the complete version of the application, or after you have worked through
the example of adding the lease functionality in the rest of this chapter. To lease a property to a
tenant (Margo Fender):
1. Click the Rental Properties radio button and select the property from the list boxthis
enables the New Lease button (this button is only enabled when you have an unleased
property selected).
2. Click the New Lease button. This displays the New Lease dialog box, which is slightly
different on JVM and .NET (see Figure 10-3). Windows Forms includes a Date Picker
control, but Swing does not (you can find third-party Date controls for download on
the web). So on JVM, you must enter start dates in the format dd/mm/yy or mm/dd/yy
(depending on locale), but the Windows Form version of the application lets you pick the
date from a calendar.
3. Click the Tenant button, select Margo Fender from the Tenant Selector, and then click
OK to return to the New Lease dialog box.
Figure 10-3 The Windows New Lease Form and Java Swing New Lease dialog box
4. Pick or enter todays date as the Lease Start, and pick an end date three months away as
the Lease End. The date fields in the JVM application will validate dates entered.
5. Click OK to create the new lease. Click the Leases radio button to see all the leases on
the system.
Now that you have seen the application, lets look at the general architecture before going into
the specifics for the .NET and JVM versions.
148
Application Architecture
The .NET version of the UI adds two more projects to the persistent version of the agency from
Chapter 9:
WindowsLeases: COBOL Windows Forms project defining all the windows and dialog
boxes for the application
AgencyControls: COBOL Windows Forms control library defining two reusable custom
controls for the UI
The JVM version of the UI also has two new projects:
JavaLeases: Java Swing project defining all the Windows and custom controls for
theapplication.
AgencyJavaBridge: COBOL project to handle events from the Rental Agency and send
them on to the Java UI. Java does not have a native event mechanism like Visual COBOL
and .NET, so we need this intermediate layer.
There is one more project shared by both user interfacesAgencyText. This contains a single
class, Messages, which defines messages and title text that can be shared by both user interfaces.
The messages are all static public strings. Keeping all the text for an application in one place makes
it easier to localize the application for different languages. We havent gone as far as defining all
the text in the application in here (all the labels on the UI are defined in the UI classes), and we have
had some text defined in our business objects, but all other new text used in this chapter is defined
here. You can see the full dependency diagram for the application in Figure 10-4.
AgencyText
.NET UI JVM UI
AgencyStore
WindowsLeases JavaLeases
FileStorage
AgencyControls AgencyJavaBridge
RentalAgency
ExamplesSupport
The UI consists of the following main classes (the class names are the same for the JVM and .NET
versions, and the implementations are as similar as possible allowing for the differences between
the respective UI technologies):
Using the Windows Form UI 149
ItemSelector: Enables you to pick a single record out of a list box. Used to select Rental
Properties for adding to a landlord, and to pick a tenant for a lease.
LandlordView: Displays a landlord and enables you to add properties.
LeaseView: Displays a single lease, and enables you to cancel or extend it.
MainWindow: Displays different record types and is the main window for the application.
NewLease: Enables you to create a new lease (this is the functionality you will add in the
Windows Forms UI and Java Swing UI sections of this chapter).
NewPerson: Enables you to create a new tenant or landlord.
PropertyView: Displays the details of a property, and allows you to change the monthly rent.
TenantView: Enables you to display details for a single tenant.
The Toolbox
Designer file
Figure 10-6 The designer file in the Solution Explorer
Using the Windows Form UI 151
A partial class enables a class definition to be split across different source files, so that the gener-
ated designer code can be kept separate from user written code. Close MainWindow.Designer.cbl,
then right-click on the design view and select View Code. This opens a source view of MainWindow.
cbl in a separate tab. This is where you put the application logic for the window. MainWindow.cbl
has the same class-id header as MainWindow.designer.cblthese two source files are compiled
into a single class. The constructor for the class is in MainWindow.cbl, and includes the statement:
invoke self::InitializeComponent
Partial classes
Partial classes are not supported on JVM, and they are only used on .NET to support the
visual designers in Visual Studio. We recommend that you do not use partial classes for
any other purpose.
Painting a Form
We are going to add a new form to the application to enable us to lease a property to a tenant.
Figure 10-3 shows the form we are going to create. We are going to add the form, the code to make
it create a new lease, and also look at how the UI uses events and delegates.
As you add controls to the design surface on your form, be careful not to
double-click any until you want to add an event handler (the ifollowing instruc-
tions tell you when to do this). Its very easy to inadvertently add unwanted event
handlers. If you do add one by mistake, go to the Events view on the Properties
pane for the control (click the lightning bolt icon), and delete the event value
against the rst event in the list (this is always the default action for the control).
To paint a form:
1. Right-click on WindowsLeases in the Solution Explorer and click Add > New Item.
Select Windows Form from the Add New Item dialog box, give it a name of New Lease,
and click Add. This creates the NewLease form and opens it in the designer. Click the
right-hand edge and drag it to the right to make the form slightly larger. It needs to be
152
about 470 pixels wideyou can see the size of the form in the Properties pane in the
Layout group.
2. Find the Text property in the Properties pane and change the value to New Lease
(with a space). This sets the title of the form. Find the ControlBox property (grouped
under Window Style) and set the value to False. This removes the System menu and
Minimize/Maximize/Close buttons from the window frame, so it looks like the one shown
in Figure 10-7.
3. The Address Fields on the New Lease window are a single custom control, which is also
used for the New Property form. Lets add the custom controls to the toolbox.
4. Right-click inside the toolbox and select Choose Items. This displays the Choose
Toolbox Items dialog box. Click the Browse button (bottom right of the dialog box), which
opens a file chooser dialog box. Navigate to AgencyControls\bin\debug and select
AgencyControls.dll.
The list on the .NET Framework Components tab now has the com ponents you have
added. You can find them more easily by sorting the list by Namespace and scrolling to
AgencyControls (see Figure 10-8).
Using the Windows Form UI 153
5. Click OK in the Choose Toolbox Items dialog box. The new controls appear in the toolbox
(see Figure 10-9).
New controls
6. Drag the AddressFields control onto the New Lease form and position it to look like
Figure 10-3. The control is named AddressFields1 (this is the name of the member
variable that represents the control inside the forms source code).
7. Drag a button onto the design surface. Change the Text property to &Tenant (the &
creates a keyboard mapping for the T key), and the (Name) property to tenantBtn.
8. Put a text box next to the Tenant button, and resize it to the same length as the address
fields. Rename it to tenantBox.
9. Add a label, and set the text to &Lease Start. Put a DateTimePicker control next to it.
10. Repeat the process for the Lease End label and DateTimePicker.
11. Finally, add two more buttons at the bottom of the form, label them OK and Cancel, and
name them okBtn and cancelBtn.
12. Double-click on the Cancel button in the designer. Double-clicking a control creates an
event handler for the controls primary action. It opens up the code view, NewLease.cbl,
positioning the cursor in the newly generated event handler. In this case, it is a method
called cancelBtn_Click(). Insert this statement into the method body:
invoke self::Close()
13. Close() is a method inherited by our form, and it closes the form when executed. We
have just added the code that dismisses this form when the end user clicks Cancel.
The New Lease form is now visually complete. Lets add the code to the MainWindow class
todisplay it.
3. We need to replace the statement that disables btnNewLease (the field representing the
New Lease button) with code that enables it if we are in the Rental Property view and
have selected a Rental Property in the list box that is not currently leased. Replace the
existing method with the code in Listing 10-2.
4. To see all the places this method is called from, put the cursor on the method
name setBtnEnableState and press Shift+F12 (or right-click and select
Find All References). You can now see all the places this method is used. It is called
every time we change the view, or select something from the list box. Every time one of
these actions occurs, we check to see whether the New and New Lease buttons should
be enabled or disabled.
Listing 10-2 Code to enable and disable the New Lease button
2. Find the constructor for the NewLease class (the New method, just below the fields we
just added). At the moment, the only thing it does is invoke the InitializeComponent()
method, which creates all the controls for the form and wires up any event handlers (see
the Partial Classes section earlier in this chapter). Change the constructor to look like
Listing 10-3now when LeaseView is constructed, it must be given an AgencyStore (so
it can look up other records) and the RentalProperty the lease is for.
3. We have to change the code in MainWindow that constructs and displays the NewLease
form so that it passes the required information in. Open the code view for MainWindow
and find the btnNewLease_Click() method. Change the method body to read:
Now that the NewLease class has access to the information it needs, we can start adding the
remaining functionality. First, we will add the code to select the tenant. When the user clicks the
Tenant button, we will pop up a modal dialog box displaying a list of all the tenants not currrently
leasing a property. We are going to reuse an existing class called ItemSelector.
working-storage section.
01 enumerator type ItemIterator.
01 isValid type ItemValidator.
01 ok condition-value.
method-id NEW (enumerator as type ItemIterator,
validator as type ItemValidator).
invoke self::InitializeComponent()
set self::enumerator to enumerator
set self::isValid to validator
set okBtn::Enabled to false *> only enabled when tenant is selected
goback.
end method.
When you construct an ItemSelector, you have to pass in two arguments: an ItemIterator and an
ItemValidator. These are both delegates defined in the WindowsLeases project (see Listing 10-6).
delegate-id ItemIterator()
returning items as type IEnumerable[type Updateable].
end delegate.
In the previous chapter, we used delegates for receiving event notifications. Now, we are using
them so that we can write a very general-purpose class like ItemSelector (pick an item from a list),
which we can instantiate with different behaviorslike changing the criteria for the items in the list.
The ItemIterator delegate encapsulates a method that will populate the list box. More specifi-
cally, it must point to a method that will return an IEnumerable[type Updateable]. IEnumerable
is an interface that specifies methods that enable you to enumerate all the objects in a collection.
It is a generic type, which means that it operates with the type of objects you specify when you
declare it for usein this case, IEnumerable[type Updateable] means that it will return objects
of type Updateable . We touched briefly on generics when looking at lists in the The Landlord
Class section in Chapter 6.
In the Iterating Over Objects section in Chapter 9, we defined iterators in the AgencyStore
class to return RentalProperty, Landlord, Tenant, and Lease objectsall these objects are of
type Updateable as they all implement the Updateable interface. The ItemSelector can populate
its list box by running perform varying... through over the ItemIterator delegate it was
constructed with. We dont need to build any special logic into ItemSelector to tell it how to get
rental properties or tenantsor any other type of object that implements Updateable we just
give it a delegate that encapsulates an iterator to return the objects we are interested in. We could
use ItemSelector for a type of object we define at some point in the future, and as long as it is an
Updateable, ItemSelector will work without changes.
But we dont necessarily want to display all the objects of a particular typein the two uses
we have for ItemSelector so far, we only want tenants who arent already involved in a lease, and
properties that arent already owned by a landlord. This is where the second delegate is used. The
signature for ItemValidator takes an Updateable object, and returns a condition-valueeither
true or false. ItemSelector only adds the object to its list if the ItemValidator returns true.
Listing 10-7 is the OnShown() method from ItemSelector.
OnShown() is a method that ItemSelector inherits from the Form class. This method is invoked
whenever the form is first shown, so it is a good place to put the code to populate the list box. It
starts by invoking OnShown() in the superclassthis ensures that any code in the parent method
also gets run. Before we start updating the list box (member variable itemList), we invoke a method
Using the Windows Form UI 159
called BeginUpdate(), and after weve finished, we invoke EndUpdate(). BeginUpdate() stops the
list box from repainting as we add itemsotherwise, it would flicker if we added a lot of items at one
time. EndUpdate() reenables the normal list box behavior.
Between these method invocations is the code that updates the list box. There is a
perform varying loop, and the expression at the end of the perform statement is run enumerator.
The enumerator field contains the ItemIterator delegate passed in when the ItemSelector was
constructed, and run means invoke the delegate and return the result.
The next statement, if run isValid(nextItem), passes the Updateable record through to
the method encapsulated in the isValid delegate, which will return true or false. The item is only
added to the list if the result is true.
Thats how ItemSelector works. To add the code to NewLease to call it:
1. Open the design view for NewLease.
2. Double-click the Tenant button to add method tenantBtn_click(), then add the code
shown in Listing 10-8. The first statement declares an ItemIterator delegate and sets it
to the GetTenants() method of the AgencyStore. The ItemSelector will use this method
to retrieve all the tenants held by the AgencyStore.
The next statement declares an ItemValidator delegate, and sets it to an anonymous
method. This is a way of creating a delegate without having to name and write a sepa-
rate method. The code we want executed when the delegate is run is all defined inline
between the delegate and end-delegate headers. The anonymous method only has
two statements; the first casts the Updateable object passed in as an argument into a
Tenant. We know this is safe because we know that we are iterating over a list of Tenant
objects so that the underlying type will be a Tenant. The next statement sets the result,
isValid, to true only if the Tenant does not have a Lease.
Once we have created the delegates needed by the ItemSelector, we instantiate it and
display it with the ShowDialog() method. This creates the form as a modal dialog box.
ShowDialog() returns a result of type DialogResultthis has the value OK if the dialog
box was closed by someone clicking the OK button.
3. If the OK button was clicked, we store the selected item in the tenant field, which we
havent declared yet. Go to the working-storage section at the top of the NewLease
class, and add a new field:
4. Build and run the application again. This time, clicking the Tenant button pops up the
ItemSelector. Selecting a tenant populates the tenant field inside the NewLease class.
In the next section, we add the remaining code to create a Lease if we have all the
information needed.
160
invoke SetOkBtnState()
9. Go back to the design view, select the second DateTimePicker control, and set the Value
Changed event to dateTimePicker_ValueChanged. Both controls now have the same
event handler.
10. Now we can add the event handler to the OK button. Double-click the OK button to add
the event handler, and enter the code in Listing 10-10.
end method.
The code to create and store the new Lease is wrapped inside a trycatch block in case the
Lease class throws an exception. The catch block displays an error window. We dont have any
other validation inside here because the user can only click the OK button if weve already validated
the state of all the fields.
We also dont have any code in NewLease that informs the MainWindow that a new lease has
been successfully created, but if you run the application and create a new lease, the MainWindow
automatically changes its view from Rental Properties to Leases. This is done through events again.
We have added a new static event to the Lease, Tenant, Landlord, and RentalProperty classes.
The MainWindow constructor attaches a different event handler to the OnNew event for each of these
classes, and these event handlers are able to take the appropriate action when a new object is
created, without us having to write lots of extra code.
At this point, you should have added all the Windows Forms UI code to enable a user to create a
new lease. If you run the application as explained in the Using the Application section, you should
be able to follow the instructions to lease a property to a tenant.
Using the Java Swing UI 163
The WindowBuilder works only with Java code, so the UI will be written entirely in Java, and will
invoke the COBOL business logic.
This project also defines a single class, EventBridge (see Listing 10-11). Each instance of
EventBridge enables you to register an EventReceiver against the changeEvent for an Updateable
object, or to receive the static NewObjectCreated event emitted by the RentalProperty, Lease,
Tenant, or Landlord classes whenever a new object is created.
*> Register a receiver for NewObject events against the different agency
*> object classes.
method-id New(receiver as type EventReceiver) public.
set self::receiver to receiver
attach method HandleNewRecordEvent to type Landlord::NewObjectEvent
attach method HandleNewRecordEvent to type RentalProperty::NewObjectEvent
attach method HandleNewRecordEvent to type Tenant::NewObjectEvent
attach method HandleNewRecordEvent to type Lease::NewObjectEvent
end method.
end class.
166
The EventBridge class forwards a Visual COBOL event to an object, which implements
the EventReceiver interface. The event forwarding is done by the HandleChangeEvent() and
HandleNewRecordEvent() methods, which invoke methods on the stored EventReceiver. Most of
the code in EventBridge is actually concerned with keeping a record of which objects are registered
for which events in order to avoid registering the same event over and over again.
In Chapter 9, in the section Attaching the Event Handlers, we detached each event before
attaching it to avoid this problem. Because we are writing our own event forwarding code to work with
Java, we have to provide code to prevent duplicate attachments, and to enable us to detach an event.
The second constructor in the class registers an EventReceiver to get the events from an
Updateable object (the EventReceiver and Updateable are the constructor arguments). This
constructor is private , so that it can only be invoked from the static Register() method. The
Register() method keeps a record of receivers and event emitters (Updateable objects) in diction-
aries, so that registering the same receiver against the same object twice does not create another
EventBridge. There is also a static Unregister() method that enables you to detach from an event.
The MainWindow class registers itself against every object it adds to its list box, so that it can
update itself if any of the objects change. The code in the EventBridge means that MainWindow
does not get multiple notifications for a single change to an object.
Painting a Form
We are going to add a new form to the application to enable us to lease a property to a tenant.
Figure10-3 shows the form we are going to create. In the following steps, we are going to add the
form, the code to make it create a new lease, and also look at how the UI registers for events and
delegates:
1. Select the Java perspective in Eclipse.
2. In the JavaLeases project, right-click on the com.microfocus.javaleases package in
the Package Explorer, and click New > Other. Select JFrame from WindowBuilder/
Swing Designer (you can select it quickly by typing jframe into the filter field at the top of
the New dialog box). Then, click Next.
3. In the New JFrame Wizard, type NewLease and click Finish. This creates the NewLease
class, which inherits from JFrame.
4. Delete the main() method from NewLease. It is the first method in the class and starts
public static void main. This method enables you to run the JFrame as the starting
point of an application, but our application starts with the MainWindow class.
5. At the bottom of the text editor window for NewLease.java are Source and Design tabs
(see Figure 10-12). Click the Design tab to open the visual design view for the form.
Using the Java Swing UI 167
6. There are three panes to the left of the design surface. The biggest one is the Palette,
which enables you to pick the controls you want to put on the form. The Properties tab
enables you to set the properties of the currently selected control. Find the Title property
in the Properties pane and change the value to New Lease (with a space). This sets the
title of the form. Find the Type property (grouped under Window Style) and set the value
to UTILITY. This removes the System menu and Minimize/Maximize buttons from the
window frame, so it looks like the one shown in Figure 10-13.
168
7. Select the New Lease JFrame and drag the bottom-right corner to resize it to
approximately 520 by 470 pixels (the size is displayed as you drag).
8. Java Swing uses Layouts to help arrange the controls on a JFrame or JPanel. We have
used the Group Layout throughout the application as it is very flexible and easy to work
with if you are using a visual designer like WindowBuilder to generate code.
Right-click in the middle of the New Lease JPanel and click Set Layout > GroupLayout.
9. The address fields on the New Lease window are a single custom control,
AddressFields, which is also used for the New Property form. AddressFields should
already appear on the Palette under the group JavaLeases Custom (see Figure 10-14).
If it doesnt, you can add custom controls by right-clicking on the Palette, clicking Add
Component, and then clicking Choose on the Class Name field. Start typing the name
of the class you want to add as a custom component into the Open type dialog box.
Using the Java Swing UI 169
10. Click the AddressFields control on the Palette and then use the mouse to position it
near the top of the New Lease JFrame.
11. There is a toolbar at the top of the Properties pane (see Figure 10-15). The first icon (C)
displays the events for a control (these are handled through implementing event listener
interfaces as Java has no event mechanism like the one in Visual COBOL). The third
icon (green box and red diamond) is Convert local to field. Each control you add with the
painter is represented by a variable in the NewLease Java code. When you add a control,
it is defined as a local variable inside the constructor for the JFrame. Clicking the third
icon converts it to a field so you can access it from other methods in the class.
Click the icon (tooltip Convert local to field) to make addressFields a field of
NewLeasewe will need to access it from other methods.
170
Properties toolbar
12. Drag a JButton onto the design surface, and change the Text property to Tenant. You
can do this by typing on the button as soon as you have added it. Change the variable
property to btnTenant (this is the variable name).
13. Put a text box next to the Tenant button, and resize it to the same length as the address
fields. Rename it to tenantField. Click Convert to field.
14. Add a JLabel, and set the text to Lease Start.
15. Add a DateField control from the custom controls in the Palette. Change the variable
property to startDateField, and click Convert to field.
16. Add a Lease End label and second DateField, convert it to a field, and rename it to
endDateField.
17. Finally, add two more buttons at the bottom of the form, label them OK and Cancel, and
name them btnOk and btnCancel. Click OK and click the Convert to field icon.
18. Double-click on the Cancel button in the designer. Double-clicking a control creates an
event handler for the controls primary action. It also switches the editor from the design
view to the source view, positioning the cursor in the newly generated event handler.
The event handler is created as a Java anonymous class implementing the ActionLis-
tener interface. Add the statement parent.closeMe(NewLease.this) (see Listing 10-12).
Using the Java Swing UI 171
First, add three new fields to the NewLease class (the fields are all after the class header):
20. The AgencyStore and RentalProperty fields will initially show compile errors because
we havent imported their namespaces; if you click on each error line and press Ctrl+1,
Eclipse will suggest fixes. Select the fix to import the missing namespace.
21. Add a second constructor to NewLease above the existing one. Copy the code from
Listing 10-13. This constructor calls the default constructor (which is where all the
controls are added to the JFrame) and then stores the arguments in the fields we created
in the previous steps.
The New Lease form is now visually complete, and has a working Cancel button. Lets add the
code to the MainWindow class to display it.
172
Listing 10-15 Code to enable and disable the New Lease button
btnNewLease.setEnabled(rental.getLease() == null);
}
else
{
btnNewLease.setEnabled(false);
}
btnNew.setEnabled(viewState != ViewState.LEASES);
}
first statement invokes setVisible() in the superclass. This executes any code in the
parent class that should get run when the panel is displayed or hidden. The rest of the
method displays the address of the selected property in the address fields, and then sets
those fields to read-only.
3. Build the solution and check there are no compile errors. If you run the application again,
you should see the address of the selected rental property appear in the New Lease
dialog box.
Now that the NewLease class has access to the information it needs, we can start adding the
remaining functionality. First, we need to make sure that the OK button is disabled unless we have
all the information needed to create a lease.
return false;
}
3. This will give you a compiler error for the missing import for Tenant. Use Eclipse quick
fixto help you (Ctrl+1 on the line showing the error).
4. Add the two methods in Listing 10-17. You will need to import IDate to fix the compiler
error.
5. Go to the setVisible() method, and add the following statement at the end:
setOkBtnState();
6. We need to add event handlers that call setOkBtnState() when the values in the two
DateField controls change. Open the design view for NewLease, and select the field for
the Start Date.
7. Click the Show Properties icon (a small green C) on the Properties pane. Scroll down
to the Validation event, click it to expand, and then double-click Action. This adds an
event handler for the fields validation eventit will be called every time the contents of
the field changes. Enter the following statement in the actionPerformed() method of the
event handler:
setOkBtnState();
The list box on the ItemSelector is another custom component we created for our application,
called a RecordList. The RecordList simplifies using the Swing JList object and, in particular,
makes it very easy to construct lists of Updateable objects.
When you construct a RecordList, you have to pass in two argumentsan Iterable<?> and a
RecordValidator. These are both interfaces; Iterable<?> is part of the Java framework, and objects
that implement Iterable enable you to iterate over a collection of objects. Iterable is actually a
generic interface, but we are declaring it here with a ? for the generic type parameter, meaning it
can work with any type. We can use Iterable objects as the source of a for statement in Java (or
a perform varying ...through statement in Visual COBOL).
The RecordValidator interface is defined in the Agency Java Bridge project (see Listing 10-18).
In the Iterating Over Objects section in Chapter 9, we defined iterators to return RentalProp-
erty, Landlord, Tenant, and Lease objectsall these objects are of type Updateable as they all
implement the Updateable interface. The RecordList can populate itself by retrieving all the objects
for the list box using the Iterable<?> object it was constructed with.
But we dont necessarily want to display all the objects of a particular typein the two uses
we have for ItemSelector so far, we only want tenants who arent already involved in a lease, and
properties that arent already owned by a landlord. This is where the RecordValidator interface
is used. It defines a single method, Validate(), which takes an Updateable object and returns
a condition-value either true or false . RecordList only adds the object to its list if the
ItemValidator returns true. We could use RecordList for a type of object we define at some point
in the future, and as long as it is an Updateable, RecordList will work without changes.
Listing 10-19 is the populateListBox() method from RecordList.
Generics on the JVM are not as well implemented as they are in .NET, and suf-
fer from a problem known as type erasure, which means you dont always bene-
t from type safety. Ideally, we would specify that the iterator used by RecordList
had to return types of Updateable (Iterator<Updateable>). But the type erasure
problem means that the compiler cant match the return type of Iterator<Tenant>
to Iterator<Updateable>, which is why RecordList species a type of Iterator<?
extends Updateable>the compiler can match this.
When we click the Tenant button on the New Lease dialog box, we are going to construct and
display an Item Selector dialog box. The Iterable<?> and RecordValidator arguments needed
by the RecordList are passed into the ItemSelector constructor.
To add the code to display the Item Selector dialog box and get a Tenant object:
1. Open the code view for NewLease, and add a btnTenantClick() method at the bottom of
the class. The code is in Listing 10-20.
When calling the constructor for ItemSelector, we create an anonymous class that imple-
ments the RecordValidator interface. The Validate() method casts the object to a Ten-
ant, and only returns true if the Tenant does not have a lease.
We also add an ActionListener to the ItemSelector; this is another anonymous class
and the actionPerformed method is invoked if the OK button is clicked; it will retrieve
the selected item if the OK button was clicked, and set it as the selected tenant, and then
close the Item Selector dialog box.
The last statement in btnTenantClick() displays the Item Selector dialog box.
2. There will be some compiler errors after you have put in this method. You need to resolve
the missing imports for RecordValidator, ItemSelector, and Updateable. Use Eclipse
quick fix to help you (Ctrl+1 on the line showing the error).
3. Switch to the design view of LeaseView and double-click the Tenant button to add an
event handler.
Inside the actionPerformed() method of the event handler, add the statement:
btnTenantClick();
Now add the btnTenantClick() code (see Listing 10-20). This displays the Item Selector dialog
box and populates it with tenants who do not yet have leases.
}
});
tenantSelector.addActionListener(new ActionListener(){
@Override
public void actionPerformed(ActionEvent e)
{
if (e.getActionCommand().equalsIgnoreCase(OK))
{
tenant = (Tenant) tenantSelector.getSelected() ;
tenantField.setText( tenant.getEmail().getAddress());
}
tenantSelector.setVisible(false);
tenantSelector.dispose();
setOkBtnState();
}
});
tenantSelector.setVisible(true);
}
The btnTenantClick() method starts by creating an ItemSelector (the Tenant Selector dialog
box). The three arguments are:
The current JFrame (this is the parent of the modal dialog box)
The Iterable<?> object that will enable it to populate the list with tenants
An anonymous class implementation of RecordValidator that will only return true for Ten-
ant objects that do not have a lease
Once we have instantiated our ItemSelector (local variable tenantSelector), we add an
ActionListener (anonymous class again), which is fired if the OK button on the Item Selector dialog
box is clicked. The actionPerformed() method retrieves the selected tenant from the ItemSelector
and sets the tenant field from it, as well as putting the email address in the tenant field. It then closes
the Item Selector dialog box and runs the setOkBtnState() method, which enables the OK button
on the New Lease dialog box if we are able to create a lease yet.
If you run the application now, the New Lease dialog box is almost fully functional. You can select
a tenant, and the OK button is only enabled when you have enough information to create a lease.
But clicking OK doesnt do anything yet. Well add the Lease creation code in the next section.
a setOkBtnState() that uses this method to set the state of the OK button. To add the code that
creates the lease, follow these steps:
1. Open the code view for NewLease.
2. Enter the code in Listing 10-21. As always, fix any compiler errors due to missing imports.
There are Lease types in two different namespacesmake sure you import MicroFocus.
COBOL.Examples.Lettings.Lease.
3. Open the design view for NewLease, and double-click the OK button to add an event
handler. Insert the statement below into the actionPerformed() method of the event
handler:
btnOkClick();
4. Run the application. You should now be able to create a lease, and it will appear in the
Leases view of the Main Window.
JOptionPane.showMessageDialog(this,
errMsg,
Messages.ADD_RENTAL_MSG_TITLE,
JOptionPane.ERROR_MESSAGE);
}
}
}
}
The code to create and store the new Lease in Listing 10-21 is wrapped inside a try...catch
block in case the Lease class throws an exception. The catch block displays an error dialog box.
We dont have any other validation inside here because the user can only click the OK button if
weve already validated the state of all the fields.
We also dont have any code in NewLease that informs the MainWindow that a new lease has
been successfully created, but if you run the application and create a new lease, the MainWindow
automatically changes its view from Rental Properties to Leases. This is done through events again.
We have added a new static event to the Lease, Tenant, Landlord, and RentalProperty classes.
The MainWindow constructor uses the AgencyEventBridge class to attach its NewRecord() method
to the NewObjectEvent event for each of these classes. The NewRecord() method then updates
the current view if needed.
At this point, you should have added all the Java Swing UI code to enable a user to create a
new lease.
Summary
In this chapter, we looked at how in .NET we can create a desktop UI using Visual COBOL and the
Visual Studio Windows Forms Designer. For users who are compiling to JVM, we created a desktop
UI using Java Swing, which was able to use all our Rental Agency business and storage logic. For
JVM, we also looked at a technique for forwarding events from Visual COBOL to Java. The next
chapter shows you how to build a web-based version of the same application, showing you how to
run Visual COBOL under the control of a .NET or Java Web server.
C H A P T E R 11
181
182
Our single page client will use JavaScript to GET data from our REST services and present it
on the page to the user. It will also be able to POST data back to the services to add new records.
There is a lot of material to get through in this chapter, so we are only going to create a subset of
Rental Agency functionality in our web application:
Display rental properties, leases, tenants, and landlords
Add landlords and tenants
This should illustrate all the points we want to explain.
What Is REST?
REST stands for REpresentational State Transfer. Each resource you want to access has its own
URL. For example, in the Rental Agency, tenants are at http://agency/service/tenants and landlords
are at http://agency/service/landlords (agency is the root URL for the applicationif you are running
under Eclipse, the full URL for tenants is http://localhost:8080/JavaWebLeases/service/tenants).
The different HTTP verbs (POST, GET, UPDATE, DELETE) correspond to the CRUD (Create,
Read, Update, Delete) operation you want to perform. You put the id of the record you want to ac-
cess on the URL path to indicate which record you want. For example, in the case of a Tenant or
Landlord, the id is the email. So if I perform a GET on http://agency/service/tenants/m.fender@
example.com, the service will return the record for that tenant. Doing a GET on http://agency/service/
tenants with no id on the end returns an array of all the tenants.
You can see this in action very easily if you run the web application as explained in the previous
sections. An end user accesses the application through the web page (index.html or index.cshtml
depending on whether you are running on JVM or .NET), but this web page is calling the REST
services. You can call these services directly, though.
A web browser performs an HTTP GET on any URL you put in the address bar. If you are on
JVM, enter this URL:
http://localhost:8080/JavaWebLeases/service/tenants
If you are on .NET, enter this URL (the port number might be different on your computerit will be
the one shown in your web browser address bar when you ran the application from Visual Studio):
http://localhost:2858/service/tenants
This fetches all the tenant records for the application, in a text format known as JavaScript
Object Notation (JSON). The Firefox or Chrome browsers show the JSON in the browser window,
whereas Internet Explorer gives you a choice between downloading it as a file or opening it in another
application. The JSON looks like this:
This JSON consists of an array wrapped inside an object called list. The array is inside the
square parentheses and consists of a set of objects. Each object is inside curly braces and consists
of a set of name-value pairsthe name appears on the left side of each colon, and the value appears
on the right side. Each pair is delimited by a comma. JSON is a very simple format that is easily
readable by machines, and which can also be understood by humans. It can be hard to follow when
it is all on one line, but you can cut and paste it into a website like JSON Lint (just search for json
lint on the web) and get it reformatted into something easier to read.
186
NetWebLeases JavaWebLeases
AgencyStore
AgencyDto
FileStorage
RentalAgency
ExamplesSupport
The web version of the Rental Agency is a set of REST services as our back end, with a single
page that calls those REST services to retrieve or update information on the web server. This
architecture completely decouples the logic for accessing the data and business rules from the
presentation layer that the user sees. It also enables other services to become clients to the Rental
Agency. For example, if you wanted Rental Agency to be able to participate in a comparison system
that enabled end users to look at properties across several different companies, providing a REST
interface for accessing your data is a good place to start.
Figure 11-5 shows the basic architecture of the application. Four controller classes implement
the REST services for each of the four record types. The controllers are implemented in C# on
.NET, and in Java on JVM, and call into the Visual COBOL code, which implements the business
logic and storage. Visual Studio and Eclipse both provide tooling and support for creating this type
of web application that makes it easier to use C# or Java to implement the web service front end.
We are exploiting the ease of interoperation between these languages and Visual COBOL to call
our back end Rental Agency functions.
The user interface is provided by a single web page, index.html on JVM and index.cshtml on .NET.
Although the page is hosted on the application server along with the rest of the application, it is
actually run by the end users web browser, which is why Figure 11-5 shows it outside the applica-
tion server. An application server is a web server that hosts active contentso it isnt just serving
up static assets like pages and images, but is also running some actual code.
.NET applications usually run in the Microsoft IIS server. JVM applications can run on any of
a wide range of servers, but we have chosen Apache Tomcat for our example as it is easier to
configure and run than more fully featured application servers (for example, IBMs Websphere).
However, you could run the Rental Agency application on a different Java server without changing
the code, as long as that server had the JAX WS RS support defined as part of the Java standard
for implementing REST web services. This is further explained in the Implementing the JVM REST
Service section later in this chapter.
188
App Server
TenantsController AgencyText
LandlordsController AgencyStore
PropertiesController FileStorage
LeasesController RentalAgency
ExamplesSupport
AgencyDto
WebPage
TenantDto objects, and the web server framework automatically serializes them into JSON format
for you. The diagram doesnt show it, but each request constructs a new TenantsController ob-
ject, so controllers must be lightweight to construct. It also means that you cant store any state in
them between requests.
HTTP is an inherently stateless protocol; if you need to maintain state on the server, you have to
maintain a separate session object for each end user and associate each request with the appropriate
session object. Java and .NET web application frameworks both provide straightforward ways of
doing this. However, in our simple application, all state is held inside the end users browser, and
one of the great virtues of the REST pattern is that it does not need any server-side state.
Requests TenantsController
[JSON Array]
Redirect to tenants/fred@example.com
TenantDto GetTenant(id)
Response PutTenant(form)
The box on the left-hand side of the diagram is a new object for every web request, but we dont
necessarily want to create a new AgencyStore every time the application server creates a new
controller. When we create the AgencyStore, we have to load our cache with the data, which is an
expensive operation.
The simplest solution is to make some small changes to AgencyStore. We will create a static
GetStore() method that returns an instance of AgencyStore, and make the constructor private so
that the only way to get an AgencyStore instance is through the GetStore() method. Our control-
lers can all invoke GetStore() to fetch an AgencyStore. Our implementation of GetStore() always
returns the same instance of GetStore(), so we incur the costs of creating the AgencyStore only
once no matter how many requests the application server is handling. Listing 11-1 shows GetStore()
and a revised constructor.
create landlordDictionary
create tenantDictionary
Introducing the Web Application 191
create rentalDictionary
create leaseDictionary
end method.
The GetStore() method has a sync modifier. This marks the method as a critical sectiona
piece of code that can only be executed on one thread at a time. If one thread is already executing
this method, a second thread has to wait till the first one exits the method. This prevents us from
creating two AgencyStores if we get two request threads running at the same time. Multiple request
threads can occur even if only one user is connected, as the page that displays our data makes
multiple HTTP GET requests to fetch all the data.
There is some initialization code in the constructor to set the filenames for each file, which we
will cover in the Configuring the Application section. The other significant thing that we do with
this constructor is to create a RunUnit, and add all the file programs to it. This is good practice
with any legacy code that will be run from multiple threads, but it is even more important if we start
rearchitecting the application further, as discussed in the Scalability Considerations section.
program-id. PersonFile.
file-control.
select PersonFile assign to dynamic fullPathName
file status is file-status
192
organization is indexed
access mode is dynamic
record key is filerec-email
.
*> ...
linkage section.
01 lnk-status pic xx.
01 lnk-filename pic x(260).
*> ...
The changes are shown in bold, and ellipses indicate where we have omitted code. The
file-control paragraph now assigns PersonFile to dynamic rather than to external as it did
before. Dynamic assignment enables you to specify the full path and filename in a variable, in this case
called fullPathName. You can declare the variable explicitly in working-storage, or if undeclared,
the compiler automatically creates a working-storage variable for you as a pic x(260).
We are relying on the compiler to create the working-storage variable for us, but because we are
going to pass the value into the program, we need to declare a linkage variable (lnk-filename) of
the right size. The variable is set by calling the SetFileName entry point, which we have placed at
the end of the program. We have shown the code for this entry point in Listing 11-2, but because
exactly the same code is required in all three file programs, we have actually put it inside a copybook
called filename.cpy.
Now that we have a way of setting the filename for each program, we need the mechanism to call
the SetFileName entry point for each program. The logical place to do this is in the AgencyStore
constructor, but this implies that AgencyStore has the configuration information.
The .NET and JVM web applications both use their own native mechanisms to read the con-
figuration file, and then pass the properties in it to the AgencyStore, by invoking the static method
SetFilenames(). Listing 11-3 shows in bold the parts of AgencyStore that configure the code.
*> ...
*> ...
method-id New.
if filenames = null
raise new PersistenceException(Filenames not configured)
end-if
*> ...
end method.
The SetFilenames method takes a dictionary of key-value pairsthe values are the full pathnames
to the data files. The keys are all defined as public read-only strings so that they can be shared with
the code in the web application that creates the dictionary. Because the Rental Agency application
cant function at all if the filenames arent set, AgencyStore throws an exception under either of the
following circumstances:
The dictionary does not contain all the expected values.
The constructor is called before the dictionary has been passed in.
Separating the DTOs from the business logic enables us to change the representation of
data without touching the core of the application.
The Java serializer forces the first letter of each property to lowercase (in accordance with
Java conventions). Our DTO objects have lowercase properties throughout so that the JSON
serialization is the same on both platforms, enabling us to use the same JavaScript to display
the results on both platforms. This last item is not likely to be a consideration for most web
applications, which will be written for either Java or .NETnot both.
Listing 11-4 shows the LeaseDto class, which consists of a constructor and a set of properties.
The other DTO classes follow the same pattern.
class-id MicroFocus.COBOL.Examples.LeaseDto.
01 #start string property public.
01 #end string property public.
01 rentalProperty type AddressDto property public.
01 cancelled condition-value property public.
01 tenant string.
01 #id string.
Scalability Considerations
Although we have a very basic implementation of the Rental Agency working as a web application in
this chapter, this implementation has a number of issues that would need to be corrected to enable
it to scale up. There are several levels of scalability for web applications depending on how many
simultaneous users you need to be able to service. The best designs for web applications enable
them to autoscale, increasing the number of resources for the application as demand goes up, and
decreasing them again as demand goes down. Demand management of resources is particularly
useful if you are deploying your application in a cloud environment like Amazon Web Services or
Microsoft Azure.
This section explains the issues with the current implementation of the Rental Agency, and sug-
gests the kind of changes you would need to make to improve things. The intention is to give you a
better understanding of web applications and how to architect them.
Introducing the Web Application 195
The first issue with our web application is that we have not made it completely thread safe. As
we explained in the Principles of the REST Service Controllers section, requests can come in on
more than one thread, and even a single refresh of our web page will make more than one request.
The two areas where you might see contention between threads are:
Writing records: If two different threads attempt to access the same file at the same time,
the first one to start will get a file lock until the operation is complete. You need to change
the file access programs to check for locks and retry an operation if a file is locked.
Iterating over the dictionaries at the same time as a write operation: Methods in AgencyStore
like GetTenants() use an iterator to return all the tenants in the collection. If you add or re-
move an element from a collection while iterating over it, the iterator will throw an exception.
One way to mitigate this would be to clone the dictionary values into a new collection (clon-
ing a collection does not mean cloning every object, just copying the values) and returning
that. One of the reasons Rental Agency does not take that approach at the moment is that
at the time of writing you need to use JVM- or .NET-specific code to clone a collection. We
would not recommend the alternative approach of using a mutex to block write access to
the collections when they are being iterated over. That could cause performance issues as
any write threads would be held up for potentially long periods.
Once youve addressed the current threading issues in the application, you have improved the
reliability of the application across threads. However, you could improve its scalability by changing the
current singleton implementation of GetAgencyStore() to something that creates one AgencyStore
per request thread. Most application servers allocate threads to requests from a thread poolthey
dont generally create and destroy threads on the fly as these are relatively expensive operations.
Instead, a request is run on a thread allocated from a thread pool. When the request completes,
the thread is returned to the thread pool and idles until it is required for another request (see
Figure 11-8).
The thread pool will allocate new threads up to some limit determined by the application server
(either on the basis of configuration or based on the hardware available). As we stated earlier, you
dont want to create an AgencyStore per request as loading the records is expensive.
AgencyStore
Thread2
AgencyStore
Thread3
But creating one AgencyStore per thread and recycling them removes the problem of multithread
access to dictionaries and will probably give you better performance. The procedural file access
programs need to be encapsulated inside run-unitsthe constructor for the AgencyStore (Listing
11-1 The AgencyStore GetStore() and revised constructor methods) already does this. You will
still need to deal with file locking issues, though, and you have an additional problem: When you
save or update a new record in one AgencyStore, you will need to update the caches in the other
AgencyStores. Youll need to differentiate between the event for an object has changed, I need
to save it again and an object has been changed on a different thread, I need to update my copy,
but I dont need to save it again.
Enabling multiple copies of the AgencyStore enables your application to scale better while
running on a single web server. However, some web applications have to run across multiple web
servers and then we start to run into a new set of problems. Until you are into really large-scale
applications, you probably still want to have only one process accessing your data files, but you
might want to be able to run the cached data across multiple web servers. Because most applica-
tions do more reads (which come from the cache) than writes, this would enable significant scaling
up. However, you now have the issue of keeping AgencyStore caches on different machines up to
date. Visual COBOL events arent a suitable mechanism for these notifications as they only work
inside a single process, not across multiple processes on multiple machines. For an application to
scale at this sort of level, youll need some kind of broadcast mechanism to notify multiple listeners
that they need to update themselves. Message queueing middleware is a common solution to this
sort of problem (see Figure 11-9).
Web Server
Web Server
only when the application starts. In NetWebLeases, we have modified this to call a new method called
SetFilenames(). Listing 11-5 shows Global.asax.cs with the SetFilenames() method and the state-
ment to invoke it at the end of the Application_Start() method. The new code is shown in bold.
namespace WebLeases
{
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
SetFilenames();
}
The SetFilenames() method creates a dictionary of strings, adds the location of each file (the
WebConfigurationManager class has a static AppSettings indexer that enables you to read the
settings from Web.config), and then invokes the AgencyStore.SetFilenames() method, which is
shown in Listing 11-3.
Open RouteConfig.cs under the App_Start folder. This class maps URLs to controllers. There are
two routes mapped in RouteConfig. Listing 11-6 shows the new route we added for NetWebLeases
in bold.
namespace WebLeases
{
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute({resource}.axd/{*pathInfo});
routes.MapRoute(
name: service,
url: service/{controller}/{id},
defaults: new { controller = Service, action = Index,
id = UrlParameter.Optional });
routes.MapRoute(
name: Default,
url: {controller}/{action}/{id},
defaults: new { controller = Home, action = Index,
id = UrlParameter.Optional }
);
}
}
}
The route at the bottom of the listing is a route added by default to all MVC applications. This
default route maps URLs at the root of the website to controllers that match the root path. You can
see the URL is decomposed into controller (a class), action (a method), and id (optional parameter).
The HomeController is the default controller for this path, and the default action is Index. This means
that these two URLs are equivalent, and they both invoke the Index() method on the HomeCon-
troller class:
http://agency/Home/Index
http://agency
The ASP MVC routing rules automatically append Controller to the controller portion of a path
when looking for a matching class. Listing 11-7 shows the Index() method of the HomeController. It
returns the result of the View() method (HomeController inherits from a class called System.Web.
Mvc.Controller, which implements this method).
200
The View() method looks for an Active Server Page in the Views folder of the project. It looks in
the folder with the same name as the controller (Home), and returns a page matching the method
name (Index). If you expand the Views\Home folder in the NetWebLeases project, there is a file
called Index.cshtml, and this is the server page that will be rendered as HTML and returned to the
user when they navigate to the root URL of the website.
Active Server Pages usually have some active content, which can be altered by your server
code before it is sent to the user, but our application just uses HTML and JavaScript and has no
content set by the server. This enables us to use an almost identical page on both the .NET and
JVM implementations of this application.
So the upshot of the default routing is that if users go to the root URL of our website, they will
get the contents of Index.cshtml rendered as HTML in their web browser.
However, Index.cshtml will itself make HTTP requests to the URLs representing the web services.
These URLs all have /service/ in the path. These are the paths matched by the routing rule shown
in bold in Listing 11-6. So:
http://agency/service/tenants
http://agency/service/tenants/m.fender@example.com
namespace WebLeases.Controllers
{
public class TenantsController : Controller
{
public AgencyStore Store
{
get
{
return AgencyStore.GetStore();
}
}
Implementing the .NET REST Service 201
// GET: Tenants
[HttpGet]
public ActionResult Index(String id)
{
if (id != null)
{
string tenantEmail = Server.UrlDecode(id) ;
var t = Store.FindTenant(tenantEmail);
if (t != null)
{
TenantDto dto = new TenantDto(t);
return Json(dto, JsonRequestBehavior.AllowGet);
}
else
{
Response.StatusCode = 404;
return Json(null, JsonRequestBehavior.AllowGet);
}
}
else
{
var listTenants = new List<TenantDto>();
foreach (Tenant t in Store.GetTenants())
{
listTenants.Add(new TenantDto(t));
}
// Never return an unwrapped json array - security issue
return Json(new { list = listTenants },
JsonRequestBehavior.AllowGet);
}
}
[HttpPost]
public ActionResult Index(FormCollection form)
{
string emailAddress = form.Get(email).TrimSpaces();
string name = form.Get(name).TrimSpaces();
try
{
var email = new Email(emailAddress);
if (Store.FindTenant(emailAddress) != null)
{
Response.StatusCode = 409;
Response.StatusDescription = Messages.DUPLICATE_RECORD;
202
return Content(String.Empty);
}
else
{
var t = new Tenant(name, emailAddress);
Store.Save(t);
Response.StatusCode = 201;
return RedirectToAction(Index, Tenants,
new {id = emailAddress });
}
}
catch (PersistenceException e)
{
Response.StatusCode = 400;
Response.StatusDescription = e.Message;
return Content(String.Empty);
}
}
}
}
The Index() method in this class is overloaded. One implementation has a string parameter, id,
and the other has a FormCollection parameter, form. Youll notice that the two methods are deco-
rated with different custom attributes. The first is marked as [HttpGet] and the other as [HttpPost].
This means that if the URL http://agency/service/tenants is requested with the HTTP GET verb, it is
routed to the first Index() method, and if it is requested with the POST verb, it is routed to the sec-
ond method, enabling us to handle the different requirements of GET, which should retrieve records,
and POST, which should create a new record. If we wanted to handle the UPDATE and DELETE
verbs, we would need to decorate the first Index() method with these attributes, too, and include
code to find out which verb had been used and act accordingly. We couldnt create new Index()
methods for these verbs as they would all take the same parametera single stringand you cant
have multiple implementations of a method with the same name and signature in a single class.
The first Index() method checks for the presence of the id parameterif it is there, we are
being asked to return the record with the matching id (in the case of a tenant or landlord, this is the
email address). If the id parameter is present, we look up the record in the AgencyStore and return
the record if it exists, or send back a 404 status (not found) if it does not. If the id parameter is not
there, we return the collection of records.
Note that this method returns either a single TenantDto, or a collection of TenantDto objects.
The Json() method serializes the TenantDto into JSON for us. This method returns a JSON object,
which contains name-value pairs for all the public fields or properties of any object passed to it.
The second Index() method is invoked when the URL is invoked with the POST verb. Our single
page application does this when you click OK in the dialog box for adding a new Tenant. The data
for the new record is passed in the body of the HTTP request as name-value pairs, and is passed
into the Index() method as a FormCollection, which contains those name-value pairs.
Implementing the JVM REST Service 203
<project xmlns=http://maven.apache.org/POM/4.0.0
xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance
xsi:schemaLocation=http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd>
<modelVersion>4.0.0</modelVersion>
<groupId>com.microfocus.book</groupId>
<artifactId>JavaWebLeases</artifactId>
<version>1.0</version>
<packaging>war</packaging>
<name>playtime</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet</artifactId>
<version>2.22.1</version>
</dependency>
<!-- Required only when you are using JAX-RS Client -->
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-client</artifactId>
<version>2.22.1</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-client</artifactId>
<version>2.22.1</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.22.1</version>
</dependency>
</dependencies>
</project>
Running the following Maven command copies all the binary dependencies into the target/
dependencies directory of your project. You will need to install Maven on your machine if you want
to do this yourself, although you dont need it to run our downloaded example. The Maven website
Implementing the JVM REST Service 205
is http://maven.apache.org/ at the time of writing, but you can find it by doing a web search for
apache maven.
mvn dependency:copy-dependencies
Using Maven to download the dependencies is much easier than trying to work out the
dependencies and download them yourself, but we cant use an actual Maven project to build
JavaWebLeases as Visual COBOL does not support Maven projects at the time of writing, making
it difficult to express a COBOL project as a dependency to Maven.
Expand the folders for JavaWebLeases in the Package Explorer to see the folder structure (see
Figure 11-11). The WebContent folders contain all the files required for the website. The Content
folder contains the static content like CSS files. The Scripts folder contains all the JavaScript files.
The WEB-INF folder contains a lib directorythis is where we copied all the jar files needed by
Jersey. The web.xml file in this directory contains some application configuration information for the
web serverwell look at this in the next section.
The external dependencies are in the WebContent/WEB-INF/lib folder, but we also need to
reference all the internal dependencies (the other projects needed). This is done slightly differently
for a web project. Right-click on the JavaWebLeases project and click Properties. There is an entry
called Deployment Assembly near the top of the list of the Properties dialog box, which defines all the
other binaries that need to be deployed along with this project. Weve added project dependencies
for all the COBOL projects that make up the Rental Agency as they all need to be deployed to the
web server. Weve also added the COBOL JVM Runtime System (this is a Java Build Path entry).
JavaWebLeases folders
Now that weve looked at how we set up the JavaWebLeases project, in the next section we look
at how we configured the application.
Implementing the JVM REST Service 207
<servlet>
<servlet-name>jersey-servlet</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer
</servlet-class>
<init-param>
<param-name>jersey.config.server.provider.packages</param-name>
<param-value>com.microfocus.examples</param-value>
</init-param>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>
com.microfocus.examples.javawebleases.application.WebLeases
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>jersey-servlet</servlet-name>
<url-pattern>/service/*</url-pattern>
</servlet-mapping>
</web-app>
208
There are two further pieces of configuration included in our servlet definition (the two
init-param elements):
The jersey.config.server.provider.packages parameter tells Jersey to scan all the
Java classes in the application for those in the com.microfocus.examples package. These
classes will be used as the controllers for the application.
Our application has a startup class called com.microfocus.examples.javawebleases.
application.WebLeases.
The WebLeases class extends a class called javax.ws.rs.core.Application (part of the JAX-RS
specification) and will get loaded and run at execution startup. We could also use WebLeases to
specify our controller classes explicitly as an alternative to having Jersey scan for them.
The WebLeases class is where we can load our application-specific configuration, the paths and
names of the data files (see the section Configuring the Application earlier in this chapter). The
getClasses() method of this class is called at application startup, and is usually used to return
a list of all the controller classes for the application. Our getClasses() method returns an empty
list because we are using package scanning as described above, but it also loads a configuration
file called webleases.properties, and uses this to configure the AgencyStore for the application.
You can see Webleases.java and webleases.properties under the src folder of JavaWebLeases.
Listing 11-11 shows the WebLeases class (minus the import statements at the start).
// Read resources
Implementing the JVM REST Service 209
The WebLeases class uses the Java class loader to open the webleases.properties file. The class
loader has methods to open resources (like the one used here, getResourceAsStream()), which
search for named files on the current class path. This means you can bundle things like configuration
files along with the .class files for your application, and the class loader can find them without needing
any further information beyond the filename. When the Java project is built, webleases.properties
in the src folder gets copied into the output folder along with all the .class files for the application.
The properties file itself is just a text file with a list of name-value pairs that we can unpick using
the java.util.Properties class.
All these classes extend the AbstractController class, which simply provides a method for
retrieving the AgencyStore object.
210
@Path(/tenants)
public class TenantsController extends AbstractController
{
@Context
UriInfo uri ;
@GET
@Produces(MediaType.APPLICATION_JSON)
public JsonArrayHolder<List<TenantDto>> getTenants()
{
List<TenantDto> serializableTenants = new ArrayList<TenantDto>();
for (Tenant t : getStore().GetTenants())
{
serializableTenants.add(new TenantDto(t));
}
return new JsonArrayHolder<List<TenantDto>> (serializableTenants);
}
@GET
@Path({id})
@Produces(MediaType.APPLICATION_JSON)
public TenantDto getTenant(@PathParam(id) String id)
throws UnsupportedEncodingException
{
String tenantEmail = java.net.URLDecoder.decode(id, UTF-8);
Tenant t = getStore().FindTenant(tenantEmail);
TenantDto dto = t == null ? null : new TenantDto(t);
return dto;
}
@POST
@Path({id})
@Produces(MediaType.APPLICATION_JSON)
public TenantDto getTenantFromPost(@PathParam(id) String id)
throws UnsupportedEncodingException
{
return getTenant(id);
}
Implementing the JVM REST Service 211
@POST
public Response Index(@FormParam(email) String emailAddress,
@FormParam(name) String name)
{
emailAddress = emailAddress.trim();
name = name.trim();
The TenantsController class is decorated with the @PATH annotation, which specifies that this
class is to be used for requests that start with the subpath tenants, that is, any path that starts:
http://localhost:8080/JavaWebLeases/service/tenants
Each method is further decorated with either the @GET or @POST annotations (which specify the
verb to map from the HTTP request, as well as the @PATH and @Produces annotations. The @PATH
annotation specifies the next segment of the expected path, with parameters defined by curly braces.
If you look at the getTenant() method, the next path segment is an {id}, and the method signa-
ture includes the annotation @PathParam(id), which puts the id on the path into the id argument
of the method. So this URL:
http://localhost:8080/JavaWebLeases/service/tenants/fred@example.com
JSON, but you can also specify other serialization types like XML. You can also specify a number
of different types, and the container servlet will pick the serialization type based on the information
in the request header from the client.
Summary
In this chapter, we have taken a short look at how to deploy a COBOL application as a REST
service on either IIS or a Java web server. Web application development is a large topic, so we have
attempted to provide an overview of one single style of web service development (REST), while
providing information to help you research and understand the topic further.
This concludes our work with the RentalAgency application. The remaining chapters in this book
are reference material. The next chapter takes another look at the COBOL Compiler and how to build
your application without using Visual Studio or Eclipse, and the chapters after that provide formal
syntax definitions for the Visual COBOL language features that are the subject of this book. For
syntax definitions for the entire COBOL Language, consult the Micro Focus product documentation.
C H A P T E R 12
Program Structure
and Compilation
This chapter formally describes the format for COBOL source code and the relationship between
source files and compiled artifacts. In this chapter, youll learn about:
The Visual COBOL Compiler
Source file formats
Managing dependencies
213
214
Each main source file can contain one or more type definitions, although by convention, it usually
contains only one main type definition (which may have nested types defined within it). You cannot
usually split a program or type definition across separate compilation source units, the exception
being partial classes. A class-id header and its corresponding endclass must appear in the same
main source file.
ClassA.cbl
Program1.class
class-id ClassA.
ClassA.dll
end class.
ClassA.class
Program1.cbl MoreClasses.cbl
class-id ClassZ.
ClassZ.class
copy1.cpy end class.
class-id ClassX.
copy2.cpy
end class.
ClassX.class
The compiler takes one source compilation unit from the source file specified on the compiler
command line, and any others must be specified using ilsource directives (one directive for each
source compilation unit).
or
A command line on UNIX for the sources in Figure 12-1 Compilation input and output looks
like this:
or alternatively, you can use the -j option as a shortcut for the jvmgen directive:
Source Formats
This section looks at the format for source code inside a source file. The first COBOL programs
were transcribed onto punched cards, which were then read into the computer. Punched cards
have a finite length, which meant that the maximum length of a line in COBOL was 72 characters.
The source line was divided into three areas:
Sequence numbercolumns 1 to 6: This is reserved for line numbers, and is usually ignored
by the compiler.
216
Indicator areacolumn 7: An asterisk in the indicator area means the line is a comment. A
hyphen is a continuation character for literals. A $ indicates a compiler directive.
Code area Acolumns 872: Source code.
The Visual COBOL compiler supports this format (known as fixed), and two others: variable and
free. The default source format for Visual COBOL when compiling managed code (JVM or .NET)
is variable. The rules for source format variable are:
Sequence number: columns 1 to 6
Indicator area: column 7
Source code from column 8 up to a line length of 256 bytes
The rules for source format free are:
No indicator area
Column 1 is treated as the indicator area for a single line if it contains the special character
* (comment) followed by a space
Source code from column 1 up to a line length of 256 bytes
Line length
The maximum line length for free and variable source formats is 256 bytes. However,
because COBOL accepts UTF-8, some characters can occupy more than one byte, so the
line length might be less than 256 characters. Visual COBOL can also compile files that
use double-byte character sets (for example, older Kanji source code is often DBCS).
The source format is set by the sourceformat compiler directive, and can be set as part of the
compiler command line, or by using $set sourceformat(format) anywhere inside a source file,
where format is variable, free, or fixed. The default for managed code is source format variable,
so to change it to free from inside the source file, add the directive $set sourceformat(free). You
can change the source format as many times as you want inside a source file, although we wouldnt
advise it as it is likely to confuse anyone else looking at your code.
Comments
There are two ways of marking a comment in COBOL. You can put an asterisk * in the indicator area
(column 7 or 1 depending on source format) to indicate that this line is a comment line. You can
also use the combination *> anywhere on the line after the indicator area, to indicate that the text
following till the end of the line is a comment. Listing 12-1 shows some comments in different formats.
Compiler Output
As stated earlier in this chapter, Visual COBOL outputs class files on the JVM and assemblies on
.NET. The next two sections look at this in a little more detail.
Class Files
On the JVM platform, the compiler generates one .class file per type definition in the compilation
groupso if you define several classes in one source unit, there will be more class files than there
are source units. Nested types are also generated as separate files. For example, the source file in
Listing 12-2 generates these class files:
A.class
A$B.class
See the Nested Types section in Chapter 13 for more information about nesting.
class-id A public.
class-id B.
end class.
end class.
Visual COBOL also has to follow Java conventions for namespacing, where a namespace forms
the path to a class file. For example, all the classes defined in the Rental Agency project used as an
example earlier in this book have the namespace MicroFocus.COBOL.Examples. The build output
directory is set as bin, but all the classes for the project are generated under bin\MicroFocus\
COBOL\Examples.
The .class files are not natively executable on UNIX or Windows but must be run with the java
command. At its simplest, the java command looks like this:
java <mainclass>
where <mainclass> is the class containing the main entry point for the application. The java
command starts execution from the method with this signature in the mainclass:
However, you can specify any public static method as the main method with the ilmain directive.
The ilmain directive takes a single argument, the name of a public static method. This method must
appear in one of the classes in the compilation group, and the compiler will generate a main()
method with the expected signature in the same class, which invokes your specified main method.
If you do not specify a main method with ilmain, the COBOL compiler will pick a public static main
method and make that the main method.
If you have a method anywhere in your compilation group with exactly the method name and
signature expected by Java, and omit the ilmain directive, no extra code is generated.
218
Assemblies
An assembly on .NET is either a Windows executable (.exe file) or Windows dynamic link library
(.dll file). It contains executable code for all the types defined in all the source units specified on the
compiler command line. Windows executables can be built either as console applications (the default)
or as Windows applications. A Wiindows application does not have a console for input and output.
The Visual COBOL compiler builds an assembly by first generating intermediate language (.il)
files. Intermediate Language is an assembler language for the abstract machine represented by the
Common Language Runtime (CLR)see the JVM and CLR for COBOL Programmers section in
Chapter 4. It then invokes the Intermediate Language Assembler (ilasm), which assembles the
.il files and generates the executable. The ilasm utility is supplied by Microsoft as part of the .NET
platform. The Visual COBOL compiler deletes the .il files after compilation is complete.
By default, the compiler will attempt to build a console executable. If none of the classes in the
compilation group has a public static method, the build fails with an error message. You must have
a static main method as the initial entry point for the application when you run the executable. If
there is more than one static main method, select the one you want as the main entry point using
the ilmain directive.
The ilmain directive requires a single parameter, the name of the method. If you have multiple
static public methods with the same name in different classes, you cant guarantee which one will
be picked as the main entry point at build time. By convention, most applications have a static public
method called Main() with this signature as the main entry point:
The argument is a string array, which will contain all the parameters passed on the command
line when the application was started.
If you want your executable built as a Windows application (no console), include this compiler
directive:
ilsubsystem2
A .dll is loaded by an application once it starts running and does not require a main entry point.
There is more information about libraries in the section References on .NET later in this chapter.
Dependencies
Most applications will consist of the main application and one or more dependencies. These de-
pendencies might be other projects you have created yourself, or they might be from third parties.
The dependencies have to be available to the Visual COBOL compiler when you build your project,
and to the application at run time. Figure 12-2 shows an application with three projects and their
dependencies. The Leases project depends on both RentalAgency and ExamplesSupport, and
RentalAgency depends on ExamplesSupport (this sample application and diagram were introduced
in Chapter 3).
Leases
RentalAgency
ExamplesSupport
On a managed platform, all the compile time and run-time dependency information is included
in the binary files generated at build time, so these projects must be built in this order:
1. ExamplesSupport
2. RentalAgency
3. Leases
The main entry point for the application is Leases in the Leases project, and at run time, it will
need access to the built versions of RentalAgency and ExamplesSupport. The next two sections
explain the different mechanisms for dependencies on the JVM and .NET platforms. We will do this
using a simple example with two different projects, each of which contains a single source file (A.cbl
and B.cbl, respectively). The two source files are shown in Listing 12-3.
Java Classpath
Java uses the same mechanism to resolve compile and run-time dependencies, known as the
classpath. The classpath is a concatenated list of all the directories and jar files to search for classes
an application depends on. The compilation classpath is taken from the following places:
An environment variable called CLASSPATH
The jvmclasspath COBOL directive
If the classpath is set in both places, the compiler will search all paths listed in both the environment
variable and the compiler directive. Do not include the namespace for a class when setting paths.
A Compilation Example
Listing 12-3 shows two separate source files A.cbl and B.cbl, in two different locations representing
different projects, Support and Examples, in the file locations shown in Table 12-1. The source files
represent classes com.mf.examples.A and com.mf.suppport.B, respectively, and the source files and
.class files are in subfolder structures that match the class namespace, following the Java convention.
Dependencies 221
A depends on B, so we have to compile B.cbl first. From a command prompt, change the directory
to the location of the support project (source\support). Create a bin directory for the compiler output,
and then run one of the following commands for Windows or UNIX, respectively:
This generates A.class and puts it into the examples bin directory.
A Run-Time Example
To run the example from the previous section, we need to ensure that B.class can be found at run
time. We have the choice of putting it into the CLASSPATH environment variable, or specifying a
classpath to the java or cobjvm command when running it. If we put the folder for A.class onto
the classpath as well, we can actually run the application from any directory rather than having to
make C:\source\examples\bin the current directory. This means we need to add an entry to the
classpath. On Windows:
set classpath=%classpath%;c:\source\Examples\bin;c:\source\Support\bin
222
On UNIX:
set CLASSPATH=$CLASSPATH:/home/hub/source/examples/bin:/home/hub/source/
support/bin
export $CLASSPATH
java com.mf.examples.A
The command is the same on the Windows and UNIX platforms. Note that you have to use the
fully qualified classname.
Dependencies on .NET
On the .NET platform, the dependencies for a project are other built assemblies, and the dependent
assemblies must be available at compile time and run time. We will use the sources in Listing 12-3
at the file locations in Table 12-2 to show how this works.
B.cbl C:\source\examples\src\
A.exe C:\source\examples\bin\
B.dll C:\source\support\bin\
To include dependent assemblies in a compilation, use the ilref directive (when you add
references in Visual Studio you are setting this through the IDE). You can include either an absolute
or relative path to the assembly. So, for our example, you would compile B.cbl first as a .dll file. First,
create a support\bin directory, then from directory C:\source\support, use the following command:
The iloutput directive writes the assembly B.dll to C:\source\support\bin (you can use an
absolute or relative path in iloutput). By default, the assembly has the same filename as the main
source file listed on the command line, but you can change it using the ilassembly directive. Any
directories that are part of the iloutput path must be created before you run the compiler. The (sub)
parameter passed to ilgen tells the compiler to build the assembly as a .dll file.
To build A.cbl, create a support\bin directory, then from c:\source\examples:
This creates assembly A.exe in c:\source\examples\bin. However, before we can run A.exe, it
needs to be able to find B.dll. The simplest way of doing this is to have both files in the same location.
So copy B.dll to the same location as A.exe, and then run A.exe. This is what Visual Studio does
when you build a project with other dependenciesit copies the assemblies needed to the output
folder so that they are all in one place.
Summary 223
Before you can strongly name an assembly, you must create your public/private key pair, which
you can do using the sn command-line tool included with Visual Studio. Organizations that are
going to distribute their assemblies should generate one key pair and use the same private key for
signing all their assemblies; this means one public key can be used to verify the identity of any of
your assemblies.
Delay signing is a mechanism to enable a private key to be kept secret from developers. Delay
signing only requires the public key, and the delay-signed assemblies can be used in the development
process by switching off verification of the signed assemblies. A delay-signed assembly can be
signed later with the private key by using the sn tool.
Strong naming is a large topic and a full discussion is out of the scope of this
book. To learn more, search on the web for strong named assembly. This
should pull up the Microsoft documentation on MSDN in most of the top hits, as
well as some other useful articles about the subject.
Summary
In this chapter, we looked at the inputs and outputs of the Visual COBOL compiler. This helps you
understand what is happening under the covers when you build projects in Visual Studio or Eclipse,
and is particularly useful if you want to create build scripts.
C H A P T E R 13
Type Denition
A Visual COBOL type is the template for creating any kind of object. Visual COBOL supports both
value types and reference typesthe distinction between the two is explained in the next section.
Types fall into three broad categories, which youll learn about in this chapter:
Types defined as part of the virtual machine specification for either the .NET or JVM runtimes.
Types defined as part of the core framework for .NET or JVM. In .NET, these are part of the
System namespace, and in JVM, they are part of the java or javax namespaces.
User-defined types. These are either types you define yourself or import as part of
anotherlibrary.
In practice, runtimes and compilers make no real distinction between framework and user-defined
types, but those types that are in the System or Java namespaces can be thought of as part of the
language definition. The types you will define and use most often will be classes and interfaces, but
there are other types as well (such as delegates, enums, and annotations), and this chapter covers
all of them. In Visual COBOL, all types except interfaces ultimately inherit from the Object class
(System.Object in .NET or java.lang.Object for JVM).
Syntax diagrams
The syntax diagrams in this book follow the same notation used in the COBOL Language
Reference in the Micro Focus product documentation. The only significant difference is
that in this book we have shown all required words in underlined lower-case rather than
underlined upper-case.
225
226
class-id. A.
01 numericField binary-long.
01 fieldOfB type B.
method-id. M1.
01 localB type B.
set localB to new B()
set fieldOfB to localB
end method.
end class.
class-id. B.
01 someText string.
end class.
program-id p.
end program.
Figure 13-1 shows the way stack and heap memory are used as the program runs.
Classes 227
Code
Stack Heap
Program p
(1)declare objectA as type A=new A() objectA (1)
instance of Class A
(2)invoke objectA: :M1 (reference to numericField (binary-long, 4 bytes)
instance of class A) fieldOfB (reference to instance of B)
method M1
(4)
(3)set localB to new B( )
Figure 13-1 The allocation of data on the heap and stack as the program in Listing 13-1 runs
At the start of the program, step (1) allocates a reference on the stack frame for the program, and
when we construct an object A using new, storage is allocated on the heap for all the data declared
in A (the data declared as fieldsnot data declared inside methods).
In step (2), we invoke M1. This creates a new stack frame with the storage for data used inside
the method, which in this case is a reference for pointing to an instance of class B. In step (3), we
create an instance of class B, which allocates data on the heap and sets our local reference to
point to the heap data. Finally, in step (4), we copy the local reference to the instance of B to a field
in instance A. This reference is now on the heap so that when we exit the method M1, we still have
a reference to the instance of B.
Both the .NET and JVM runtimes use heap and stack memory like this. However, with .NET, you
can define your own new value types that will be stored directly where they are declared, unlike
reference types, which only have a reference where they are declared and which are allocated in
heap memory when they are instantiated with the new operator. Visual COBOL simulates some of
the behavior of value types for the JVM platform to maintain source code compatibility between
.NET and JVMthis is explained in more detail in the Value Types section later in this chapter.
Classes
A class is a named entity with a set of members. The members are fields, methods, constructors,
properties, indexers, and events. A class does not have to have all types of members.
A class can optionally inherit data and code from a single immediate parent, so A can inherit from
B, but it cannot inherit from B and C. However, the inheritance chain can be any lengthA can inherit
from B, and B can inherit from C. Classes can also optionally implement any number of interfaces,
so A can implement X, Y, and Z. When you instantiate a class (using the new operator), storage for
all the nonstatic fields is allocated on the heap, and a reference to the new instance is returned.
228
Class Syntax
Figure 13-2 shows the formal syntax definition for a class.
The partial modifier is supported on .NET only; see the Partial Classes sec-
tion later in this chapter for more information.
The sharing parent modifier is only for use in nested classes; see the Nested
Types section later in this chapter for more information.
Classes 229
Class Identifiers
The class identifier names the class and can include a namespace:
For example, Class1 and MicroFocus.Book.Class1 are both acceptable class identifiers, although
best practice is to define all types inside a namespace (ideally scoped to a company name) to avoid
name clashes with types from other libraries or frameworks. You can use any valid COBOL identifier
as a class name, but for interoperability with languages other than COBOL, avoid identifiers that
start with a number or include hyphens.
The constraints paragraph enables you to restrict the types acceptable as generic parameters to your
type. Generic types are defined in more detail in the Generic Types section later in this chapter.
230
Example Classes
Listing 13-2 shows a class named Class1 , belonging to namespace MicroFocus.COBOL.Book.
Examples.
class-id MicroFocus.COBOL.Book.Examples.Class1.
*> class members - data, properties, methods
end class.
*> Class named Implementation, implementing two interfaces:
class-id Book.Examples.Implementation
inherits Parent
implements type Interface1.
end class.
end class.
Partial Classes
Partial classes are only supported on .NET and are only intended to support
the Visual Studio designers used for painting Windows Forms, WPF forms, and
ASPX pages. Users are not expected to implement their own partial classes.
The rationale for partial classes is that they enable Visual Studio to keep
codegenerated and parsed by a designer separate from the code written by
theprogrammer.
A partial class enables you to define a class in multiple separate source files, each of which has a
class-id header, modified by the is partial clause. At compile time, all of the partial classes are
combined into a single class definition. All of the separate parts of the class must be referenced
by ilsource directives.
Classes 231
Common Clauses
There are several clauses that are used in a number of placesclass headers, method headers,
property headers, and data declarations. The various clauses are covered in the following sections.
Attributes Clause
Custom attributes enable you to add metadata to parts of your program. Custom attributes have
many different uses; some are read by the compiler and some are read by other systems that are
executing your code. For example, both the .NET Framework and Java enable you to annotate meth-
ods or classes to say that they are deprecated. When you compile code that references deprecated
code elsewhere, the compiler can emit a warning. The attribute is System.Obsolete on .NET and
java.lang.Deprecated in Java.
The Test attribute is an example of data used to help decide how to execute your code. The test
frameworks NUnit (.NET open source) and JUnit (Java open source) use test runners that load your
assemblies or class files, and run all the tests they find in there. They know which code to execute
because any method that is a unit test will be marked with the Test attribute.
You can apply the attributes clause anywhere you see it defined in the syntax diagrams in this
chapterit can be applied to any type and any member. The attributes clause syntax is defined
in Figure 13-4.
attribute attribute-name
[ ( [ value-parameter ]
{[ prop property-name = property-value ] } ... ) ]
JVM
attribute attribute-name
[ ( [ constrictor-parameter } ... ]
{[ prop property-name = property-value ] } ... ) ]
.NET
You can supply value-parameter in the JVM format as a value to an attribute property named
value if one is defined. As explained in the sidebar, .NET attributes are types which can contain the
same kind of code as a class, including constructors that take multiple arguments.
Listing 13-4 shows attributes being applied at the type level, and also to fields, members,
andarguments.
class-id UseAtt02
attribute Author01(Stieg Larsson).
01 s1 string
attribute Author01(Stieg Larsson).
method-id m1
attribute Author02(Henning Mankell prop country = Sweden)
end method.
method-id m2
attribute Author03(Henning Mankell)
attribute Author03(Stieg Larsson).
end method.
end class.
Visibility Modifiers
There are five visibility modifiers you can apply to different entities:
private: The entity is only accessible within the type where it is defined.
public: The entity is accessible from anywhere.
protected: The entity is accessible from the type where it is defined and from subtypes.
internal: The entity is accessible from the type where it is defined and other types in the
same assembly (when compiled for .NET) or the same namespace (when compiled for JVM).
protected internal: The entity is accessible from the type where it is defined, subtypes,
and other types in the same assembly (when compiled for .NET) or the same namespace
(when compiled for JVM).
The first group of entities that visibility modifiers can be applied to is members, and if you dont
apply a visibility modifier, the compiler applies a default:
Fields: The default is private.
Methods: The default is public.
Properties: The default is public.
Operators: The default is public.
Indexers: The default is public.
Iterators: The default is public.
We recommend you use public properties rather than making fields public. Anything you mark
as public is effectively part of the API for your class, and using properties rather than fields makes
it easier for you to change the implementation without changing the API (see the sections Fields
as Properties and Properties later in this chapter for more information about properties).
You can also apply visibility modifiers to some types, but only public and internal are allowed
unless the type is nesteddefined inside another type (see the Nested Types section later in this
Classes 233
chapter for information about this). You can apply visibility modifiers to the following types, and if
you dont apply a modifier, the default is public:
Classes
Interfaces
Value types
Delegates
Enumerations
Type Specifier
You use a type specifier anywhere you need to provide the type of somethingfor example, when
declaring an argument, field, or other data item, or when specifying a parent type or implemented
interface in a class header. COBOL has a number of predefi ned type specifi ersfor example,
binary-long and string. The full list of predefined type specifiers is in Chapter 14. Figure 13-5
shows the syntax for a type specifier.
For predefined type specifiers, you simply give the name of the specifier. For all other types, the
type name is preceded by the type keyword. The optional occurs clause creates a resizeable ar-
ray of items of the type specified. COBOL provides common syntax for indexing and iterating over
arrays across both platforms. This is covered in more detail in Chapter 14 in the Arrays section.
Listing 13-5 shows some sample type specifiers.
A generic type list enables you to list the types required when the type you are specifying is
ageneric type. The syntax for a generic type list is shown in Figure 13-6.
For example, to declare a Stack object (see the Defining a Generic Type section later in this
chapter):
01 theThing T.
01 theOther U.
01 theUnknown V.
end class.
The different constraints you can apply have the following meanings:
implements: The generic parameter must be a type that implements the specified interface.
inherits: The generic parameter must be a type that inherits from the specified type.
newable: The generic parameter must be a class with a default constructor (one that
requires no parameters).
valuetype: The generic parameter must be a value type.
reference type: The generic parameter must be a reference type (a class).
The example in Listing 13-7 shows a generic class with a constraints paragraph that means it
can only be constructed with generic arguments that implement the IComparable interface and
inherit from Base, respectively. You can constrain as many or as few of the generic arguments for
a type or method as needed.
constraints.
constrain T implements type IComparable[]
constrain U inherits type Base.
01 theThing T.
01 theOther U.
01 theUnknown V.
end class.
Fields
Fields are the data items for a class or value type. By default, all fields are instance members and
are allocated for each new instance of the type you create with the new operator. Figure 13-9 shows
the syntax definition for the fields in a type.
236
The working-storage section header is optional, but if used, it must appear before any
methods inside the class. You can declare a COBOL group item as a field, but you cannot include
any types as subitems in the group apart from the native COBOL types available in nonmanaged
Micro Focus COBOL. Group items are covered in Chapter 14. Only use group items where you
need to manage data from legacy code.
If the event keyword is used, the type-declaration must refer to a delegate type (see theDelegates
section). The event keyword makes the field into an event declaration (see the Events section).
Identifiers
COBOL identifiers can start with a letter or digit (see Listing 13-8). An identifier can contain let-
ters, digits, underscores, and hyphens and be up to 30 characters long. COBOL has a great many
reserved words when compared with other languages, but you can use reserved words as identifiers
if you prefix them with the # character. If this name is exposed outside the class (as a public field or
property), other languages see the identifier as the word without the # prefix. Data item identifiers
are not normally case sensitive in COBOL (for backward compatibility), but if exposed as public
fields or properties, case-sensitive matching is used when matching the property name. Most lan-
guages do not allow identifiers to start with a digit or contain hyphens, so you should avoid this for
any identifier you intend to expose as a public or protected field, method, or property.
01 number-of-people binary-long.
01 EmployeeName string.
01 #day binary-char. *> day is a reserved word
Fields as Properties
Properties enable you to expose data fields outside the class they are declared in, but they give you
more control than simply making a field public. You can make properties read- or write-only, or give
different visibility to the property getter (read) and setter (write). You can make a field a property by
simply marking it is as such, or you can define properties as special methods using the property-id
header (this is covered later in the chapter). This enables you to add validation when properties are
set, or calculate a property based on other values.
Classes 237
However, in many cases, you simply want to expose a field as a property, and the property syntax
on a data declaration makes this very simple. Java does not have properties as first class members
like .NET, but Visual COBOL does provide properties for JVMthis is described in more detail in
the Properties section later in this chapter. When you use the property syntax directly on a field,
the field is always treated as private by the compiler, and any visibility modifier used is applied to
the property.
Listing 13-9 shows three different way of marking a field as a property:
The #Name field is exposed as Name and is read-only (no set).
The flag field is exposed as TrueOrFalse, is set to false on initialization, is a static member
(part of the type rather than instances of the type), and is protected, so only this type and
types that inherit from it can access the property (the flag field itself is private).
The #Address field is exposed as Address and can be read or written from anywhere (de-
fault public visibility).
Static Members
When you mark a member as static, it is a class variable rather than an instance variable. So, it is
accessible directly through the class without you having to instantiate an object. You can use class
variables for any state you want to be accessible across all instances of the class (the instances
access it by referring to the type).
Static members are accessible from both instance and static methods and properties. You can
also access static members through the self and super expressions (see the section entitled Self
and Super in Chapter 16).
Constants
When you declare a member as constant, you must also specify a value. Constants must be either
numeric types or strings. You can expose a constant outside the class where it is declared as a
read-only property ( property with no setsee the Properties section below), or make the
constant member publicin either case, its value cannot be changed. However, there is a subtle
distinction between the two:
Making a constant public: The constant value is compiled into any classes that use it.
This is more efficient at run time as it avoids making a call to access a property. But if you
change the value of the constant, you need to recompile any external classes that access
the constant.
238
Making a constant a read-only property: Every time the constant is accessed, there
is a small overhead for a method invocation to access the property. But because it is
accessed at run time, if you change the value of the constant, there is no need to recompile
any dependent classes.
Constants are always static fields and are accessed through the type rather than through an
instance object. Listing 13-10 shows some sample constants and some code accessing them.
end class.
.
Initialize Only
The initialize only phrase restricts modification of the fields contents to constructors. (See the
Constructors section later in this chapter.) An instance field can only be modified from an instance
constructor, and a static field can only be modified from a static constructor.
An initialize only field is effectively a read-only field you can initialize either during object
instantiation or class loading (instance and static fields, respectively).
Fields as Events
You can declare a field that stores an instance of a delegate type as an event. You can then attach
and detach listeners to the event using the attach and detach statements. When the event is
invoked, all the attached listeners receive a notification. Events are a convenient way of implement-
ing callbacks. Events are explained in more detail in the Delegates section later in this chapter.
The effect of marking a field as an event is that the compiler generates accessors to enable you to
add event handlers to the event, and remove them (see the Delegates section later in this chapter).
Classes 239
Final Members
When you mark a member with the final modifier, it cannot be overriden in any subclasses. How-
ever, on .NET, it can still be redefined with the redefine modifier (see the Redefine Methods
section later in this chapter).
Override Members
A type can reimplement members from its parent (same method name and signature). When you
do this, you must add the override clause to the member header to indicate that this is intentional.
When you override a member, this is the method that will get executed on objects of this type, even
if the object has been cast to a different type.
In Listing 13-11, class FirstDescendant inherits from Ancestor, and class SecondDescendant
inherits from FirstDescendant. Ancestor implements two methods, M1 and M2, which take a single
string argument. M1 is overidden in both classes, and M2 is only redefined in FirstDescendant. The
main method in SecondDescendant creates an object of each type and invokes both methods on all
of them. It also casts both of the descended objects back into the Ancestor type. Method M1 in Sec-
ondDescendant also invokes M1 in its superclass (FirstDescendant) by using the super keyword.
For more information about super, see the Self and Super section in Chapter16.
However, regardless of how the object is cast, the member that gets invoked is the overridden
member of the actual underlying object. You can prevent a subclass from overriding a particular
member by adding the final clause to the member header.
class-id MicroFocus.COBOL.Book.Examples.Ancestor.
end class.
240
class-id MicroFocus.COBOL.Book.Examples.FirstDescendant
inherits type Ancestor.
end class.
class-id MicroFocus.COBOL.Book.Examples.SecondDescendant
inherits type FirstDescendant.
end method.
end class.
Redefine Members
The redefine clause is different in meaning from the override clause. Whereas an overridden
member is executed regardless of the type an object has been cast to, a redefined member is only
executed when the object is actually cast to the redefining type. Casting is covered in Chapter
16 in the section As (Implicit and Explicit Casting). In Listing 13-12, the override clause on
FirstDescendant::M2() has been changed to a redefine (the listing only shows the changed
method). Now, the FirstDescendant::M2() method only gets executed when the object invoked is
cast to FirstDescendant or a subclass of FirstDescendantwhen the underlying object is cast
to Ancestor, method Ancestor::M2() is invoked.
When this program is run, the redefine clause changes the behavior as shown:
Properties
A property is a way of accessing a data item. The Fields as Properties section showed how a field
could be directly exposed as a property, but in this section, you learn how to write a property so
that you can use code to validate input, or derive a property from other values. See Figure 13-10.
If you specify a visibility modifier on the property header, any visibility modifier you specify on
the getter or setter must be more restricted than the one on the header. You can specify different
visibility for the setter and the getter, and you can also omit either the setter or the getter, although
you must define at least one.
Inside the statement block, you refer to the value you are going to return (getter) or have been
passed (setter) using the property-value data item. You do not have to declare property-value;
the compiler automatically defines property-value as a data item of the type you have declared
for the property.
For the definitions of final and override , see the sections Final Members and Override
Members later in this chapterthey have the same meanings for properties as they do for methods.
A static property is accessed directly from the class rather than through an instance.
Classes 243
Properties on JVM
Java does not support properties as members in their own right, but treats methods
named with the convention getPropertyName and setPropertyName as property getters
and setters. When you compile a class with properties for JVM, the compiler follows
this convention, so Java code can access the properties using setPropertyName and
getPropertyName. Visual COBOL code can access them simply using the property name.
Methods
A method is a named block of code inside a type. Instance methods can access the instance and
static fields of the type, and static methods can access the static fields. Methods can also have
their own local data, which is created on the stack when you enter the method, and deallocated
when you exit. If you create new instances of reference types (like classes) inside the method, your
local reference to it will be on the stack, but the data for the instance will be created on the heap
(see the section Value Types and Reference Types earlier in this chapter). If the only reference is
a local one, the instance will be garbage collected and the heap storage when the reference is no
longer used. This might be after you leave the method, but could actually happen while you are still
inside the method once you are executing code that no longer accesses the object.
Figure 13-11 shows the syntax for a method.
A method name can be any valid COBOL identifier. Method names are treated as case sensitive
by COBOL and by Java. However, some .NET languages are not case sensitive with consideration
to method names, so we advise against distinguishing method names by case alone. The visibility
modifiers for a method are the same as described in the earlier section Visibility Modifiers.
By default, Visual COBOL methods are virtual, meaning they can be overridden
in subclasses, unless marked with the final modifier. This is the same behavior
as Java, but different from C#, where methods must be explicitly marked as
virtual before they can be overridden.
Listing 13-13 shows a class P with an overloaded method Q(). One method takes no arguments;
the other takes a string as its argument.
class-id P.
method-id Q () public.
end method.
end class.
The Q method, which is invoked, is determined by the parameter types in the statement that
invokes the method. By default, all arguments to a method are passed by value. If you specify by
reference, the arguments are passed by reference, so the receiving method gets the address of
the parameter that has been passed in and can change its value (the equivalent of the ref keyword
in C#, or specifying arguments in C with the * operator).
The example in Listing 13-14 shows a method that takes two parameters by reference. Note
that by reference and by value are both transitiveonce you specify by reference in the
method signature, all subsequent arguments are by reference until you specify by value again.
class-id ExampleCode.A.
working-storage section.
move 5 to n
move Hello World to s
invoke a::ByValMethod(n s)
display n
display s
display n
display s
end method.
end class.
+0000000005
Hello World
+0000000007
goodbye world
The ByValueMethod changed the local values of the parameters passed in, but this does not
affect the variables in the calling code. But the ByRefMethod changed the value inside the value
type (binary-long) to the value set inside the called method, and has changed the reference to the
reference type (the string) to the new string constructed inside the method.
The optional returning clause defines the type of value returned from the method. The returning
clause is not part of the method signature; you cant have two methods with the same name that are
only distinguished by having different returning clauses. You can also invoke a method that has a
returning clause without having to receive the value returned.
Although the returning clause is not used for resolving the correct method out of a group of
overloaded methods, it is significant when matching method groups for delegates. In order to set a
delegate to a method, the method must have the same signature and returning clause as defined
by the delegate. See the Delegates section later in this chapter for more information.
Classes 247
end class.
By Output
The by output modifier is similar to by reference, except that the parameter does not have to be
assigned before it is passed into the method.
This is only available for Visual COBOL compiled to .NET. JVM does not provide
an equivalent facility.
Classes 249
Static Methods
Static methods apply to the class rather than instances of the class, so you invoke them on the class.
A static method can only access fields that are declared as statica static method has no access
to the instance data of the class. Listing 13-17 shows a piece of code invoking a static method.
method-id M1 static.
*> method code...
end method.
end class.
Static methods are often used for utility code that does not need access to instance variables. For
example, if you defined a class called Mathematics that had a Cube(n as binary-long) method,
it would make sense to make Cube() a static methodit does not need access to any data other
than the value passed to it.
Extension Methods
Extension methods enable you to add extra methods to classes without changing their source code
or recompiling them. You arent able to access any of the private fields or methods of the class
you are extending, so you cannot break encapsulation, but they are a convenient way of adding
functionality. Extension methods can be particularly useful for cross-platform programming, as
shown in the following example (see Listing 13-18).
The following rules apply to extension methods:
They must always be public.
You cannot mark them as static (extension already implies that the method is static).
The first argument of an extension method must be the same type as the class you are
extending. The parameter passed into this argument is the object the extension method
operates on. All extension methods must have at least this one argument.
Listing 13-18 shows code to add a CountWords method to strings, and a class with a main
method that uses it. CountWords appears to the client code as though it was actually a method on
the string type itself. For JVM, we have added a second extension method because java.lang.
String has a split() rather than a Split() method, and method names are case sensitive. This
method is conditionally compiled so that it will only appear when compiled to JVM.
250
*> java.lang.String also has a split method, but it is a lowercase split and
*> expects a string. For JVM only, we extend string with a second method -
*> Split - so that it looks more like the .NET one.
end class.
class-id MicroFocus.COBOL.Book.Examples.StringCounter.
end class.
Indexers
An indexer enables you to set or get an indexed property. If a type implemements an indexer, it can
be treated like an array and you can use COBOL array syntax (the 0-based index form onlysee
the Arrays section in Chapter 14). The syntax is shown in Figure 13-13.
Classes 251
An indexer can specify one or more index-identiers. This is analogous to creating an indexer
that works like a multidimensional array.
Listing 13-19 shows a simple class, Indexable, and some code in the Main method, which retrieves
the second element from Indexables internal array (indexing is 0-based when done with square
brackets, so 1 is the second element).
working-storage section.
01 dataItems string occurs 5.
method-id new.
set content of dataItems to (one two three four five)
end method.
252
end indexer.
end class.
Although indexers most commonly use a numeric type as the index property, you can use other
types. The example in Listing 13-20 is very similar to the earlier example, but it uses an enum as the
index property, enabling you to access the elements using Roman numerals.
method-id new.
set content of dataItems to (one two three four five)
end method.
end class.
end class.
Classes 253
enum-id MicroFocus.COBOL.Book.Reference.Roman.
78 I.
78 II.
78 III.
78 IV.
78 V.
end enum.
In this example, the numeric values of the Roman enum start from 0this is
the default for enums. However, because the code indexes into the array with
0-based indexing (square brackets), the program still behaves as expected; the
value Roman::I still returns the first value from the IndexableByEnum class.
Iterators
Iterators are a special type of method that make it easy to write code for enumerating the contents
of arrays and collections using perform varying (for more information, see the Perform Varying
section in Chapter 15). The perform varying statement enables you to enumerate all the elements
in an array or collection, but an iterator enables you to write code to be more selective about what
you return. The syntax is shown in Figure 13-14. Iterators are similar to methods, but instead of an
optional returning clause, there is a compulsory yielding clause.
Each time you exit an iterator using the goback statement, the local state of the iterator is
preserved so that when you enter it again, any looping operation you are executing resumes from
where it was rather than starting again. The stop iterator statement ends execution of the iterator
without preserving state (the same thing happens if you get to the end of the iterator code without
executing a goback, so stop iterator is optional).
The example in Listing 13-21 shows an iterator that returns only the even numbers from an array
of binary-long values. It is invoked through the perform varying statement in the Main method.
class-id MicroFocus.COBOL.Book.Examples.Iterators.
01 fibonacciArray binary-long occurs any value
table of binary-long (1 2 3 5 8 13 21 34 55 89 144 233 377).
end iterator.
end class.
Constructors
An instance constructor is a special type of method that is invoked when you use the new operator
on a type that can be instantiated. Most constructors are instance constructors and are usually
referred to simply as constructors. You can have more than one constructor for a class or value type,
differentiated by their method signature (see the Method Signature and Returning Value section).
Constructors can only appear in classes and value types. The syntax is shown in Figure 13-15.
These are the main differences between a constructor and other methods:
Constructors do not have a returning phrase. They always return an instance of the type
they are a member of.
Constructors cannot be marked as abstract , redefine, or override (none of these
modifiers means anything in the context of a constructor).
Constructors are only invoked by using the new operator against a type or when chained
from another constructor. For more information, see the Inheritance and Constructors
section in Chapter 6.
If you define a class without including a constructor, the compiler creates a default constructor
that has no arguments, providing its parent class has a parameterless constructor. Otherwise,
you need to define a constructor that chains to the parent constructor. For more information,
see the Inheritance and Constructors section in Chapter 6.
256
If you define a class, and include a constructor that takes one or more arguments, but do not
define a constructor with no arguments, the compiler does not create a default constructor.
A constructor can call another constructor from the same class (using the self keyword),
but this must be the first statement in the statement-block.
A constructor can call a constructor from its superclass (using the super keyword), but this
must be the first statement in the statement-block.
A constructor marked as static cannot take any arguments. For more information see the
following Static Constructors section.
Listing 13-22 is a short example that shows a class with two constructors. The constructor without
arguments chains to the other constructor, passing through a default parameter.
class-id ExampleCode.Ctors.
working-storage section.
01 field1 binary-long property with no set as Field.
end method.
method-id new.
*> when you chain to another constructor, it must be
*> the first statement
invoke self::New(1)
end method.
end class.
Classes 257
Static Constructors
A class can optionally have a single, static constructoralso known as a class constructor. A class
constructor is only called once during execution, when the class is initially loaded. There is no guar-
anteed order or time at which class constructors will be called by the runtime. It is guaranteed that
the class constructor will be called before any other code accesses the class or creates instances
of it. You cannot include any access modifiers on a static constructor (they would have no meaning
because the static constructor is only accessed by the run-time system).
Most classes do not need a static constructor, but they are useful for initializing static data that
cant be set in a value clause, or for performing any actions that only need to be done once.
You dont know what order finalizers will be called in. For example, if you have objects that
are nodes in a tree, there is no reason to suppose that the finalizers for leaf nodes will be
called before the finalizers for root nodes.
If a finalizer throws an uncaught exception, you can potentially end up with an object in an
indeterminate state.
Finalizers on .NET can potentially be called more than once.
Having said all that, you might still have a reason for writing a finalizer. You might decide you
want a safety net in case your explicit disposal method to clean up resources doesnt get called,
or because your managed object is a peer for an object running in native codein which case, the
finalize method is an opportunity for you to delete the native peer (this is not a particularly common
case). However, before going down this route, read some of the excellent articles available on the
web about finalizers/destructors for Java or .NET so you better understand the issues involved.
To implement a destructor in Visual COBOL, you must override the method Finalize on the
.NET platform and finalize on the JVM platform. The method takes no arguments and returns no
result. The first thing your finalizer should do is call the finalizer on its superclass; that way, you can
be sure that the superclass still gets an opportunity to do its own cleanup. Listing 13-23 shows an
outline implementation for a destructor method that uses conditional compilation to make it work on
either the .NET or JVM platforms. The outline doesnt show it, but unless your finalizer code is very
trivial, put it inside a try/catch/finally block to avoid having your finalizer throw an uncaught exception.
method-id
$if jvmgen set
finalize
$else
Finalize
$end
override.
$if jvmgen set
invoke super::finalize
$else
invoke super::Finalize
$end
end method.
Classes 259
Operators
Operator members enable you to use standard COBOL operators with instances of your own types.
For example, you can overload the equality operator (equals or =) so that you can test two instances
of your own type to see if they are the same value. Table 13-1 shows the list of operators you can
overload with an operator member.
Table 13-1 List of operators that can be overloaded with an operator member
= + b-and explicit
<= * b-xor +
< / b-not -
>= ** b-left
> b-right
Overriding the comparison operators enables you to determine your own rules for the equality
and inequality of instances of your own types. Normally, if you compare two reference type objects
for equality or inequality, the comparison done is to see whether the two object references point to
the same objectthis is not the same as determining that they have the same value. Many classes
implement an equals method to compare values, and by overloading the = operator, you can invoke
this method when they are compared using the equality operator. If you overload the = operator,
you must also overload the inequality operator <> (this is enforced by the compiler). You should
also overload the GetHashCode() (.NET) or hashCode() (JVM) methodthis is not enforced by
the compiler. For more information about hashcode methods, see the Keys section in Chapter 14.
The explicit and implicit operators enable you to override the usual casting rules used by
the compiler and runtime when you convert from one type to another.
The syntax for an operator is shown in Figure 13-16.
260
Listing 13-24 shows a partial implementation of a ComplexNumber class. For brevity, it only implements
addition and implicit and explicit conversions, but you could write overloads for all the arithmetic operators.
The main method in the OperatorsExample class instantiates two ComplexNumber objects and adds
them together, which returns a new ComplexNumber object, which has real and imaginary parts that
are the sum of the real and imaginary parts in the two operands. When you overload an operator, its
important to follow the Principle of Least Surprise; in the example given here, the result of adding our
two objects together is what you might reasonably expect. But, for example, what would the expected
behavior be for addition or subtraction operators on an Employee class?
The example also shows the implicit conversion from a float-long to a ComplexNumber, and an
explicit conversion back from a ComplexNumber to a float-long. The explicit conversion fails if the
ComplexNumber does not have an imaginary part with a value of 0.
For more information about conversions, see the Implicit and Explicit Casting
section in Chapter 16.
end operator.
end class.
class-id MicroFocus.COBOL.Book.Reference.OperatorsExample.
move 2.1 to f
end method.
end class.
Interfaces
An interface is a type that defines a set of members, but has no implementation or data of its own.
Interfaces can define the following members:
Methods
Properties
Indexers
Events
When another type implements an interface, it must provide a definition of all the members
defined by the interface. An abstract class can define members without providing an implementa-
tion if it marks those definitions as abstract. A type that implements a particular interface can be
implicitly cast to that interface; code using the interface can be completely unaware of the actual
type providing the implementation. We provided an example of this in Chapter 8, in the section A
Better Date Class.
Frameworks make heavy use of interfaces to enable common operations to be carried out across
many different types. For example, the .NET Framework has an interface called IComparable that
enables two objects to be compared for sorting, and Java has a similar interface, Comparable. You
can sort objects of any type that implements these interfaces using arrays, as shown in Chapter 14.
The syntax for an interface is shown in Figure 13-17.
Interfaces 263
Interfaces can only be marked internal or public, unless they are nested inside another type
(see the Nested Types section later in this chapter). Interface-identiers follow the same rules
as class identiers. An interface can itself implement other interfaces; this means that part of the
contract for a type implementing this interface is to implement all the other interfaces specified here.
The method, property, and indexer members contain no implementation code (there is no statement
block), but in addition, there are no modifiers. All members have the same visibility as the enclosing
interface (either public or internal).
Listing 13-25 shows a very simple interface and a class implementing it. The main method
declares a type of IAlarm, but actually stores an instance of DigitalClock in it, and then invokes
the snooze method.
264
interface-id MicroFocus.COBOL.Book.Examples.IAlarm.
end interface.
end method.
end class.
Sometimes, two different interfaces might specify a method with the same name and signature.
If a class implements both these interfaces, a single implementation method might serve for both
interfaces. If you need to provide a separate implementation for each interface, you can qualify the
methods with the for phrase, as shown in Listing 13-26.
interface-id MicroFocus.COBOL.Book.Examples.IAlarm.
method-id Snooze(duration as binary-long).
end method.
end interface.
interface-id MicroFocus.COBOL.Book.Examples.ISleeper.
method-id Snooze(duration as binary-long).
end method.
end interface.
class-id MicroFocus.COBOL.Book.Examples.Television
implements type IAlarm type ISleeper.
end class.
The for phrase is available only when compiling for .NET. JVM does not support
different implementations for methods with identical signatures.
Value Types
On the .NET platform, you can define your own value types, where the fields for the type are stored
directly where the value type is declared (see the Value Types and Reference Types section earlier
in this chapter for more information). You are advised to only use value types that hold small amounts
of data (up to about 16 bytes is the size recommended in .NET Framework documentation), but also
only when you want a value type semantic. Visual COBOL will compile value types for JVM, but
because JVM offers no support for defining value types, they are compiled as classes.
Listing 13-27 shows a very simple value type, StudentRecord, with two fields. The main method
creates an instance of StudentRecord in local variable sr1 , assigns sr2 to sr1, and then modi-
fies the value of one of the fields in sr2. Because StudentRecord is a value type, the value of sr1
remains unchanged, and when the names of the two records are printed out, you can see that sr1
and sr2 are actually separate records.
This is what we mean by a value type copy semanticassigning one instance of a value type
to another actually copies the values into a new object on the stack. If you change the headers of
StudentRecord to class-id and end class, making it a reference type, and run the program again, it
prints out Sue twice because sr1 and sr2 are now separate references pointing to the same object.
class-id a.
method-id main (args as string occurs any) static.
01 sr1 type StudentRecord value new StudentRecord(Bob, 3.5).
01 sr2 type StudentRecord.
end class.
Delegates
A delegate is a type that can store a reference to a particular method. The method can either be
associated with a particular instance object, or it can be a static method on a particular class. A
delegate has a method signature, and can only hold a reference to a method of the same signa-
ture. A delegate is similar to a procedure or function pointer, but is type safe because you can only
invoke a delegate against a method that matches the number and type of parameters defined by
the delegate. Figure 13-18 shows the syntax for defining a delegate.
Once you have defined a delegate, you can instantiate it by setting it to a method group. A method
group is the set of all the overloaded methods with a specified name from a particular instance (or
class in the case of static methods). The method group is implicitly converted by the compiler into
a delegate, matching the method signature and returning type (if there is one) of the delegate to a
method from the method group. You will get a compile-time warning if no methods in the group match
the delegate.You get a method group by prefixing an object expression with the method operator.
Listing 13-28 defines StringDelegate, which takes one argument, a string, and the
DelegateExample class, which has two methods called M1 , one of which takes a string argument
Delegates 267
and, therefore, matches the delegate. The main method instantiates an instance of DelegateExample
and creates a delegate that points to the instance method M1. It also creates a delegate and attaches
it to the static method S. It then invokes each delegate using the run verb.
end method.
end class.
The diagam in Figure 13-19 shows that the instance ex in DelegateExample has a method group
for the M1 methods, and that the delegate created matches the M1 method that takes a string. The
268
delegate has a pointer to the method and the instance against which it will run the method. When
you run the delegate, it actually executes M1 on object ex.
Delegate Object
ex
Method group
d
Method M1(string)
Method M1(binary-long)
Events
You can also use delegates to define an event, by adding a field of the type of a delegate to a class,
and modifying the declaration with the event keyword. You can then add many event handlers to
your single event, and when you run the event, all the attached handlers are all invoked.
An event handler is just a delegate that matches the signature of the delegate type. You can create
delegates as shown in the previous section, but the attach and detach verbs also enable you to
connect and disconnect method groups directly from events without going through the intermediate
step of creating a delegate explicitly.
Listing 13-29 shows a delegate type, MsgHandler, and two classes, Broadcaster and Receiver.
Broadcaster defines an event using the MsgHandler delegate, and Receiver attaches a method
handler to it. The code in the main method instantiates a single broadcaster and two receivers, and
then runs the event.
The attach verb is used to attach the event handlers to the event, and the detach verb is used
to remove one of the event handlers. Delegates and events make it easier to send callbacks to
objects at run time without having to define callback interfaces and implement them everywhere
they are needed, as well as providing the ability to send the callback to multiple receivers. If you
have multiple receivers, they are invoked sequentially, and the order of dispatch is not guaranteed.
Event handlers should return promptly in order not to slow down dispatch to other handlers. If an
event handler has to carry out a task that could be lengthy, it should hand it off to a separate worker
thread and return promptly.
The SendEvent() method copies the event defined as an instance member into a local variable,
then tests that it isnt null before invoking it. If no handlers have been attached to an event, it appears
as null and trying to invoke it produces a null reference exception. The reason for copying it to a local
Delegates 269
variable before using it is to guard against the condition where the last remaining event handler is
detached from a different thread between being tested for null and being invoked.
delegate-id MicroFocus.COBOL.Book.Examples.MsgHandler
(msg as string) protected.
end delegate.
interface-id MicroFocus.COBOL.Book.Examples.IBroadcaster.
01 eventSender type MsgHandler event public.
end interface.
class-id MicroFocus.COBOL.Book.Examples.Broadcaster
implements type IBroadcaster.
01 eventSender type MsgHandler event public.
end class.
class-id MicroFocus.COBOL.Book.Examples.Receiver.
01 #label string.
method-id new(l as string).
set #label to l
end method.
end class.
Anonymous Methods
There is a second form of delegate definition that enables you to define a delegate object inline as
an anonymous method. This can save cluttering your classes with lots of small methods that are
only used as the target for events. The syntax is defined in Figure 13-20.
Anonymous methods enable you to attach inline code directly to an event. Listing 13-30 uses
the delegate and Broadcaster types from the example in Listing 13-29 to show this.
delegate-id MicroFocus.COBOL.Book.Examples.MsgHandler
(msg as string) protected.
end delegate.
class-id MicroFocus.COBOL.Book.Examples.Broadcaster.
01 eventSender type MsgHandler event public.
end class.
Generic Types 271
class-id MicroFocus.COBOL.Book.Examples.Receiver.
01 #label string.
method-id new(l as string).
set #label to l
end method.
method-id Main(args as string occurs any) static.
01 b type Broadcaster.
attach
delegate using by value msg as string
display anonymous method msg
end-delegate
to b::eventSender
end method.
end class.
Generic Types
A generic type is one where the exact types of some fields, method arguments, or property arguments
are not specified in the type definition. The unspecified types are supplied at run time when instances
of the type are created. Generic types enable you to benefit from type safety without having to define
a different class each time you want the same functionality available to different types of data. The
most common use of generics is for collections and dictionaries. A generic collection type can store
different types of objects. You can instantiate the same collection type to work with boats in one
place, and cars in another, by specifying the type of object you want it to hold at instantiation time.
The supplier of the collection class does not need to know at implementation time what kinds
of objects you will be storing within the collection, but at the same time, you get type safety, as
once you have instantiated a collection for a particular type, you can be sure that all the objects you
retrieve from it will be of that type. Before generic types were available, the usual work-around was
to store items of type object in a collection, and then cast them to the required type when they were
retrieved. This could lead to run-time exceptions when you retrieved an object of an unexpected type.
You can define the following types as generic types:
Classes
Interfaces
Delegates
272
See the Generic Constraints Paragraph section earlier in this chapter for
information on how to constrain generic parameters.
01 stackStorage list[T].
method-id New.
create stackStorage
end method.
*> Pop the data off the stacks until they are empty. Data is returned
*> in reverse order
perform until stringStack::Size = 0
display stringStack::Pop
display numberStack::Pop
end-perform
end method.
end class.
Enumerations
An enumeration is a list of constant values. Figure 13-21 shows the syntax for defining an enumeration.
274
You can only use native managed numeric types as the type of values in an enumeration, and by
default, an enumeration is binary-long. You can specify values on each item in the enumeration,
but if you dont specify values, the items are numbered sequentially starting from 0. Listing 13-32
defines a DaysOfWeek enumeration and a short class that provides a method for testing whether a
given day is part of the weekend. This enumeration does not have a value clause on each item, so
the compiler numbers them from 0 (monday) to 6 (sunday).
end enum.
class-id MicroFocus.COBOL.Book.Examples.WeekendTest.
Java enumerations are more like classes than the ones provided in .NETthey
enable you to define other members as part of the enum. Visual COBOL does
not allow this.
move 3 to n
set aDay to n as type DaysOfWeek *> succeeds for this value
display aDay
set n to aDay as binary-long *> always succeeds
move 8 to n
set aDay to n as type DaysOfWeek *> fails on JVM, succeeds on .NET
end method.
Flag Enumerations
The .NET Framework has a System.Flags attribute that you can apply to an annotation, which enables
you to use an enumeration as a set of binary-flags. If you mark an enumeration with the Flags custom
attribute, you can operate on it using bitwise operators. Listing 13-34 shows a simple example of
combining two values from an enumeration marked with Flags. Flags is actually the custom at-
tribute System.FlagsAttribute, but the System namespace is always assumed, and the Visual
COBOL compiler respects the convention that .NET custom attributes are always named with the
suffix Attribute, which can be omitted from declarations of the attribute.
enum-id Protocol
custom-attribute type Flags.
78 oddParity value 1.
78 startBit value 2.
78 stopBit value 4.
end enum.
class-id MicroFocus.COBOL.Book.Examples.FlagEnumeration.
display p
end method.
end class.
Custom Attributes
In the earlier Attributes Clause section, you learned how to apply attributes to types and mem-
bers to provide extra information at compile time or run time. You can also define your own custom
attributes in COBOL. Figure 13-22 shows the syntax.
We refer to custom attributes in Visual COBOL, as .NET was the first managed
framework to provide this facility and it was implemented for COBOL on .NET
first. Custom attributes when compiled to JVM actually create Java annotations,
which are the equivalent entity on JVM. JVM annotations are more limited than
.NET attributes: in JVM, you can create an attribute that has one or more argu-
ments, but you cannot create fields, methods, and properties as you can in .NET.
The optional usage is clause enables you to restrict where an attribute can be placed. If there
is no usage is clause, you can apply the attribute anywhere that allows an attributes clause. When
the usage clause is used, you can only use the attribute in the named places.
The optional multiple phrase means that you can use the attribute multiple times on any given
entityotherwise, you can only apply the attribute once in each place.
A custom attribute can define one or more fields, which can be set through the attribute constructor.
Assembly attributes are placed on the class header, but actually apply to the
assembly the class is compiled into. Assembly only has meaning for .NET
compilation.
Listing 13-35 shows two custom attributes that will compile and work on .NET or JVM. The type
names follow a .NET convention that all attribute names end in the word Attribute, but that you do
not need to include that part of the name when referring to the attribute elsewhere. COBOL follows
this convention, and implements it on both the .NET and JVM platforms. Author01Attribute defines a
single value, and Author02Attribute defines two values. The Country value is exposed as a property
so that it can be set in an attributes clause using the prop keyword.
attribute-id Author01Attribute.
01 #value string.
method-id new (#name as string).
Set #value to #name
end method.
method-id inst.
end method.
end attribute.
attribute-id Author02Attribute.
01 Country string property.
01 #value string property no get.
Method-id new (#name as string).
Set #value to #name
end method.
end attribute.
In .NET, attributes are defined as classes, which inherit from System.Attribute. The equivalent
to Author01Attribute defined as a .NET class is shown in Listing 13-36. In Java, annotations (the
equivalent of attributes) are defined using the @interface keyword, so Listing 13-37 shows the
equivalent Java definition of our annotation. The data specified in an attribute can be retrieved at
run time using reflection. Both JVM and .NET support reflection, which allows for dynamic run-time
discovery and manipulation of types and methods. Reflection is a large topic, and beyond the scope
of this book, but Visual COBOL supports reflection on both platforms.
Nested Types 279
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthorAttribute
{
public String value();
}
Nested Types
You can nest the definition of types inside another type. A nested class can access any static fields,
methods, or properties of the containing class. Use the sharing parent phrase in the class definition
of the inner class in order for it to gain access to the instance members of the containing class. Any
inner type definitions must appear after the definition of all the type members (fields, methods, and
properties) of the outer type. The following types may contain nested types within them:
Classes
Interfaces
Value types
Attributes
From the containing class, you can access the nested classes directly by name. If your nested
class is visible to outside types, its name is formed from the containing class name and the inner
280
class name, joined by a + sign. The code in Listing 13-38 shows an inner class accessing a static
member of its container, and an inner class method being invoked from both the containing class
and an external class.
Nested types must be defined at the end of a class, before the end class header but after any
class members. You can nest classes, delegates, and enumerations inside a class.
class-id MicroFocus.COBOL.Book.Examples.OuterClass.
01 s_field string static value Outer class .
end method.
class-id InnerClass.
method-id M1.
display type OuterClass::s_field
end method.
end class.
end class.
class-id MicroFocus.COBOL.Book.Examples.InnerClassAccessor.
invoke ic::M1
end method.
end class.
Nested Types 281
Sharing Parent
As stated earlier, the sharing parent phrase enables a nested class to access instance fields
of the containing class. The nested class must have access to an instance of the containing class
for this to work. When you instantiate the nested class from an instance of the containing class, it
gets an implicit reference to the containing object automatically. When you instantiate the nested
class from somewhere other than the containing class, you must explicitly pass in an instance of the
containing class as the first parameter of any constructor. This argument is not explicitly declared
in any of your constructor definitions for the inner class.
Listing 13-39 shows a sharing parent inner class. It is accessed both from method M0 of the
SharedParent class that contains it, and from method One of UnnestedClass. When constructed
from UnnestedClass, we pass in an instance of SharedParent even though that is not specified
as an argument in either of the InnerClass constructors.
class-id MicroFocus.COBOL.Book.Examples.SharedParent.
01 s_field string static value Outer class .
01 field string value Instance Data.
method-id M0.
declare ic as type InnerClass = new InnerClass
invoke ic::M1
end method.
method-id M1.
display field
display s_field
display innerClassfield
282
end method.
class-id MicroFocus.COBOL.Book.Examples.UnnestedClass.
method-id One.
display Method One of unnested class
declare sp as type SharedParent = new SharedParent
declare ic as type SharedParent+InnerClass
= new SharedParent+InnerClass(sp)
set ic to new SharedParent+InnerClass(sp Hello from unnested class)
invoke ic::M1
end method.
end class.
Summary
This chapter defined the syntax for all the different types you can define in Visual COBOL (classes,
interfaces, value-types, delegates, enumerations and custom attributes). Visual COBOL supports the
.NET and JVM type systems in a consistent and cross-platform manner. Although some constructs
are not natively available on the JVM platform, Visual COBOL emulates these where possible. For
example, value-types, delegates and properties are all available on Visual COBOL on .NET and
JVM even though they are not natively supported by the JVM runtime. This enables you to write
cross-platform code that can be compiled and run on either platform.
C H A P T E R 14
Data Types
Traditional COBOL defines data down to the byte level; this comes from its history as one of the
earliest high-level languages when memory was expensive. For example, PIC XX COMP-5 defines
a 2-byte, unsigned integer, and COMP-5 specifies that the byte order should be according to the
native byte order for the CPU running the code.
For compatibility with existing code, Visual COBOL still supports this way of defining data, but
it also defines a number of new Visual COBOLspecific data types that match those used on man-
aged platforms. In addition, it also supports a small set of useful cross-platform objectsstrings,
arrays, lists, and dictionaries. This chapter defines all the predefined data types built in to Visual
COBOL, as well as showing their equivalents on the .NET and JVM platforms.
In this chapter, youll learn about:
Numeric types and literals
The Boolean type
Group Items
Type References
Cross-Platform types - object, string, array, list and dictionary
Integer Types
Table 14-1 lists all the predefined integer types available in Visual COBOL, together with their native
representation on the JVM and .NET platforms. Integers are value types.
283
284
JVM does not have primitives to represent unsigned integers, although you
can declare them in Visual COBOL and they will work as expected even when
compiled to JVM.
On .NET, unsigned integers are supported by the listed value types; however,
they are marked as not Common Language Specication (CLS) compliant.
This means that not all languages running on the .NET platform support them
natively (C# does support these types).
You can implicitly cast between integer types in any direction, but if you are casting from a larger
integer type to a smaller one, and the value is too large, the result of the operation is 0. Listing 14-1
shows a binary-short being cast to a binary-long, and back again. It also casts a binary-long
with a value too large to fit in a binary-short; the binary-short is set to 0.
compute l = (2 ** 30)
set s to l *> this value cannot be represented by binary-short
display l s *> s = 0
end method.
end class.
Numeric Types for Managed Code 285
Floating-Point Numbers
Floating-point numbers on both platforms are represented by the single-precision and double-
precision IEEE 754 formats. The float-short type can represent signed values from approximately
1.5 10^45 to 3.4 10^38 with a precision of 7 digits. The float-long type can represent signed
values from approximately 5.0 10324 to 1.7 10^308 with a precision of 1516 digits. Floating
points are value types. Table 14-2 shows floating-point type equivalents in COBOL, .NET and JVM.
As with integers, you can cast backward and forward between float-short and float-long, but
if a value in a float-long is too large to fit in a float-short, the float-short is set to 0. You can
assign integer, fixed-point, or floating-point literals to a floating-point data item.
High-Precision Decimals
Data items declared as decimal are represented as System.Decimal or java.math.BigDecimal,
but arithmetic is actually carried out using COBOL high-precision arithmetic to 38 decimal places.
Decimals are value types. Table 14-3 shows decimal equivalents in COBOL, .NET and JVM.
end method.
end class.
Overflow Conditions
COBOLs default behavior for an overflow condition is to return 0. However, you can catch overflow
conditions in compute statements by using the on size error phrase. Listing 14-3 shows a short
program that shows the on size error and not on size error phrases taking different actions
depending on the result.
compute l = (1 / 0)
on size error
display Overflow condition
not on size error
display l
end-compute
end method.
end class.
Numeric Types for Managed Code 287
Numeric Literals
There are three types of numeric literals in COBOLinteger, fixed-point, and floating-point. You can
assign any literal to any type of numeric data item. However, fractional values are simply truncated
when assigned to integers, or to floating-point or decimal numbers of lower precision than the
fraction. If a value is larger than the capacity of the receiving data item, the data item is set to 0.
Integer Literals
Integer literals are normally assumed to be in base 10, and can start with an optional + or - sign
followed only by one or more digits. You can also specify integer literals in base 16 (hexadecimal),
by preceding them with an H and putting the value inside quotes. All of the following are examples
of valid integer literals:
37
-15
+8
HF0CA99
Fixed-Point Literals
A fixed-point literal can start with an optional + or - sign, followed by one or more digits from 0 to
9 and a decimal point. The decimal point can appear anywhere except as the last character. These
are all examples of valid fixed-point literals:
3.1415926
-42
+83.0
Floating-Point Literals
You can assign any numeric literal to a floating-point item, but the format for floating-point literals
is shown in Figure 14-1. There must be no spaces between any of the characters. For example:
1.55E5
-1.73 22
7.2E-10
Boolean Type
A boolean data item is represented by a Visual COBOL condition-value. A condition-value
can be set to true or false (the case does not matter). You cannot cast condition-values to or
from numeric data items or literals. Table 14-4 shows the equivalents in .NET and JVM.
Listing 14-4 shows some examples of using a condition-value. A condition-value is a value type.
We do not recommend the use of COBOL group items for new code unless you need to interact
with older code. There are three reasons for this:
Defining a class is a better way to group a set of fields together; you get all the benefits of
type safety from the compiler and appropriate validations and functions for the record can
be built in to the class. COBOL does not handle group items in a type-safe way.
It can be slower accessing fields as part of a group item than fields defined as level 01.
You cant put Visual COBOL reference types or value types into group-item fields.
Listing 14-5 shows an example of a group item. In this record, the declaration of address-line
has an occurs 3 clause at the end. This allocates a fixed array of 3 address-line fields. This na-
tive array is an area of contiguous memory, and should not be confused with the managed arrays
described later in this chapter.
The field uk-post-code redefines zip-code this means that only 8 bytes of storage are
allocated and that uk-post-code and zip-code occupy the same space. You can address this as
one record (zip-code), or as two 4 byte records (outward-code and inward-code). When the
program in Listing 14-5 is compiled as native code, the compiler allocates a contiguous block of
memory in COBOL working-storage (the heap) the same size as the total length of all the fields.
Each field has a pointer to the start of the part of the memory block that represents it.
program-id GroupItems.
01 cust-record.
03 cust-name pic x(80).
03 cust-address.
05 address-line pic x(80) occurs 3.
05 zip-code pic x(8).
05 uk-post-code redefines zip-code.
07 outward-code pic x(4).
07 inward-code pic x(4).
03 age pic 9(4) comp-x.
procedure division.
move Director General to cust-name
move BBC to address-line(1)
move W1A to outward-code
move 1AA to inward-code.
end program.
When one field redefines another, it is sharing the same storage as the field it redefines; a field
can be redefined many times. You will often see this in older code where complex record structures
are created, with the value of a flag field indicating the type of data stored so that you know which
set of redefined fields to interpret it through. COBOL has direct support for reading and writing
records to files that predates the use of relational databases, and some COBOL applications still
290
store data in ISAM (Indexed Sequential Access Method) and VSAM (Variable Sequential Access
Method) files.
Figure 14-2 shows how the group item in Listing 14-5 is actually allocated in memory and the
areas each data item points to.
cust-record
cust-name
cust-address
Address-line(1)
80 bytes
Address-line(2)
80 bytes
Address-line(3) 80 bytes
zip-code 80 bytes
4 bytes 4 bytes
uk-post-code
2 bytes
outward-code
inward-code
age
When cust-record is compiled to managed code, the compiler allocates a byte array the correct
size to hold all the fields, and addresses each individual field by using the offset from the start of
the array. This is why you cant declare an object reference inside a group item; although the code
generated by the compiler in conjunction with the COBOL run time understands the structure of
this data, the garbage collector on the managed platform does not. If Visual COBOL allowed object
references inside a group item, the garbage collector would not know about the reference and could
delete the underlying object while COBOL code still had a reference to it.
Type References
You can declare a data item that refers to an instance of a type not predefined in the Visual COBOL
language by preceding the type name with the keyword type. As stated in the previous section,
any such data item must be declared at 01 level or the compiler gives an error. You can use the fully
qualified type name (which includes the namespace), or just the type name itself with no namespace.
The compiler will recognize names without namespaces in either of the following circumstances:
The namespace has been given to the compiler using the ilusing() directive.
The namespace of the type is the same as the namespace of the type making the declaration.
You can put ilusing directives directly into your code, for example:
$set ilusing(javax.swing.ui)
Cross-Platform Reference Types 291
You can also set these directives as part of the compiler command line or in a directives file. Visual
Studio makes this particularly easy to do as it has a Namespaces page in the Project Settings dialog
box, which shows all the namespaces for assemblies referenced by your project, and you can select
the ones you want imported; Visual Studio sets the ilusing tags as part of the project. In Eclipse,
you can set ilusing directives directly on the Build Configuration page of the project properties.
Object
object is the odd one out in this list. Although all other types are descended from object and it
provides some very basic functionality needed by all types, you never create an instance of object,
and you rarely refer to it by name; for example:
class-id MyClass.
Despite that, there are times when you do need to be able to refer to the root class. In Visual
COBOL, object is a keyword; when you compile to .NET, it refers to System.Object, and when
you compile to JVM, it refers to java.lang.Object. You dont use the type keyword with object.
For example:
01 o object.
Strings
Strings are the most commonly used type in most applications. Although they are implemented as
reference types in both .NET and JVM, both platforms create them as invariant objects, which have a
value-type copy semantic. (See the Value Types section in Chapter 13 for more information about
copy semantics.) String is a predefined type in Visual COBOL, referred to by the string keyword.
You declare strings without using the type keyword. For example:
01 s string.
292
You can set a string to a string literal, but you can also set a string to any type of object because
the compiler inserts code to call the ToString() or toString() method (depending on the platform),
and this is the value the string gets set to.
When you declare an object using the string keyword, you get a System.String on the .NET
platform, and a java.lang.String on the JVM platform. You can access all the methods of these
objects directly in Visual COBOL, but because the two platforms implement their APIs slightly
differently, code written this way is not portable.
However, Visual COBOL leverages existing COBOL syntax to enable you to carry out the following
operations on Visual COBOL strings with code that will work on both the .NET or JVM platforms:
Comparison
Concatenation
Substrings
Search/Replace
We cover comparison in the following section. Search, replace, and some substring operations
are covered in the next chapter, in the Inspect and Unstring statements. Concatenation and reference
modification of strings is covered in Chapter 16, in the Reference Modification Expression section
and the Concatenation Expression section. Some string operations you might want to use (for
example, forcing to upper- or lowercase) are not yet covered by cross-platform syntax. You can use
extension methods as a work-around for this; for example, you could use conditional compilation to
add a ToUpper() method on the JVM platform, which maps back to Java String::toUpperCase().
Comparison
You can compare strings for equality and inequality using the standard equality and inequality operators.
The equality operator is mapped to String::Equals(object) on .NET and String::equals(object) on
JVM. Listing 14-6 shows an example of comparing two strings.
end method.
end class.
Cross-Platform Reference Types 293
Nonnumeric Literals
A COBOL nonnumeric literal is a sequence of any allowable characters available in the computers
character set, and can be between 1 and 8192 bytes in length, delimited by either quotation marks
or apostrophes (the start and end delimeter must match). You can use hexadecimal values, which
are turned into characters of the equivalent value by using Xnn. A literal can span more than one
line by using the continuation character (hyphen) inside the indicator area.
See the Source Format section in Chapter 12 for more information about the
Indicator area.
Listing 14-7 shows some COBOL nonnumeric literals. The last one is on three separate lines.
You can also create strings that have a null appended to the end (x00) by prefixing the string
with z. For example:
Arrays
Visual COBOL arrays are fixed size once created. You can declare the size when you declare the
array, or you can set the size at run time. When you declare an array, you have to declare the type
of object that it will hold. An array can only hold objects of either the type declared or of types that
inherit from or implement that type (you can declare an array for objects of a particular interface type).
Arrays are objects of type System.Array on the .NET platform or Java arrays on the JVM platform.
You can think of an array as a container for objects of a particular type. The syntax for declaring
an array is shown in Figure 14-3. If you declare an array as occurs any, you are not setting a size,
and no actual array is created until you either set the size or the content. If you use a numeric size,
an array of that size is allocated as part of the declaration, and you can start storing elements in it
immediately.
294
You can use set size of to create an array of a specific size. The syntax is shown in Figure 14-4.
You can set the size using an integer data item (binary-long, binary-short, or binary-char), a
numeric literal, or any expression that returns an integer valueso you can set the size using the
result returned from a method. You cant use set size of to resize an existing array. When set
size of is executed, a new array is created and the reference put into identifier-1. If identifier-1 was
already pointing to an array, that array still exists, but unless the reference to it is stored elsewhere,
you are no longer able to access it.
You can also set the contents of an array to a list of elements using the set content of statement.
The syntax is shown in Figure 14-5. Like set size of, set content of creates a new array and
puts the reference to it in identifier-1. The list of elements goes inside brackets. You can use any
list of expressions that evaluate to objects of the type expected for the array.
set myArray(1) to 99
set myArray[0] to 99
You can use the size of operator to get the number of elements in an array:
Finally, you can also loop over the contents of an entire array using perform varying. The syntax
for perform varying is described in Chapter 15, but there is an example in Listing 14-9.
end class.
296
class-id MicroFocus.COBOL.Examples.Cat.
01 #name string property with no set as Name.
You can also sort arrays, provided the objects stored implement the System.IComparable
interface on the .NET platform and the java.lang.Comparable interface on the JVM platform. See
Chapter 8 for the implementation of an IDate interface that did exactly this.
The sort verb is covered in Chapter 15, and you can see an example of sorting
an array in the Cross-Platform Date Code section in Chapter 8.
Multidimensional Arrays
Visual COBOL supports multidimensional arrays (up to a maximum of 16 dimensions). You can
create both rectangular arrays and jagged arrays. Jagged arrays are created as arrays of arraysso
in the case of a two-dimensional array, the columns representing the second dimension can be of
different lengths. Figure 14-6 illustrates rectangular and jagged arrays. To create rectangular ar-
rays, you simply list the size (or any for arrays allocated at run time) of each dimension in turn after
occurs when you declare the array. For jagged arrays, you repeat the occurs keyword before each
dimension you want to be jagged.
Listing 14-10 shows some examples of creating and using rectangular and jagged arrays.
Cross-Platform Reference Types 297
Rectangular Arrays
Jagged Arrays
01 colors string occurs 5
occurs 3. "red" "green" "orange" "blue" "yellow"
display stringArray(2 2)
display stringArray[1 1] *> Will also display Buttercup
set numberArray(1 5) to 99
end method.
end class.
Lists
Visual COBOL lists are collections of objects that can grow dynamically as needed. A list is a generic
type; when you declare a list, you must specify the type of objects you intend to store in it. (See the
Generic Types section in Chapter 13.) You can only add objects of that type or types that inherit
from it, or implement it if the type declared is an interface. You can store value or reference types
in lists. The syntax for declaring a list is shown in Figure 14-7.
Lists are mapped to an object implementing System.Collections.Generic.IList on the .NET
platform and java.util.List on the JVM platform. Visual COBOL provides syntax to create lists;
to add, remove, and update elements; and to delete lists (this removes all the elements from a list,
but leaves the list object itself).
The square parentheses surrounding the type-declaration are part of the syntax
and not indicators of an optional item in this syntax diagram.
A list is a reference typeso before you can start using it, you must instantiate it, using the create
statement. When you initially create a list, it is empty. You can write to it using the write statement,
and you can read elements back with the read statement. Elements are stored in the list in the
order you write them, and you can retrieve individual elements by index. Lists use 0-based indices
onlylists are not available in earlier dialects of COBOL, so there is no need for backward compat-
ibility with 1-based indices. Use the size of operator to find out how many elements are stored in
a list. You can also overwrite the element at a particular index with a different one, or remove it (this
changes the position of all the subsequent elements in the list). Lists grow as you add elements
(new elements are added at the end of the list). You can also iterate over all the elements in a list
using the perform varying statement. The syntax for all the statements for working with lists is
fully documented in Chapter 15. Listing 14-11 shows a program with some examples of using lists.
01 aNumber binary-long.
read stringList into aString key 3 *> Read 4th item into aString
perform varying nextString as string through stringList
display nextString
end-perform
end class.
300
Dictionaries
Visual COBOL dictionaries are collections of key-value pairs, which can grow dynamically as
needed. A dictionary is a generic type; when you declare a dictionary, you must specify the type of
object for the key and the type of object for the value (keys and values do not have to be the same
type). Keys and values can be either value or reference types. The syntax for declaring a dictionary
is shown in Figure 14-8.
Dictionaries are mapped to an object implementing System.Collections.Generic.IDictionary
on the .NET platform and java.util.Map on the JVM platform. Visual COBOL provides syntax to
create dictionaries, add new key-value pairs, add or remove pairs, or update the value associated
with a particular key.
A dictionary is a reference type, so you must instantiate it before you can start using it, and a
newly created dictionary is zero length with no elements. The dictionary grows as you add ele-
ments, but there is no implied order to the elements. You can iterate over the keys in a dictionary
using the perform varying statement, but there is no guarantee as to the order in which the keys
will be returned. Each key in the dictionary is uniqueif you add key-value pairs with the same key
twice, the second value overwrites the first one. The syntax for all the statements for working with
dictionaries is fully documented in Chapter 15. Listing 14-4 shows a program with some examples
of using dictionaries.
The square parentheses surrounding the type-declaration are part of the syntax
and not indicators of an optional item in this syntax diagram..
*> You can detect the error condition that a key is not there
read stringDictionary into stringValue key Key 99
invalid key
display Key not found
not invalid key
display Key found
end-read
*> You can also use the square bracket indexer syntax to specify a key.
set stringValue to stringDictionary[Key 1]
rewrite stringDictionary from stringValue key Key 5
end method.
end class.
Keys
Dictionaries use the equals and hashcode methods of keys to store and retrieve keys. These methods
are implemented on object (the top-level parent for all types in Visual COBOL), which means you
can use any type as a key. However, the default methods for equality implemented by object are
based on the object reference. So, two objects are only equal if they are actually the same object.
Many objects in the .NET and Java frameworks override these methods to work sensibly with the
data they contain. For example, both platforms consider two strings with different object references
to be equal if they contain the same value.
302
The equals and hashcode methods must provide consistent results with each other, so if you
override one, you must override the other. If two objects are equal, they must return the same value
for their hashcode. However, two objects that return the same hashcode do not have to be equal.
Dictionaries work more efficiently if you have a good hashing mechanism that will return a good
range of values. A rule of thumb for starting to build a hash mechanism is to base your hash values
on some combination of all the fields inside the object you compare in your implementation of the
equality method.
There are quite a few subtleties to writing a good hash mechanism; for example, you cant have
the value of a hash mechanism change over the objects lifetimeif you use an object as a diction-
ary key while it has one hash value, and then the hash value changes as a result of some other
operation on the object, you now cant retrieve the element with the same key. Finally, any hashing
mechanism you write must run fast as it will get called often. Its probably a good idea to read some
articles on the web about implementing GetHashCode() or hashCode() before writing your own.
On the .NET platform, you must override the object::Equals(object) and object::GetHashCode()
methods. On JVM, you must override object::equals(object) and object::hashCode().
Summary
Visual COBOL provides a set of predefined data types for numbers, strings, arrays and collections
that work consistently on the .NET and JVM platforms, as well as providing backwards compatibility
with existing COBOL code.
C H A P T E R 15
Statements
Visual COBOL has a number of new statements not found in procedural COBOL, and some of
the statements from procedural COBOL have new functionality in Visual COBOL. For example,
the string, unstring, and inspect statements in Visual COBOL work directly with strings rather
than fixed-length display items. This chapter covers statements that are new in Visual COBOL, or
that have new functionality in Visual COBOL. It does not document all the statements that exist in
traditional procedural COBOL. This chapter defines the syntax for statements for:
Assignments
Transfer of control
Objects
Events
Collections and indexable objects
Exceptions
Strings
The statements are ordered alphabetically for ease of reference.
303
304
Attach
The attach verb registers an event handler against an event. You can see examples of the attach
verb in use in the Events section in Chapter 13. The syntax is shown in Figure 15-1.
There is more information about delegates, events, and method groups in the
Delegates section in Chapter 13.
An anonymous method is one written inline using delegate end-delegate. You can see an
example in Chapter 13, in the Anonymous Methods section.
Declare 305
Create
The create verb constructs an instance of a list or dictionary. The syntax is shown in Figure 15-2.
You can see examples of the create verb in Chapter 14, in the Lists and Dictionaries sections.
Declare
The declare statement enables you to declare local data items inside a method, property, indexer,
iterator, or constructor. The syntax is shown in Figure 15-3.
Data items are in scope from where they are declared from the point of declaration onward (no
forward references). They are only in scope within the block of code in which they are declared. By
block of code, we mean either the member in which the declaration appears, or within a perform
block, conditional block (if else statement block), trycatchfinally block, or anonymous method.
You can declare multiple items within the same statement, but they must all be of the same type,
and will all have the same value if assigned to the optional source-expression.
The as clause is optional if you have provided a source-expressionthe data items will all be
assigned the type returned by the source-expression. If there is no assignment, you must specify
a type with the as clause. The syntax for a type-specifier is defined in Chapter 13, in the Type
Specifier section.
If you specify an as clause and a source-expression, the source-expression must be type
compatible with the type-specifier (type compatibility is discussed in the Type Compatibility
section in Chapter 6).
Listing 15-1 shows some examples of the declare statement.
declare s4 as string
end-if
end method.
end class.
Delete
The delete verb enables you to remove an element from a list or dictionary. The syntax is shown
in Figure 15-4.
If source-expression-1 is a list, the key is the numeric index indicating the element to remove.
The index is 0-based (the first element is at index 0). After the operation, all subsequent elements
are moved up one position.
If source-expression-1 is a dictionary, the key is the key given when the key-value pair was
added to the dictionary.
The optional not invalid key and invalid key clauses enable you to execute different code
according to whether the specified index/key does not exist. If there is no invalid key clause, and
the index/key is not in the list/dictionary, delete throws an exception. Listing 15-2 shows an example
of the delete verb together with the invalid key and not invalid key clauses.
delete l key 0
invalid key
display element does not exist
not invalid key
display element 0 deleted
end-delete
delete d key I
end method.
end class.
Detach
The detach verb deregisters an event handler against an event. You can see examples of the detach
verb in use in the Events section in Chapter 13. The syntax is shown in Figure 15-5.
In Figure 15-5, delegate-instance is a source expression that returns a delegate matching the
method signature of the event-expression.
A method group is the list of overloaded methods from a specified object or type that match a
particular name. When you provide a method group as the target of an detach statement, the compiler
selects the method that has the best matching compatible signature to the event in event-expression.
A compatible signature is one with the same method name and parameters of the same or compatible
types.
There is more information about delegates, events, and method groups in the
Delegates section in Chapter 13.
Goback
The goback verb returns control from the method where it is executed to the calling code. If
themethod has a returning clause, the value returned is the value of the data item named in the
returning clause at the point of executing the goback. The syntax is shown in Figure 15-6.
If
The if statement provides structured conditional blocks. The syntax is shown in Figure 15-7.
Conditionals can be nested (statement-block can always start with another conditional).
By default, the compiler will allow you to omit the end-if phrase, but we strongly
recommend that you always include it in an effort to improve code readability
and avoid ambiguity.
Inspect
The inspect verb enables you to carry out search and replace operations on strings. There are
three main variations of inspect: inspect tallying (see Figure 15-8), inspect replacing (see
Figure 15-9), and inspect tallying replacing (see Figure 15-10).
The inspect tallying verb with the characters clause counts the number of characters in
string-source-expression-1. The before or after clause restricts the count to before or after the
first occurrence of string-source-expression-2.
The inspect tallying verb with the all or leading clause counts the number of times
string-source-expression-3 occurs in string-source-expression-1. The before or after clause
restricts the count to before or after the first occurrence of string-source-expression-4.
The result is always added to numeric-target-expression-2.
Inspect 309
The inspect replacing verb with the characters phrase replaces all characters in
string-source-target-expression-1 with the fi rst character in string-source-express ion-2. The
before or after clause restricts the replacement to before or after the first occurrence of
string-source-expression-3.
The inspect replacing verb with the all leading or first clause replaces occurences of
string-source-expression-4 inside string-source-target-expression-1 by string-source-expression-5.
String-source-expression-4 and string-source-expression-5 must be the same length. The
before or after clause restricts the replacement to before or after the first occurrence
of string-source-expression-6. The all phrase replaces all occurrences, the first phrase replaces
only the fi rst occurrence, and the leading phrase means that the replacement only occurs if
string-source-expression-3 occurs at the start of string-source-target-expression-1.
end class.
Invoke
The invoke verb enables you to invoke a method on an object, or a static method in a class. The
syntax is shown in Figure 15-11.
Using Invoke
In practice, the invoke verb is only needed when you want to invoke a method that either
does not return a value, or where you dont need the return value. Most of the time,
methods are invoked as part of an expression (see the Method Invocation Expression
section in Chapter 16). COBOL is unusual in having a verb to invoke a methodlanguages
like C# and Java do not. However, in COBOL, all statements must start with a COBOL
verb, so the invoke verb enables us to write a statement that invokes a method without
the invocation being part of some other expression.
Perform Using
The perform using statement is intended for use with objects that implement the Disposable
pattern. This is a pattern for use with classes that represent resources that need to be released
after use. For example, if you open a file to read it, you should close it again after you have finished
reading it. Figure 15-13 shows the syntax.
Listing 15-4 shows perform using; the example uses conditional compilation so that it will
compile and run on the JVM and .NET platforms.
method-id Dispose().
display Closing now
end method.
end class.
On .NET, if an exception thrown from within a perform using block will cause pro-
gram termination (that is the exception is not caught anywhere further back in
the stack), the Dispose() message does not get sent. However, if your program is
terminating, all resources are likely to be released at that point anyway.
To iterate through arrays and lists, you only need an identifier compatible with the type stored in
the array or list. You can declare an identifier inline using the as clause.
To iterate through a dictionary on the JVM platform, you must specify key, value , or both,
depending on what you want to iterate through. If you specify key and value, the keys and values
are returned in the pairs in which they were originally written to the dictionary. The identifier types
must be compatible with the key and value types used to define the original dictionary.
On the .NET platform, you have the option of either using the key and value words, or you can
omit them and iterate through the dictionary using KeyValuePair objects.
The key and value clauses can be specified in either order. Listing 15-5 has an example of
iterating through an array and a dictionary.
$if JVMGEN not defined *> Use KeyValue pairs on .NET only
perform varying nextPair as type KeyValuePair[string string]
through nameDict
display nextPair::Key space nextPair::Value
end-perform
$end
end method.
end class.
Raise
The raise verb enables you to raise (throw) an exception. The syntax is shown in Figure 15-15.
In most cases, the source-expression will be construction of a new exception object. The object
constructed must be a subclass of java.lang.Throwable on the JVM platform and System.Exception
on the .NET platform. However, on the JVM platform, your exception types would normally be expected
to be a subclass of java.lang.Exception.
At the point at which you raise an exception, the normal flow of program control is interrupted,
and the stack will unwind until the exception is caught inside a try... catch block.
See the Try Catch section later in this chapter for more info and examples.
You can omit source-expression only when the raise verb is inside a catch block. Then, it reraises
the exception caught by the catch phrase.
Reset Collection 315
Read Collection
The read verb can be used to read an element from a list or dictionary. The syntax is shown in
Figure 15-16.
Reset Collection
The reset verb removes all the elements from a list or dictionary and sets its size back to zero. The
objects stored are not directly affected; all that happens is their references are removed from the
collection that has been reset. However, if the only reference to an object was in the collection, it
will now be unreachable and will be garbage collected at some point in the future. The syntax is
shown in Figure 15-17.
Rewrite Collection
The rewrite verb can be used to change the value of an existing element in a list or dictionary.
Thesyntax is shown in Figure 15-18.
Set
The set verb is used to assign a value to a target-expression. The syntax is shown in Figure 15-19.
The set size of statement sets the size of a previously declared array. Repeated
numeric-source-expressions enable you to set a multidimensional array. The set content of state-
ment enables you to set the contents of a previously declared array. The table-content-expression
contains the contents of the array between parentheses. The format of a table-content-expression
Sort 317
is defined in the Table Of Expression section in Chapter16. You can see examples of arrays in the
Arrays section in Chapter 14.
The general form of the set statement is used for all other assignments. You can have multiple
target-expressions on the left-hand side of the set, in which case they are all set to the value of
source-expression. The type of source-expression must be compatible with the type of target-
expression, as explained in the Type Compatibility section in Chapter 6.
Sort
The sort verb enables you to sort the contents of an array. The syntax is shown in Figure 15-20.
An ascending sort is the default; the following two statements are equivalent:
sort anArray
sort anArray ascending
The descending keyword reverses the direction of the sort to be highest to lowest. The
duplicates phrase institutes a stable sortif there are several elements with the same sort value,
the order of the duplicate elements is preserved during the sort. On JVM, the sort is always stable
so the duplicates phrase has no effect.
You can specify that an array is sorted by its properties, using the on element-expression phrase.
Element-expression specifies a property of the element type stored in the array. You can invoke a
method on the property inside the element-expression as long as the method returns an object that
is sortable. An element-expression always starts with the keyword element. For example, to sort
an array of objects on their Name property, ignoring case:
If you dont explicitly specify a sort key, the elements in the source-expression are taken as the
sort keys. The sort keys must implement the System.IComparable interface on the .NET platform
and java.lang.Comparable interface on the JVM platform. Visual COBOL numeric types and
strings implement these interfaces and so an array of strings or numbers can be sorted. If you cre-
ate your own type and want its instances to be sortable, you must implement these interfaces. See
the Cross-Platform Date Code section in Chapter 8 for an example of this.
Listing 15-6 shows a program that sorts an array of people by name and then by birth year.
Because it is using the Person class Name and BirthYear properties as the sort keys, and they
318
are string and binary-long, respectively, the Person class itself does not need to implement
IComparable or Comparable.
class-id SORT01.
method-id main static.
declare unsorted = table of type Person(
new Person(prop BirthYear = 1948, prop Name = Cat Stevens),
new Person(prop BirthYear = 1955, prop Name = Kevin Costner),
new Person(prop BirthYear = 1952, prop Name = Vladimir Putin),
new Person(prop BirthYear = 1955, prop Name = Bill Gates),
new Person(prop BirthYear = 1948, prop Name = Kathy Bates),
new Person(prop BirthYear = 1956, prop Name = David Copperfield),
new Person(prop BirthYear = 1948, prop Name = Jean Reno))
perform varying p as type Person through unsorted
display p
end-perform
display By birth year...
declare work-array = unsorted(1:)
sort work-array ascending key element::BirthYear
perform varying p as type Person through work-array
display p
end-perform
display By name...
set work-array = unsorted(1:)
sort work-array ascending key element::Name
perform varying p as type Person through work-array
display p
end-perform
display By descending birth year...
set work-array = unsorted(1:)
sort work-array descending key element::BirthYear
perform varying p as type Person through work-array
display p
end-perform
display By ascending birth year, then descending name...
set work-array = unsorted(1:)
sort work-array ascending element::BirthYear descending key element::Name
perform varying p as type Person through work-array
display p
end-perform
display By birth year, duplicates in order...
set work-array = unsorted(1:)
sort work-array ascending key element::BirthYear duplicates in order
perform varying p as type Person through work-array
String 319
display p
end-perform
end method.
end class.
String
The string verb enables you to concatenate a series of strings together. You can also use the
concatenation operator & to do this, but the string verb has an extra option. The syntax is shown
in Figure 15-21.
You can have a number of source-expressions before the into keywordthese are concatenated
in order into the target-expression, replacing the current contents if there are any. The optional
delimited by phrase enables you to specify a delimiting string; this means the contents of
source-expression are only taken up to the first delimiter.
You can specify a delimiter clause on every source-expression if you want. Otherwise, the delimiter
clause applies to all preceding source-expressions. The delimited by size phrase enables you to
specify that you want the whole of the source-expression used (this is also the default in the absence
of any delimited phrases). Listing 15-7 shows an example using the string verb.
320
end method.
end class.
Sync
The sync statement enables you to mark a code block as a critical section. The syntax is shown
inFigure 15-22.
A critical section is a block of code that can only be executed from one thread at a time. Any other
threads that try to enter a critical section while it is already executing are forced to wait at the sync
statement until the currently executing thread leaves the critical section. The object represented
by source-expression is the mutex and is used as a flag to synchronize access. You can use any
object of any type as a mutex, and if you share the same mutex with several critical sections then if
any of those critical sections is being executed, all the others are blocked until the mutex is released.
You are advised not to use self as the mutex. A common pattern is to declare an instance vari-
able of type object and use that as the mutex. Listing 15-8 shows the sync statement used inside
a method.
method-id CriticalSection().
display outside critical section
sync mutex
add 1 to counter
end-sync
end method.
end class.
Try Catch
The try catch statement enables you to handle exceptions in your code. The syntax is shown
inFigure 15-23.
Each catch header includes an exception object reference. Although you can use an identifier to
a previously declared exception object, usual practice is to use the data-identifier as type-specifier
phrase to declare an exception object inline with the catch block. A catch block can only catch
exceptions that are compatible with the exception object declared in the catch header.
If an exception is raised in statement-block-1, each catch header is examined sequentially; the first
one that declares an exception type compatible with the one raised is the one that gets executed.
Where you have multiple catch blocks, you should start with the most derived type of exception
first. If you start with a catch block for processing exceptions of type Exception (the most general
type of exception), none of the other blocks will ever be reached.
If there is a finally header, statement-block-3 will always get executed whether an exception
has been raised or not. This makes statement-block-3 a good place for any cleanup code needed
after executing code in statement-block-1. However, because inside the finally block you dont know
whether an exception has been thrown or not, you dont know how much of statement-block-1 may
322
have executed, so you need to code defensively, in particular looking out for null references where
data items have not yet been initialized.
Exception Handling
Exceptions are a very powerful mechanism for handling errors and unexpected conditions in your
code. As soon as an exception is raised (most other languages use the word throw rather than
raise), the usual flow of execution is interrupted. If there is a suitable catch block in the current
method, execution resumes at the start of the catch block. Otherwise, the stack unwinds to the
caller of the current method, where there is another opportunity to catch the exception if there is a
suitable catch block. This process repeats until either the exception is caught or the program exits.
Listing 15-9 illustrates this process. The Main() method constructs an instance of TryClass and
invokes method One(). Method One() invokes method Two() from inside a try block. Method Two()
raises an exception of type FirstException. There is no catch block in method Two(), so control
passes back to method One(), which has a catch block for exceptions of this type.
The code in the catch block executes, followed by the code in the finally block. Code in the
finally block always executes whether or not an exception has been thrown, and whether or not it
was caught by a catch block. Because the exception has been caught and handled, execution now
proceeds from the end of the finally block in method One().
Method Three() is now invoked from inside another try block, but Method Three() attempts to
invoke a null object, which causes a null pointer exception. The catch block in method One() cant
catch this exception as a null pointer exception is not type compatible with our FirstException. This
means control passes straight back to the catch block in the Main() method, which does catch
the exception.
method-id One().
try
invoke Two ()
catch e as type FirstException
display Caught e
catch e as type SecondException
display Also caught e
Try Catch 323
finally
display finally
end-try
try
invoke Three()
catch e as type FirstException
display Wont catch null exception thrown from Three()
finally
display Finally
end-try
end method.
method-id Two().
display Two
raise new FirstException(Method went wrong)
display This code never gets reached
end method.
method-id Three().
declare tc as type TryClass = null
invoke tc::Three
end method.
end class.
end class.
324
Unstring
The unstring verb enables you to divide a string into smaller pieces marked by delimiters you
specify. The syntax is shown in Figure 15-24.
The delimited by phrase contains the separators to divide the string up with in source-expression-2.
Source-expression-2 must be a string. If you include the optional all phrase, unstring will count
any number of consecutive delimeters as a single separator.
Target-expression-1 must also be a string. The optional delimiter and count phrases enable
you to store the delimeter, and the count of delimeters in target-expression-2 (a string ) and
target-expression-3 (a binary-long), respectively. You can put a number of target-expressions
after the into keyword, and unstring will put one delimited string into each occurrence of
target-expression-1. However, if there are more delimited strings to be extracted then there are
target-expressions to write them to, unstring writes as many as it can and then completes. You
can use on overflow to get notification of this condition.
You can use unstring and the with pointer and not on overflow phrases to loop through
source-expression-1 unstringing one delimited string at a time. Source-target-expression-4 must
be a readable and writable numeric (usually a binary-long data-item), and at the start of unstring
execution, it points to the next delimited string, and it is incremented by 1 at the end. To start at the
beginning of source-expression-1, set source-target-expression-4 to 1 initially.
Listing 15-10 shows an example of unstring. The delimited by all space phrase means that
multiple spaces are counted as a single delimeter. Data item p is the pointer and always points to
the character position unstring will start from. Because we only have one target-expression for
unstring to write to (data item nextString), unstring overflows every time until we have extracted
the last delimited phrase from s1 . The count variable c has the length of nextString after each
operation, and t counts the number of strings extracted each time. When there are no more strings,
the not on overflow phrase executes the exit perform statement and we leave the perform loop.
Write 325
end class.
Write
The write verb enables you to add a value or key-value pair to a list or dictionary. The syntax is
shown in Figure 15-25.
When used with a list, the write verb adds a new element containing source-expression-2
to the end of the list, and there is no key clause. When used with a dictionary, you must use
the key clause. The value in source-expression-2 is written into the dictionary against key
source-expression-3. If the key already exists in the dictionary, the invalid key statement block is
executed if it has been included and an exception is raised if it has not.
You can see examples of writing to lists and dictionaries in Chapter 14.
326
Summary
Visual COBOL has a set of statements which are either not available in other dialects of COBOL,
or which have new functionality not available in other dialects. This chapter provided the reference
syntax for each of these, together with example code for most of them. The next chapter describes
all the different types of expression available in Visual COBOL, some of which were referred to in
this chapter.
C H A P T E R 16
Expression Types
In Chapter 15, we broadly categorized all expressions into two types:
source-expressions: These supply a value. Examples of source-expressions are data items,
literals, readable properties, or method invocations that return a value.
target-expressions: These require a value. Examples of target-expressions are data items,
or writable properties. Anything that goes on the left-hand side of a set statement must be
a target-expression.
Some expressions can be either a source or a target, depending on context. For example, a data
item can be put on either the left or right side of a set statement.
Expressions are often composed of other expressions, joined together by operators. The other
broad categorization of expressions is into:
primary-expression
conditional-expression
arithmetic-expression
327
328
Primary Expressions
This is the complete list of primary-expressions:
literal
data-item
self-access-expression
super-access-expression
parenthesized-expression
method-invocation-expression
non-parameterized-member-access
object-construction-expression
chained-constructor-expression
delegate-invocation-expression
anonymous-method-expression
indexed-expression
reference-modication-expression
concatenation-expression
conversion-expression
type-of-expression
method-group-expression
size-of-expression
table-of-expression
Literal Expressions
Literal expressions can be string, numeric, or null. The formats of numeric and literal expressions are
documented in Chapter 14, in the sections Numeric Literals and Nonnumeric Literals.
The keyword null represents a null-expression. Any data item that refers to an object reference
is set to null until it is explicitly set to refer to an instance of an object.
In most cases, you can omit self when accessing a member in a classif there
is no source-expression as the target of a member access, self is assumed.
class-id A public.
01 AField1 binary-long protected.
01 AField2 binary-long protected.
method-id One.
display Class A Method One
end method.
method-id Two.
declare c = new C(self)
end method.
end class.
class-id C public.
01 instanceData type A.
method-id New(arg as type A).
set instanceData to arg
end method.
end class.
Parenthesized Expressions
Any expression can be put inside parentheses (see Figure 16-2), either to override normal operator
precedence, to avoid ambiguity, or simply for clarity. Listing 16-3 and Listing 16-4 show parentheses
being used to override normal arithmetic operator precedence and to improve clarity, respectively.
declare n as binary-long
set n = 3 + 2 * 4 + 6 *> n = 17
set n = (3 + 2) * (4 + 6) *> n = 50
Use a type-specier (see Figure 16-5) for access to static members. See the Type Specier
section in Chapter 13 for information about type-speciers. A non-parameterized-member-access-
expression can be a target-expression if the member is a eld, settable property, or indexer.
332
A type-specifier that follows the new operator does not require you to include
the type keyword as is the case with type-specifiers used elsewhere. For
example, new Customer() is equivalent to new type Customer().
The source-expression must evaluate to a delegate. Delegates are covered in more detail in
Chapter 13, in the Delegates section, but Listing 16-5 shows an example of using a delegate to
get a result from a calculation.
end class.
Although you can use named parameters when dening and invoking delegates,
you cannot use default parameters.
334
Listing 16-6 shows an anonymous method being attached to an event in method Receiver:::Main().
delegate-id MicroFocus.COBOL.Book.Examples.MsgHandler
(msg as string).
end delegate.
class-id MicroFocus.COBOL.Book.Examples.Broadcaster.
01 eventSender type MsgHandler event public.
end class.
class-id MicroFocus.COBOL.Book.Examples.Receiver.
01 #label string.
method-id new(l as string).
set #label to l
end method.
Expression Types 335
attach
delegate (msg as string)
display anonymous method msg
end-delegate
to b::eventSender
end method.
end class.
Indexed Expressions
An indexed-expression enables you to specify a particular element in an indexable primary-expression.
Note that in Figure 16-9, the square parentheses in the rst option and the round parentheses in
the second option are required, and not part of the syntax diagram markup.
Indexable Items
Indexable items require one or more indices to refer to a particular element of the indexable item.
The following types are indexable:
Arrays: Each index is an integer (the number of indices must match the number of dimensions
of the array).
Types that dene an indexer (see the section Indexers in Chapter 13): Each index must be
of a type compatible with the type of the corresponding parameter dened by the indexer.
The following types have indexers on .NET, and on JVM, the compiler treats them as a special
case, enabling them to be used as though they had indexers:
Lists: Subscript is always a single integer.
Dictionaries: Subscript is a single index, which must be of a type compatible with the
dictionary key type.
Strings: Subscript is always a single integer and returns a character.
end class.
Concatenation Expressions
A concatenation-expression (see Figure 16-11) evaluates to a string that is the concatenated result
of the source-expressions. Because a source-expression can also be a concatenation-expression,
you can concatenate any number of items into one result. Any source-expression that is not a
string is converted to a string by calling the toString() or ToString() methods on JVM and .NET,
respectively.
Conversion Expressions
A conversion-expression (see Figure 16-12) uses the as operator to perform either an explicit or
implicit conversion of the type of the source-expression into the type of type-specier. This operation
will throw an error at run time if the conversion is not possible.
The behavior when casting from numeric types to enums on .NET and JVM is
different. On JVM, casting a numeric type to an enum that cannot represent the
particular value in that type will throw a run-time error; on .NET, no error is thrown.
338
Type Of Expressions
The type of operator (see Figure 16-13) returns a System.Type object on the .NET platform and
a java.lang.Class object on the JVM platform. These objects contain metadata about the type in
the type-of-expression. For more information, look for articles about reection on the Java or .NET
frameworks.
The keyword type can be omitted from the type-specifier when the
type-specifier is part of a type of expression.
Size Of Expressions
The size of operator (see Figure 16-15) returns the length of a string, array, list, or dictionary as
a binary-long.
Table Of Expressions
A table of expression enables you to specify an array of items. This enables you to create a table
inside a method invoke, for example. Figure 16-16 shows the syntax for a table-of-expression, and
Expression Types 339
Figure 16-17 shows that the table-items consist of round parentheses enclosing either a list of
source-expressions or a list of table-items.
You can explicitly specify the array type as part of the table-of-expression; otherwise, the compiler
will attempt to infer the type from the contents of the array. Listing 16-8 shows an example of
table-of-expressions.
end method.
end class.
340
Conditional Expressions
A simple-conditional-expression (see Figure 16-18) evaluates to a condition-value (either true or
false). A conditional-expression can consist of one or more simple-conditional-expressions chained
together using the and and or logical operators. You can also use parentheses to change the prece-
dence of evaluation of conditional-expressions, as shown in Figure 16-19 for conditional-expressions.
The unary not operator negates the value of a simple-conditional-expression or reverses the meaning
of a relational-operator, instance of, or contains operator.
Relational operators are listed in the next section. Source-expression-1 and source-expression-2
can be:
Numeric source-expressions
String source-expressions
Types for which suitable operator overloads exist (see the Operators section in Chapter 13)
Any kind of source-expression for compatible reference types, but only for the equality and
inequality relational operators. This will test that the two references point to the same object.
The instance of operator evaluates to true if source-expression-3 is of a type compatible with
type-specier, and false otherwise.
Source-expression-4 must be a list or a dictionary. If source-expression-4 is a list ,
source-expression-5 must be of a type compatible with the elements stored in the list. If
source-expression-4 is a dictionary, source-expression-5 must be of a type compatible with the
keys in the dictionary.
Arithmetic Expressions
Arithmetic expressions (see Figure 16-20) can be either unary or binary expressions. Arithmetic
operators are listed in the Arithmetic Operators section.
Operator Types
We have divided operators into unary and binary operators. Unary operators take precedence over
binary operators.
Unary Operators
Unary operators always precede a source-expression.
+
- (change sign)
b-not (binary notinverts binary digits)
not (conditional notchanges true to false and false to true)
Binary Operators
The types of binary operators are shown in precedence order below. The relational, instance of,
and type of operators have the same precedence.
Bitwise arithmetic operators
Arithmetic operators
Relational operators
Logical operators (and and or)
You can alter the precedence using round parentheses (see the Parenthesized Expressions
section). Unary operators take precedence over binary operators.
Arithmetic Operators
Arithmetic operators follow the usual rules of arithmetic precedence:
**
* /
+ -
342
Relational Operators
Relational operators all have equal precedence. Table 16-1 shows all the relational operators and
their alternatives.
Logical Operators
The logical operators enable you to combine conditional-expressions. They are:
and
or
Summary
This chapter shows the different types of expressions available in Visual COBOL, together with the
operators that work on them. One of the biggest differences between Visual COBOL and other
dialects of COBOL is that a compound expression (like an arithmetic expression) can be used
almost anywhere a single value could be used.
Index
343
344
date type, cross-platform programming 102116 wrapping .NET and JVM types 104114
cross-platform date code 114116 design goals 29
IDate interface 103104 desktop user interfaces
wrapping .NET and JVM types 104114 Java Swing UI 163180
AbstractDate class 106108 adding functionality to New Lease window
DateFactory class 112114 173180
DateJvm class 110112 adding New Lease form 172173
DateNet class 108110 displaying New Lease window 173
Debug menu (Visual Studio debugger) 21 handling Visual COBOL events 164167
Debug pane (Eclipse debugger) 28 installing WindowBuilder for Eclipse 163
declare statement 54, 305306 painting forms 166171
declaring Rental Agency application 143149
arrays 293 application architecture 148150
dictionaries 300 extending the application 149
lists 298 using the application 144147
declaring generic types 273 Windows Form UI 149162
dedicated administrator connection. SeeDAC adding forms to application 154155
default parameters 247 adding functionality to forms 156162
default shortcut keys displaying forms 155
Eclipse debugger functions 27 painting forms 151154
Visual Studio debugger functions 21 Visual Studio Forms Designer 150152
defining destructors 259
COBOL files 8384 detach statement 307
events, persistence 132136 detach verb 268
defining types dialects 3031
vsibility modifiers 67 dictionaries 7577, 300302
definition adding value/key-value pairs, write statement
classes 16 325326
delegate...end-delegate statement 304 create statement 305
delegate-instance source-expression 304 indexable items 336
delegate invocation expressions 332333 keys 301
delegates 3940, 266271 reset statement 315
adding functionality to forms 157160 rewrite statement 316
anonymous methods 270271 dictionary type 101
defining for events 132 directives
events 268270 ilcutprefix 91
Delete function 123 ilsmartlinkage 91
delete statement 306307 ilsmartnest 91
delimiter clause 319 ilsmartrestrict 91
dependencies 219220 ilsmartserial 92
.NET 222223 DisplayFormat() method 55
persistent RentalAgency 120 displaying
dependencies, between projects 18 dates 114116
Eclipse 2425 forms, Windows Form UI 155
Visual Studio 19 list box items 145
dependencies (JCM), managing with Maven 203205 DisplayValue() method 17
dependency diagram, UI 148 display verb 55
deregistering event handlers, detach statement 307 Disposable pattern
descending sorts 317 perform using statement 311
design .dll files 19
Agency Store, persistence 121122 DTO (Data Transfer Object) 193
date type 102116 dynamic assignment 192
cross-platform date code 114116 dynamic link libraries 219
IDate interface 103104 Dynamic Web Project folders 206
348
E IsValid() 52
method-invocation-expression 310
Eclipse 2329 numeric-source-expressions
creating projects and classes 4849 set statement 316
Eclipse debugger 2729 primary 327, 328
projects anonymous method 334335
importing and building 2324 chained constructors 332
launch configuration 2526 concatenation 337
running Hello World (quotes) 34 conversion 337
running Rental Agency application 183184 data item 328329
support for namespaces 59 delegate invocation 332333
WindowBuilder 143, 163 indexed 335336
Eclipse debugger 2729 literal 328
element-expression 317 method group 338
EndUpdate() method 159 method invocation 331
entry point non-parameterized member access 331
JVM platform 36 object construction 332
.NET 36 parenthesized 330331
enumerations 3940, 49, 273276 reference modification 336337
casting to/from 275 self 329
flag enumerations 276 size of 338
strings 276277 super 329330
equality operator 292 table of 338339
equals method 301 type of 338
errors source-expression 304
exception handling 322323 delegate-instance 304
EventBridge class 164 source-expressions 327
event handlers 136138, 268 source-target-expression 304
deregistering with detach statement 307 table-content-expressions
event keyword 236 set statement 316317
EventReceiver interface 164 target-expression 304
events 268270 assigning value with set statement 316
DateTimePicker 161 target-expressions 327
event handlers ExtendLease() method 72
deregistering with detach statement 307 extension methods 116117, 249250
fields as 238
Java Swing UI 164167
updating saved objects, persistence 131138
defining events 132136
F
event handlers 136138 factories 112
exception handling 322324 factory pattern technique 112
raising exceptions with raise statement 314 fields 235238
try catch statement 321322 as events 238
exceptions, checked exceptions 54 as properties 236237
.exe files 19 constants 237238
executable files (assemblies) 19 date class 46
explicit conversions 337 identifiers 236
explicit operator 259 intialize only phrase 238
expressions 16 ItemSelctor class 157
arithmetic 327, 340 privacy 49
conditional 340341 static members 237
condition-expression 308 file-control paragraph 83
conditonal 327 file description (fd) 83
element-expression 317 files
349
I
G IComparable interface 105
iDate interface, designing date type 103104
garbage collection 43, 257259 IDate objects 17
generic constraints paragraph 234 identifiers 229, 236
generic types 271273 Id field 72
generic using clause, classes 229 if/else/end compiler directives 104
generic using phrase 234 if statement 52, 308
GetDate() method 113 ilcutprefix directive 91
GetDate(years as binary-long months as binary-long ildasm tool 85, 87, 88
days as binary-long) method 113 ILGEN directives 31
GetDisplayValue() method 135 ilmain directive 36
GetDisplayValue() method 72 ilsmartlinkage directive 91
GetFormattedDate() method 116 ilsmartnest directive 91
GetInstance() method 97 ilsmartrestrict directive 91
GetLandlords() method 121 ilsmartserial directive 92
GetLeases() method 121 ilusing compiler directives 5758
GetProperties() method 70 ilusing directives 290
GetRentalProperties() method 121 implements constraints paragraph 235
GetStore() method 190 implicit conversions 337
GetTenants() method 121, 188 implicit operator 259
Global.asax editor 197 Import dialog box 23
goals, design 29 importing
goback statement 253 namespaces 5758
group compilation 213 Eclipse support 59
group items 80, 288290 Visual Studio support 58
compiler directives 80 importing projects
Eclipse 2324
Import Projects page (Eclipse) 24
Import Wizard 23
independent types 49
350
M DisplayValue() 17
EndUpdate() 159
main entry point equals 301
JVM platform 36 ExtendLease() 72
Main method 36 extension 116117, 249250
.NET 36 FindLandlord() 121, 126
main() method 85 FindLease() 121
Main method 36 FindProperty() 121
Main() method 85 FindTenant() 121
MainWindow class 149 GetDate() 113
ManagedCaller project 99 GetDate(years as binary-long months as binary-
managed code 3638 long days as binary-long) 113
calling native code 99100 GetDisplayValue() 72, 135
numeric types 283287 GetFormattedDate() 116
allocation rules for other numeric types GetInstance() 97
285286 GetLandlords() 121
floating-point numbers 285 GetLeases() 121
high-precision decimals 285 GetProperties() 70
Maven dependency management system GetRentalProperties() 121
managing JVM dependencies 203205 GetStore() 190
member modifiers 239 GetTenants() 121, 188
final modifier 239 HandleChangeEvent() 166
override clause 239241 HandleNewRecordEvent() 166
redefine clause 241 hashcode 301
members, classes. Seeclasses HomeController Index 200
memory mangement 43 Index() 202
message queueing middleware 196 instance 243
Messages class 148 invoke statement 310
method arguments invoking with method invocation expressions 331
linkage section and 82 IsActive() 72
method group 304, 307 IsAvailable() 74
method group expressions 338 IsCancelled() 72
method groups 266 isReadyToCreate() 174
method-invocation-expression 310 IsReadyToCreate() 160
method invocation expressions 331 IsValid() 52
method operator 338 LeaseProperty() 74
method overloading , 121122 LoadAllPersons() 127
Method P 30 LoadAllRentals() 128
353
LoadCache() 128
main() 85
N
Main 36 named parameters 247
Main() 85 name field 237
method signature and returning value 244249 namespaces 5760
OnChange() 133 importing 5758
OnShown() 156 Eclipse support 59
PersonToRecord() 126 Visual Studio support 58
Pop 272 Java and .NET conventions 5960
populateListBox() 176 Namespaces page, Virtual Studio 291
Push 272 native code 36
RecordToLease() 130 calling from managed code 99100
Register() 166 speed 38
RegisterUpdateable 137 NativeLibrary project 99
Save() 121 nested classes 92
SendEvent() 268 nested types 49, 279282
setBtnEnableState() 155, 172 managed code
SetFilenames 193 procedural COBOL interoperation 8589
SetFilenames() 198 namespaces
setVisible() 173 conventions 5960
snooze 263264 native COBOL data 7980
StartLease() 68 .NET 39
static 249 cross-platform programming
static Register() 166 extension methods 116117
StopRun() 98 writing portable code 101102
ToLower() 116 dependencies 222223
toLowerCase() 116 disassembly of Hello World 8687
toString() 55, 135 main entry point 36
ToString() 55, 135 namespace conventions 5960
ToUpper() 116 object model 39
toUpperCase() 116 procedural COBOL interoperation
transfer of control COBOL files 8385
goback statement 307308 COBOL records 80
Unregister() 166 copybooks 81
UpdateRecord() 136 linkage section 82
View() 200 native COBOL data 7980
virtual 40 procedural programs, manged code 8589
method signature 244249 REST service
Micro Focus dialect 30 implementing TenantsController 200202
Microsoft SQL Server. SeeSQL Server routing requests to controllers 198200
Models folder (NetWebLeases project) 197 setting application configuration 197198
modifiers 239 single inheritance 4042
by output 248 unsigned integers 284
final modifier 239 Web application development 183184
override clause 239241 Windows Form UI 149162
redefine clause 241 adding forms to application 154155
sharing parent 228, 281282 adding functionality to forms 156162
multidimensional arrays 296298 painting forms 151
multiple constructors 66 Visual Studio Forms Designer 150152
multiple phrase 278 .NET developers 8
multithreaded application servers 189191 NetWebLeases application 197
mutex 320 newable constraints paragraph 235
New ASP.NET Project dialog box 197
New COBOL JVM Class Wizard 49
354
New Dynamic Web Project dialog box 205 storage, persistence 120122
New JFrame Wizard 166 saving objects 123128
new keyword 16 transferring to records 125126
New Launch Configuration button 25 updating saved objects using events 131138
NewLease class 149 defining events 132136
New Lease dialog box 147 event handlers 136138
New Lease Form (Windows) 147 OK button 174
new operator 54 OnChange() method 133
NewPerson class 149 OnShown() method 156
New Project dialog box on size error phrase 286
Eclipse 49 opening
Visual Studio 47 Visual Studio projects 19
nonnumeric literals Open Project dialog box 19
strings 293 operation precedence, parenthesized expressions
non-parameterized member access expressions 331 330
not invalid key clause 306 operators 259262, 341
null keyword 328 as 337
numeric literals 287 binary 341
numeric-source-expressions concatenation 319
set statement 316 equality 292
numeric types 283287 inequality 292
allocation rules for other numeric types 285286 method 338
floating-point numbers 285 new 54
high-precision decimals 285 size of 338
numeric literals 287 type of 338
overflow conditions 286 unary 341
or logical operator 342
overflow conditions 286
The Future of
W N L
D
COBOL is Here
TUNE YOUR SKILLS AND GAIN EXPERIENCE
WITH THE NEXT GENERATION IN COBOL
DEVELOPMENTVISUAL COBOL.
Visual COBOL Personal Edition (PE) is here. Work within the industry-standard
IDE of your choice. Take full advantage of the new capabilities available to you
within this innovative development environment. Visual COBOL PE integrates
with Microsoft Visual Studio and Eclipse giving you the choice to develop COBOL
applications using the worlds most popular integrated development environments.
This is your opportunity to learn the enterprise language behind 70% of todays
business transactions.
Visual COBOL Personal Edition has the same features as the commercial
version. These include:
Procedural and object-oriented COBOL development
Build COBOL applications for native code, .NET and the Java VirtualMachine
Mixed language interoperability with Java and C#
Code assist tools such as Intellisense and auto-complete
Visual Studio designer support for creating WPF, Winforms and ASP.NET
applications
www.microfocus.com/products/visual-cobol/personal-edition
Bridging the Old and the New
Forget what you think you know about COBOL. Even though the
language is more than 50 years old, COBOL applications still reign in
Visual
the world of enterprise IT. With billions of transactions executed every
day and often running behind the scenes, COBOL systems touch many
aspects of our daily lives. Your mission: to start a new era of innovation
Visual COBOL
powered by modern tools that bridge COBOL systems to the world of
Java and .NET. Brought to you by the leader in COBOL development
COBOL
tools, this book is written for the COBOL, Java, and .NET developer.
Key features include:
+ A simplified, real-world example to illustrate key concepts
+ An explanation of the .NET and Java object models for the
COBOL developer
+ An introduction to COBOL for the Java or .NET developer
A DEVELOPER S GUIDE
+ A complete reference to the new syntax for Visual COBOL
TO MODERN COBOL
+ A free student development tools license integrated within
Visual Studio and Eclipse
12 12
P R E S S PRESS
boxtwelve