Application Classes General Structure

This section discusses the general form of an application class. All application classes are contained in application packages; and all application classes are composed of executable PeopleCode statements.

The division of application classes facilitates object-oriented programming and enables a separation of the following:

  • What the class provides to other classes.

  • What is applicable to the entire class.

  • What is applicable to the definition of a method.

The data types used in an application class (for methods parameters, return values, and so on) can be any PeopleCode types, including application classes. Likewise, application classes can be used anywhere in a PeopleCode program where a general data type can be used.

Application classes have a fully qualified name that is formed hierarchically by the name of the top-level package that contains them, the package that contains that package, and so on down to the short class name, that is, the one specified when the class was created in Application Designer, using a colon for the separator between names.

The fully qualified name of the class must be unique, as this is what identifies the class. The short name of the class does not have to be unique.

Image: Application classes example

For example, suppose package CRM contains the application classes Address and Customer as well as the package Utilities, which in turn contains the application classes Address and Incident:

Application classes example

There are four distinct application classes in this example:

  • CRM:Address

  • CRM:Customer

  • CRM:Utilities:Address

  • CRM:Utilities:Incident

Note: If you change the name of a package or an application class, that name change is not automatically propagated through all your PeopleCode programs. You must make the change manually in your programs. If you specify an incorrect name for a package or application class, you receive a warning when you try to save the PeopleCode.

Extension represents the "is-a" relationship. When a class extends another class, it's called a subclass of that class. Conversely, the class being extended is called a superclass of that subclass.

A subclass inherits all of the public methods and properties (collectively called members) of the class it extends. These members can be overridden by declarations of methods and properties in the subclass.

Note: Application classes have no multiple inheritance (that is, being able to extend more than one class.)

Type checking in PeopleCode (both at design time and runtime) does strong type checking of application classes, tracking each application class as a separate type. A subclass can be used as the class it extends, because it implements the public interfaces of its superclass. This is called subtyping.

In the following example, the class Banana extends the class Fruit. Banana is the subclass of Fruit. From Fruit, you can extend to Bananas. However, from Bananas you can't extend to Fruit. Another way to think about this is you can only extend from the general to the specific, not the other way around.

class Fruit
   method DoFruit();
   property number FruitNum instance;
end-class;

class Banana extends Fruit
   method DoBanana();
   property number BananaNum instance;
end-class;

The following code shows a correct way to assign a class object, because Banana is a subtype of Fruit.

local Fruit &Bref = create Banana(); 

The following is not correct. Banana is a subtype of Fruit. Fruit is not a subtype of Banana. This assignment causes an error at design time.

local Banana &Fref = create Fruit(); 

Before you can extend a class, you must first import it. You must import all class names you use in a PeopleCode program before you use them.

The names of the members (that is, all the methods and properties of a class) must be unique only within that class. Different classes can have members with the same name. As members are used only with objects, and each object knows its class, the member that's accessed is the one associated with that class.

For example, more than one class has the property Name which returns the name of the object executing the property. There isn't any confusion, because the field Name property returns the name of a field, while the record name property returns the name of a record. No class has more than one property called Name.

Note: Within a class, each member name must be unique.

The public part of a class declaration specifies the methods and properties that the class provides to other PeopleCode programs. These methods and properties are dynamically bound, that is, the actual method that is called depends on the actual class of the object, as it might be an overridden method or property from a subclass.

In the following code example, the text in bold indicates the public part of the class declaration.

/* generic building class */
class BuildingAsset
   method Acquire();
   method DisasterPrep();
end-class;

Application class objects have private and public access control. Properties or methods that are declared outside the “private” declaration section have public access control. This means that they can be referenced and used by any PeopleCode. This access is of course subject to access controls such as “readonly”. Private properties and methods are private to the class, not instance, and can only be referenced by objects of this application class.

Between these two access control schemes of public and private, lies the concept of protected methods and properties. Protected methods and properties can be accessed only by objects of this application class and those derived from this application class. Use protected methods or properties when you want to hide them from outside use, but allow the flexibility of using them in derived classes.

The declarations of protected variables and methods are done within the class declaration, before the declaration of private variables and methods. You can use protected instance variables in interface classes. Protected methods and properties can be overridden by subclasses.

Most of the time your design can be implemented through the use of private methods and properties without resorting to the use of protected methods and properties. The following examples demonstrates the rules and some of the subtleties about when to use protected methods and properties.

class A; 
   method A(); 
   property string Q; 
protected 
   method MP() Returns string; 
   property string p; 
end-class; 

