Exception handling in PowerBuilder
When a runtime error occurs
in a PowerBuilder application, unless that error is trapped, a single
application event (SystemError) fires to handle the error no matter
where in the application the error happened. Although some errors
can be handled in the system error event, catching the error closer
to its source increases the likelihood of recovery from the error
condition.
You can use exception-handling classes and syntax to handle
context-sensitive errors in PowerBuilder applications. This means
that you can deal with errors close to their source by embedding
error-handling code anywhere in your application. Well-designed
exception-handling code can give application users a better chance
to recover from error conditions and run the application without interruption.
Exception handling allows you to design an application that
can recover from exceptional conditions and continue execution.
Any exceptions that you do not catch are handled by the runtime
system and can result in the termination of the application.
PowerBuilder clients can catch exceptions thrown from application
server components and recover from them. Components developed with PowerBuilder
can also define and throw their own exception types, making them
more consistent with other server component types like Java.
Exception handling can be found in such object-oriented languages
as Java and C++. The implementation for PowerBuilder
is similar to the implementation of exception handling in Java.
In PowerBuilder, the TRY, CATCH, FINALLY, THROW,
and THROWS reserved words are used for exception
handling. There are also several PowerBuilder objects that descend
from the Throwable object.
Basics of exception handling
Exceptions are objects that are thrown
in the event of some exceptional (or unexpected) condition or error
and are used to describe the condition or error encountered. Standard
errors, such as null object references and division by zero, are
typically thrown by the runtime system. These types of errors could occur
anywhere in an application and you can include catch clauses
in any executable script to try to recover from these errors.
User-defined exceptions
There are also exceptional conditions that do not immediately
result in runtime errors. These exceptions typically occur during
execution of a function or a user-event script. To signal these
exceptions, you create user objects that inherit from the PowerScript
Exception class. You can associate a user-defined exception with
a function or user event in the prototype for the method.
For example, a user-defined exception might be created to
indicate that a file cannot be found. You could declare this exception
in the prototype for a function that is supposed to open the file.
To catch this condition, you must instantiate the user-defined exception
object and then throw the exception instance
in the method script.
Objects for exception handling support
Several system objects support exception handling within PowerBuilder.
Throwable object type
The object type Throwable is the root
datatype for all user-defined exception and system error types.
Two other system object types, RuntimeError and Exception, derive
from Throwable.
RuntimeError and its descendants
PowerBuilder runtime errors are represented in the RuntimeError object
type. For more robust error-handling capabilities, the RuntimeError
type has its own system-defined descendants; but the RuntimeError
type contains all information required for dealing with PowerBuilder
runtime errors.
One of the descendants of RuntimeError is the NullObjectError
type that is thrown by the system whenever a null object reference
is encountered. This allows you to handle null-object-reference
errors explicitly without having to differentiate them from other
runtime errors that might occur.
Error types that derive from RuntimeError are typically used
by the system to indicate runtime errors. RuntimeErrors can be caught
in a try-catch block, but it is not necessary to declare where such
an error condition might occur. (PowerBuilder does that for you,
since a system error can happen anywhere anytime the application
is running.) It is also not a requirement to catch these types of
errors.
Exception object type
The system object Exception also derives
from Throwable and is typically used as an ancestor object for user-defined
exception types. It is the root class for all checked exceptions. Checked
exceptions are user-defined exceptions that must be
caught in a try-catch block when thrown, or that must be declared in
the prototype of a method when thrown outside of a try-catch block.
The PowerScript compiler checks the local syntax where you
throw checked exceptions to make sure you either declare or catch
these exception types. Descendants of RuntimeError are not checked
by the compiler, even if they are user defined or if they are thrown
in a script rather than by the runtime system.
Handling exceptions
Whether an exception is thrown by the runtime system or by
a THROW statement in an application script, you
handle the exception by catching it. This is done by surrounding
the set of application logic that throws the exception with code
that indicates how the exception is to be dealt with.
TRY-CATCH-FINALLY block
To handle an exception in PowerScript, you must include some
set of your application logic inside a try-catch block. A try-catch
block begins with a TRY clause and ends with
the END TRY statement. It must also contain either
a CATCH clause or a FINALLY clause.
A try-catch block normally contains a FINALLY clause
for error condition cleanup. In between the TRY and FINALLY clauses
you can add any number of CATCH clauses.
CATCH clauses are not obligatory, but if
you do include them you must follow each CATCH statement
with a variable declaration. In addition to following all of the
usual rules for local variable declarations inside a script, the
variable being defined must derive from the Throwable system type.
You can add a TRY-CATCH-FINALLY, TRY-CATCH,
or TRY-FINALLY block using the Script view Paste
Special feature for PowerScript statements. If you select
the Statement Templates check box on the AutoScript tab of the Design Options
dialog box, you can also use the AutoScript feature to insert these block
structures.
Example
Example catching a system error This is an example of a TRY-CATCH-FINALLY block
that catches a system error when an arccosine argument,
entered by the application user (in a SingleLineEdit) is not in
the required range. If you do not catch this error, the application
goes to the system error event, and eventually terminates:
1 |
Double ld_num<br />ld_num = Double (sle_1.text)<br />TRY<br /> sle_2.text = string (acos (ld_num))<br />CATCH (runtimeerror er) <br /> MessageBox("Runtime Error", er.GetMessage())<br />FINALLY <br /> // Add cleanup code here <br /> of_cleanup() <br /> Return<br />END TRY <br />MessageBox("After", "We are finished.") |
The system runtime error message might be confusing to the
end user, so for production purposes, it would be better to catch
a user-defined exception—see the example in “Creating user-defined exception
types”—and
set the message to something more understandable.
The TRY reserved word signals the start
of a block of statements to be executed and can include more than
one CATCH clause. If the execution of code in
the TRY block causes an exception to be thrown,
then the exception is handled by the first CATCH clause
whose variable can be assigned the value of the exception thrown.
The variable declaration after a CATCH statement
indicates the type of exception being handled (a system runtime
error, in this case).
CATCH order
It is important to order your CATCH clauses
in such a way that one clause does not hide another. This would
occur if the first CATCH clause catches an exception
of type Exception and a subsequent CATCH clause
catches a descendant of Exception. Since they are processed in order,
any exception thrown that is a descendant of Exception would be
handled by the first CATCH clause and never by
the second. The PowerScript compiler can detect this condition and
signals an error if found.
If an exception is not dealt with in any of the CATCH clauses,
it is thrown up the call stack for handling by other exception handlers
(nested try-catch blocks) or by the system error event. But before
the exception is thrown up the stack, the FINALLY clause
is executed.
FINALLY clause
The FINALLY clause is generally used to
clean up after execution of a TRY or CATCH clause.
The code in the FINALLY clause is guaranteed
to execute if any portion of the try-catch block is executed, regardless
of how the code in the try-catch block completes.
If no exceptions occur, the TRY clause
completes, followed by the execution of the statements contained
in the FINALLY clause. Then execution continues
on the line following the END TRY statement.
In cases where there are no CATCH clauses
but only a FINALLY clause, the code in the FINALLY clause
is executed even if a return is encountered or an exception is thrown
in the TRY clause.
If an exception occurs within the context of the TRY clause
and an applicable CATCH clause exists, the CATCH clause
is executed, followed by the FINALLY clause.
But even if no CATCH clause is applicable to
the exception thrown, the FINALLY clause still
executes before the exception is thrown up the call stack.
If an exception or a return is encountered within a CATCH clause,
the FINALLY clause is executed before execution
is transferred to the new location.
Creating user-defined exception types
You can create your own user-defined exception types from
standard class user objects that inherit from Exception or RuntimeError
or that inherit from an existing user object deriving from Exception
or RuntimeError.
Inherit from Exception object type
Normally, user-defined exception types should inherit from
the Exception type or a descendant, since the RuntimeError type
is used to indicate system errors. These user-defined objects are
no different from any other nonvisual user object in the system.
They can contain events, functions, and instance variables.
This is useful, for example, in cases where a specific condition,
such as the failure of a business rule, might cause application
logic to fail. If you create a user-defined exception type to describe
such a condition and then catch and handle the exception appropriately,
you can prevent a runtime error.
Throwing exceptions
Exceptions can be thrown by the runtime engine to indicate
an error condition. If you want to signal a potential exception
condition manually, you must use the THROW statement.
Typically, the THROW statement is used
in conjunction with some user-defined exception type. Here is a
simple example of the use of the THROW statement:
1 |
Exception le_ex<br />le_ex = create Exception<br />Throw le_ex<br />MessageBox ("Hmm", "We would never get here if" & <br /> + "the exception variable was not instantiated") |
In this example, the code throws the instance of the exception le_ex.
The variable following the THROW reserved word
must point to a valid instance of the exception object that derives
from Throwable. If you attempt to throw an uninstantiated Exception
variable, a NullObjectError is thrown instead, indicating a null
object reference in this routine. That could only complicate the error
handling for your application.
Declaring exceptions thrown from functions
If you signal an exception with the THROW statement
inside a method script—and do not surround the statement
with a try-catch block that can deal with that type of exception—you
must also declare the exception as an exception type (or as a descendant
of an exception type) thrown by that method. However, you do not
need to declare that a method can throw runtime errors, since PowerBuilder
does that for you.
The prototype window in the Script view of most PowerBuilder
painters allows you to declare what user-defined exceptions, if
any, can be thrown by a function or a user-defined event. You can
drag and drop exception types from the System Tree or a Library
painter view to the Throws box in the prototype window, or you can
type in a comma-separated list of the exception types that the method
can throw.
Example
Example catching a user-defined exception This code displays a user-defined error when an arccosine argument,
entered by the application user, is not in the required range. The
try-catch block calls a method, wf_acos,
that catches the system error and sets and throws the user-defined
error:
1 |
TRY <br /> wf_acos() |
1 |
CATCH (uo_exception u_ex) <br /> MessageBox("Out of Range", u_ex.GetMessage())<br />END TRY |
This code in the wf_acos method
catches the system error and sets and throws the user-defined error:
1 |
uo_exception lu_error<br />Double ld_num<br />ld_num = Double (sle_1.text)<br />TRY<br /> sle_2.text = string (acos (ld_num))<br />CATCH (runtimeerror er) <br /> lu_error = Create uo_exception<br /> lu_error.SetMessage("Value must be between -1" &<br /> + "and 1")<br /> Throw lu_error<br />END TRY |
Integration with EAServer
If you declare exceptions on a method of a user object and
deploy the user object as a component to EAServer,
the exceptions are translated to IDL (CORBA) as part of the method
prototype. This means that PowerBuilder components in EAServer can be defined to throw
exceptions that can be handled by any type of EAServer client
application.
Other benefits for EAServer applications Another benefit for component development is that you can
handle runtime errors in the component. If you do not handle an
error, it is automatically translated into an exception and the component
stops executing.
PowerBuilder client applications that use EAServer components can handle exceptions
thrown by any type of EAServer component.
If a Java EAServer component has
a method on it that is defined to throw an exception and a PowerBuilder
proxy is created to use that component, the method on the PowerBuilder
proxy is also declared to throw a user-defined exception. The definition
of the user-defined exception is created automatically at the time
of the PowerBuilder proxy creation.
For more information about error handling in EAServer clients, see “Handling errors “.
IDL restrictions Deployment of components to EAServer imposes restrictions
on the way you can use exception handling within PowerBuilder. Only
the public instance variables defined on the exception type are
actually translated to IDL. This is because IDL exceptions cannot
have methods declared on them. Therefore if the exception type has
methods defined on it, those methods can be called within the execution
of the component but cannot be called by client applications that
catch the exception thrown.
You must keep this restriction in mind when designing exception
objects for distributed applications, exposing all exception information
as public instance variables instead of through accessor methods
on an exception object.
Two other interface restrictions also apply to exception types
of a user object that is deployed as an EAServer component.
Instance variables of exceptions on the user object methods cannot
have object datatypes. Null data is supported only for instance
variables with simple datatypes; if instance variables are structures
or arrays, null values for individual elements are not maintained.
Adding flexibility and facilitating object reuse
You can use exception handling to add flexibility to your
PowerBuilder applications, and to help in the separation of business
rules from presentation logic. For example, business rules can be
stored in a non-visual object (nvo) that has:
- An
instance variable to hold a reference to the presentation object:1powerobject my_presenter - A function that registers the presentation object
The registration function could use the following syntax:1SetObject (string my_purpose, powerobject myobject) - Code to call a dynamic function implemented by the
presentation object, with minimal assumptions about how the data
is displayed
The dynamic function call should be enclosed in a try-catch
block, such as:1TRY <br /> my_presenter.Dynamic nf_displayScreen(" ")<br /> CATCH (Throwable lth_exception) <br /> Throw lth_exception<br />END TRYThis try-catch block catches all system and user-defined errors
from the presentation object and throws them back up the calling
chain (to the object that called the nvo). In the above example,
the thrown object in the CATCH statement is an
object of type Throwable, but you could also instantiate and throw
a user exception object:1uo_exception luo_exception<br /> <br />TRY <br /> my_presenter.Dynamic nf_displayScreen(" ")<br />CATCH (Throwable lth_exception) <br /> luo_exception = Create uo_exception<br /> luo_exception.SetMessage & +<br /> (lth_exception.GetMessage())<br /> Throw luo_exception<br />END TRY
Code for data processing could be added to the presentation
object, to the business rules nvo, or to processing objects called
by the nvo. The exact design depends on your business objectives,
but this code should also be surrounded by try-catch blocks. The
actions to take and the error messages to report (in case of code
processing failure) should be as specific as possible in the try-catch
blocks that surround the processing code.
There are significant advantages to this type of approach,
since the business nvo can be reused more easily, and it can be
accessed by objects that display the same business data in many
different ways. The addition of exception handling makes this approach
much more robust, giving the application user a chance to recover
from an error condition.
Using the SystemError and Error events
Error event
If a runtime error occurs, an error structure that describes
the error is created. If the error occurs in the context of a connection
to a remote server (such as EAServer) then the Error event on the
Connection, JaguarORB, DataWindow, or OLE control object is triggered,
with the information in the error structure as arguments.
The error can be handled in this Error event by use of a special
reference argument that allows the error to be ignored. If the error
does not occur in the context described above, or if the error in
that context is not dealt with, then the error structure information
is used to populate the global error variable and the SystemError
event on the Application object is triggered.
SystemError event
In the SystemError event, unexpected error conditions can
be dealt with in a limited way. In general, it is not a good idea
to continue running the application after the SystemError event
is triggered. However, error-handling code can and should be added
to this event. Typically you could use the SystemError event to
save data before the application terminates and to perform last-minute cleanup
(such as closing files or database connections).
Precedence of exception handlers and events
If you write code in the Error event, then that code is executed
first in the event of a thrown exception.
If the exception is not thrown in any of the described contexts
or the object’s Error event does not handle the exception
or you do not code the Error event, then the exception is handled
by any active exception handlers (CATCH clauses)
that are applicable to that type of exception. Information from
the exception class is copied to the global error variable and the
SystemError event on the Application object is fired only if there
are no exception handlers to handle the exception.
Error handling for new applications
For new PowerBuilder applications, the recommended approach
for handling errors is to use a try-catch block instead of coding
the Error event on Connection, DataWindow, or OLE control objects.
You should still have a SystemError event coded in your Application
object to handle any uncaught exceptions. The SystemError event
essentially becomes a global exception handler for a PowerBuilder
application.