method A; 
   &p = "Class A: property p"; 
end-method; 

method MP 
   /+ Returns String +/ 
   Return "Class A: method MP"; 
end-method; 

==================== 

class B extends A; 
   method B(); 
   method M(&X As C); 
   method m2(&Aobj As A); 
   /* property string P; */ 
protected 
   property string Q; 
   method MP() Returns string; 
end-class; 

method B; 
   %Super = (create A()); 
   &Q = "Class B: property Q"; 
   /* &P = "Class B: property P";*/ 
end-method; 

method M 
/+ &X as FOXTEST:C +/; 

   MessageBox(0, "", 0, 0, "In B:M %This.P=" | %This.p); 
/* %This.p is class A property P */
   MessageBox(0, "", 0, 0, "In B:M %This.Q=" | %This.Q); 
/* %This.q is class B property Q */
   MessageBox(0, "", 0, 0, %This.MP()); 
/* %This.MP() calls class B method MP */

   if &X.p = "Error" then 
   end-if;
/* error: cannot reference &X.p since class B is 
not involved in the implementation of class C */
end-method;

method m2 
   /+ &Aobj as FOXTEST:A +/ 
/* MessageBox(0, "", 0, 0, "In B:M2 &Aobj.P=" | &Aobj.p); 
/* Error: cannot reference &Aobj.p from class B 
since class B is not involved in its implementation */

end-method;

method MP 
   /+ Returns String +/ 
   Return "Class B: method MP"; 
end-method; 

======================= 

class C extends A; 
   method C(); 
protected 
   property string P; 
end-class; 

method C; 
   %Super = (create A()); 
   &P = "Class C: property P"; 
end-method; 
========================== 

/* AE program */ 
import FOXTEST:*; 

Local A &A = (create A()); 

Local object &obj = &A; 
MessageBox(0, "", 0, 0, "In AEMINITEST: &Obj.p=" | &obj.P); 
/* run time error: anonymous access through type 
object not allowed*/

The following example also illustrates some of the rules surrounding protected access control.

import FOXTEST:Point3d;

class Point
   method Point(&X1 As integer, &Y1 As integer);
   method Warp(&A As Point3d);
protected
   property integer x;
   property integer y;
end-class;

method Point
   /+ &X1 as Integer, +/
   /+ &Y1 as Integer +/;
   
   %This.x = &X1;
   %This.y = &Y1;
end-method;

method Warp
   /+ &A as FOXTEST:Point3d +/
   /* Local Integer &temp = &A.Z;  ERROR cannot access &A.Z */
end-method;
================================
import FOXTEST:Point;

class Point3d extends Point
   method Point3d(&X1 As integer, &Y1 As integer, &Z1 As integer);
   method Delta(&P As Point);
   method Delta3d(&Q As Point3d);
protected
   property integer z;
end-class;

method Point3d
   /+ &X1 as Integer, +/
   /+ &Y1 as Integer, +/
   /+ &Z1 as Integer +/;
   %Super = (create Point(&X1, &Y1));
   %This.z = &Z1;
end-method;

method Delta
   /+ &P as FOXTEST:Point +/

/* &P.x = %This.x; 
ERROR cannot access &P.x since while Point3d 
(the class in which references to fields x and y occur) 
is a subclass of Point (the class in which x and y are declared), 
it is not involved in the implementation of Point (the type of parameter p)*/

/* &P.y = %This.y; 
ERROR cannot access &P.y. Same reason as above */
end-method;

method Delta3d
   /+ &Q as FOXTEST:Point3d +/

/* The protected members of Q can be accessed because the class point3d is a subclass of Point and is involved in the implementation of Point3d */
   &Q.x = %This.x; 
   &Q.y = %This.y;
   &Q.z = %This.z;
   
end-method;

The private part of a class declaration gives the declaration of any private methods, instance variables, and private class constants. Private methods cannot be overridden by subclasses, because they are completely private to the declaring class. Likewise, the instance variables can't be overridden.

The declarations of private variables and methods are done within the class declaration.

Note: You cannot have private methods or instance variables in an interface type of class.

In the following example, there are both public as well as private methods and properties. Any method that is declared before the keyword Private in the initial class declaration is public. Any method declared after the keyword Private is private. Properties are only declared in Public. Instances and Constants are only declared in Private

In the following example, the class Example extends the class ExampleBase. It has both public as well as private methods and properties. It also defines the private method in the definition of methods section of the code (the private definitions are marked like this.)

import PWWPACK:ExampleBase;

class Example extends ExampleBase
   method Example();
   method NumToStr(&Num As number) Returns string;
   method AppendSlash();
   property number SlashCount get;
   property number ImportantDayOfWeek get set;
   property string SlashString readonly;
   property date ImportantDate;
private
   method NextDayOfWeek(&Dow As number) Returns date;
   Constant &Sunday = 1;
   instance string &BaseString;
end-class;

Global string &CurrentBaseString;

/* Method definitions  */
method NumToStr
   return String(&num);
end-method;

method AppendSlash
   &SlashString = &SlashString | "/";
end-method;

get SlashCount    
   Return Len(&SlashString) - Len(&BaseString);
end-get;

get ImportantDayOfWeek
   Return Weekday(&ImportantDate);
end-get;

set ImportantDayOfWeek   
   &importantdate = %This.nextdayofweek(&newvalue);
end-set;

/* Private method. */
method nextdayofweek
   Return &ImportantDate + (&dow - Weekday(&ImportantDate));
end-method;

/* Constructor. */
method Example
   &BaseString = &CurrentBaseString;
   &SlashString = &BaseString;
   &ImportantDate = Date(19970322);
end-method;

After the declaration of any global or component variables and external functions needed by the methods, comes the actual definition of the methods. This section discusses some of the differences in how application programs are processed by the system, and how application class method parameters are processed.

  • The system never skips to the next top-level statement.

  • Application programs generally pass parameters by value, which is not the same as for existing functions.

  • Parameter passing with object data types is by reference.

  • Application programs use the out specifier to pass a parameter by reference.

Not Skipping to The Next Top-Level Statement

For existing functions and programs, the system skips to the next top-level statement in some circumstances, such as a field not found error. This never happens in an application class program. The code is always executed or an error is produced.

Passing Parameters in Application Class Methods

The parameters to a method are passed by value in the absence of an out specification in the parameter list. However, if a parameter list has an out specification, that argument (when used in the call of a method) must be a value that can be assigned something, and is passed by reference. In particular this means you cannot pass an object property to a method which requires the parameter to be an "out" parameter. Application class properties are always passed by value.

For example:

/* argument passed by reference */
method increment(&value as number out);


/* argument passed by value */
method increment(&value as number);

This is in contrast with existing PeopleCode functions that pass all parameters by reference when they can be assigned something. This means the method signature is a specification of which parameters can be updated by the method. This makes the code easier to debug and maintain because it's easy to see what can be updated and what can't.

The following is an example of code that increments a variable by one.

local Number &Mine;
&Mine = 1;
&obj.Increment(&Mine);

&Mine now has the value 2.

You cannot pass properties by reference. For example, supposed MyProp is a property of class Xan, a statement that requires a reference doesn't work if you supply the property. The following code will always fail because &Xan.MyProp is always passed by value and the MySqlExec method requires it to be passed by reference (so a value can be returned.)

MySqlExec("select from bar", &Xan.MyProp) 

This makes sense because the semantics of an object require that you can only change a property with standard property references, not as a side affect of some other action.

Passing Parameters with Object Data Types

Parameters with object data types are always passed by reference:

/* argument passed by reference  */
method storeInfo(&f as File);

If you specify the out modifier for a method parameter with an object data type, it becomes a reference parameter. This means that the parameter variable is passed by reference instead of the object that it is pointing at when passed.

For example, if you pass an object parameter with the out modifier:

method myMethod(&arg as MyObjectClass);

local MyObjectClass &o1 = create MyObjectClass("A");
local MyOtherObjectClass &o2 = create MyOtherObjectClass();

&o2.myMethod(&o1);

And inside myMethod this occurs:

Method myMethod
   &arg  =  create MyObjectClass("B");
end-method;

Since the method argument is reassigned within the body of myMethod, &o1 does not point at the new instance of MyObjectClass (initialized with "B") after the method call completes. This is because &o1 still references the original instance of MyObjectClass.

However, if &o1 had been passed with the out modifier, after the method call completes, &o1 points at whatever the parameter was last assigned to; in this case, the new instance of MyObjectClass. The parameter, rather than the object, is passed by reference.

Using the Out Specification for a Parameter

In the following example, a class, AddStuff, has a single public method, DoAdd. This adds two numbers together, then assigns them as different numbers. In the signature of the method declaration, the first parameter is not declared with an out statement, while the second one is.

class AddStuff
   method DoAdd(&P1 as number, &P2 as number out);
end-class;

method DoAdd
   &X = &P1 + &P2;
   &P1 = 1;
   &P2 = 2;
end-method;

In the following PeopleCode example, an object named &Aref is instantiated from the class AddStuff. Two parameters, &I and &J are also defined.

local AddStuff &Aref = create AddStuff();
local number &I = 10;
local number &J = 20;

The following code example is correct. &J is changed, because of the outstatement in the method signature, and because the value is being passed by reference. The value of &I is not updated.

&Aref.DoAdd(&I, &J); /* changes &J but not &I */

The following code example causes a design time error. The second parameter must be passed by reference, not by value.

&Aref.DoAdd(10, 20); /* error - second argument not variable */

You can declare methods and properties as abstract, that is, a method or property that has a signature which specifies the parameters and results, but has no implementation in the class that defines it. The implementation may occur in one of the classes that extend the class where the method or property is initially defined.

Abstract methods and properties could be used, for example, when your application wants to provide a means for in-field customization. Your application might deliver a base class specifying the abstract methods and properties and you would allow the customization to complete those methods and properties by using a class that would extend the base class and provide implementations for some or all of the abstract methods and properties.

For a class that only contains abstract methods and properties, that is, a pure interface class, you would define a PeopleCode interface, instead of a class. You define a PeopleCode interface by using the keyword Interface instead of Class. In an interface, all the methods and properties are abstract by default.

The following example illustrates the use of abstract methods and properties. Class MyInterface is the base class which has mostly abstract methods and properties. However it is fully specified by the class MyImplementation which provides and implementation for all the methods and properties.

class MyInterface
   method MyMethod1() abstract;
   method MyMethod2() Returns string abstract;
   method MyMethod3();
   property string myproperty1 abstract readonly;
   property number myproperty2 abstract;
   property number myproperty3 abstract;
end-class;

method MyMethod3
end-method;
-------------------------------------------------------------------
import FOXTEST:MyInterface;

class MyImplementation extends MyInterface;
   method MyImplementation();
   method MyMethod1();
   method MyMethod2() Returns string;
   property string myproperty1 readonly;
   property number myproperty2;
   property number myproperty3;
end-class;

method MyImplementation
   %Super = create MyInterface();
end-method;

method MyMethod1
   /* dummy */
end-method;

method MyMethod2
   /+ Returns String +/
   Return "MyMethod2's implementation is this";
end-method;

Considerations Using Abstract Methods and Properties

Be aware of the following considerations when using abstract methods or properties.

  • You cannot have private abstract methods.

  • You will receive an error if you try to provide a method body for an abstract method.

  • The method signatures must be identical between the abstract method definition and the method implementation in the derived subclass.

  • You will receive an error if you try to provide a Get or Set body with an abstract property.

  • You will receive an error and your PeopleCode program will be terminated if you try to call an abstract method or property at runtime, unless caught in a try-catch block.

  • The class that implements an interface must still assign %Super in its constructor.

The concept of an interface class is purely a syntactic assist when you are defining an application class which is totally composed of abstract methods and properties. In this case by simply specify the keyword interface you are indicating that all the methods and properties are abstract. The following two definitions are semantically equivalent.

class MyInterface
   method MyMethod1() abstract;
   method MyMethod2() Returns string abstract;
   property string myproperty1 abstract readonly;
   property number myproperty2 abstract;
   property number myproperty3 abstract;
end-class;

interface MyInterface
   method MyMethod1();
   method MyMethod2() Returns string;
   property string myproperty1 readonly;
   property number myproperty2;
   property number myproperty3;
end-interface;

When you provide an implementation for an interface you can also use the keyword implements instead of extends. While this is not mandatory, specifying implements describes more accurately what your application class is doing.

If your class implements an interface, you don't need to create an object reference to the superclass since the system does that automatically for you.

In addition if your constructor takes no parameters and simply exists to create an object reference to the superclass (that is, there is only one statement such as %Super = create mySuper();), then you don't need to define a constructor method at all.

The constructor for a class is the public method with the same name as the (short name of the) class. The statements contained in this method (if any) provide the initialization of the class.

This constructor is always executed when an object of the class is instantiated.

Note: Before a constructor runs, the instance variables for the class are set by default based on their declared types.

Instantiate new objects of an application class by using the create function. This function takes the name of the class, and any parameters needed for the constructor method.

If the short class name is unambiguous, that is, only one class by that short name has been imported, you can use just the short class name.

The following code instantiates an object of the class Fruit:

&Fref = create Fruit(); 

If there's a possibility that the name is ambiguous, you can use the full class name to instantiate the object.

The following code instantiates an object of the Invoice class:

&InvObj = create PT:examples:Invoice();

If the create function is used in the context of further object expressions, that is, with a reference to members by a dot operation, the function call must be enclosed in parentheses. This is to emphasize that the creation happens before the member reference. For example:

&InvCust = (create Invoice()).Cust();

If necessary, the constructor is supplied its parameters by the call-list after the class name in the create statement. For example:

&Example = create Example(&ConstructorParameter1, 2, "Third");

A class that does not extend some other class does not need any constructor.

A class that extends another class must have a constructor, and in the constructor, it must initialize its super class. This restriction is relaxed if the super class constructor takes no parameters, and all the constructor does is assign it to %Super. In this case, the runtime creates the super object and runs its constructor automatically. The benefit of this is two-fold: at design time you do not need to provide a constructor since the runtime automatically does the equivalent of %Super = create MySuperObject(); Also, at runtime, you may notice a performance increase since the super class object creation and construction are done without any PeopleCode. This latter benefit can be compounded in the case where there are multiple levels of inheritance.

To the general case, to initialize a superobject, an instance of the superclass is assigned to the keyword %Super in the constructor for the subclass. This assignment is allowed only in the constructor for the subclass.

The following example shows this type of assignment:

class Example extends ExampleBase
   method Example();
   ...
end-class;

Global string &CurrentBaseString;

method Example
   %Super = create ExampleBase();
   &BaseString = &CurrentBaseString;
   &SlashString = &BaseString;
   &ImportantDate = Date(19970322);
end-method

If your subclass’ constructor only exists to create the superclass object and assign that to your %Super, you can dispense with providing a constructor completely as the following example illustrates.

class A
   ...
end-class;

Class B extends A
  ...
end-class;

Class C extends B
   ...
end-class;

...
Local C &c = create C();

...

Classes A, B and C have no constructors and the one call to create C creates objects of class C, B and A and run their constructors. PeopleSoft recommends that unless your constructors take parameters and need to do more than be assigned to their %Super, that you do not provide constructors since that simplifies design time as well as may improve runtime performance significantly.

The above example is semantically equivalent to the following, except that it may run much faster, since it does not have to run PeopleCode at each step of the construction process.

class A
   ...
end-class;

Class B extends A
   Method B();
   ...
end-class;

method B
   %Super = create A();
end-method;

Class C extends B
   Method C();
   ...
end-class;

method C
   %Super = create B();
end-method;

Note: Application classes don't have destructors, that is, a method called just before an object is destroyed. The PeopleCode runtime environment generally cleans up any held resources, so they're not as necessary as they are in other languages.

External function declarations are allowed in application classes, in the global and component variable declarations, after the class declaration (after the end-class statement), and before the method definitions. For example:

import SP:Record;

class Person extends SP:Record
   method Person(&pid As string);
   
   method getPerson() Returns Record;
   method getUserName() Returns string;
   
private
   instance Record &recPerson;
   
end-class;

Declare Function getUserName PeopleCode FUNCLIB_PP.STRING_FUNCTIONS FieldFormula;

method Person
   /+ &pid as String +/
   
   %Super = create SP:Record(Record.SPB_PERSON_TBL);
   &recPerson = %Super.getRecord();
   &recPerson.PERSON_ID.Value = &pid;
   
end-method;

method getPerson
   /+ Returns Record +/
   
   Return &recPerson;
   
end-method;

method getUserName
   /+ Returns String +/
   Local string &formattedName;
   
   &formattedName = getUserName(&recPerson.PERSON_ID);
   Return &formattedName;
end-method;

When application class properties and instance variables are used as the argument to functions or methods, they are always passed by value, never by reference. This means that the called function or method cannot change the value of the property or instance variable. If you want a function call to change a property or instance variable, use a local variable in the call and then assign the property or instance variable after the call.

Suppose you have the following class and you want to call Meth2 in the body of Meth1, passing it MyProp:

class MyClass
   property number MyProp;
   method Meth1();
   method Meth2(&i as number out);
end-class;

The following PeopleCode fails with a compile-time error:

method Meth1
   Meth2(%This.MyProp);   /* error - field or temp name required. */
   Meth2(&MyProp);   /* error - field or temp name required. */
end-method;

The following PeopleCode is valid:

method Meth1
/* Assignment needed if &i is input as well as output in Meth2. */
   local number &Var = %This.MyProp; 
   Meth2(&Var);
   %This.MyProp = &Var;
end-method;

Note: You call a Java program from an Application Class the same way you do using any other PeopleCode program, that is, by using one of the existing Java class built-in functions.

See From Java to PeopleCode.