Mapping the UML Notation to Ada 95 -- Code Generation
ContentsThis chapter is organized as follows:
- Introduction
- Name Space
- Name Resolution
- Code Generation Properties and Consistency
- Classes
- Parameterized Classes
- Bound Classes
- Utilities
- Metaclasses
- Attributes
- Has Relationships
- Associations
- Dependency Relationships
- Generalization Relationships (Inheritance)
- Operations
- User-Defined Initialization, Assignment and Finalization
IntroductionThis chapter details the forward-engineering mapping between the UML notation and the Ada 95 programming language.
Roughly speaking, classes are transformed into types declared in library packages, utilities are transformed into library packages, attributes and relationships are transformed into record components. The main source of information for the code generation are the class diagrams. Code generation properties may be used to gain finer control over the way that code is produced. If component diagrams are present, some of the information they contain is also used by the code generator.
Because UML and Ada use the word "package" to designate two different concepts, this document uses the phrase "UML package" for a package in the UML acceptation, and the word "package" without qualification for an Ada package. When necessary, the phrases "logical UML package" and "component UML package" are used to refer to UML packages in the logical view or in the component view, respectively.
Name SpaceThis section defines how the naming of entities in the UML notation corresponds to the naming of declarations in the generated Ada 95 code.
The following rules define the legal names for entities of a model that is used to generate Ada 95 code:
- The name of any entity in a model may have the form:
identifier
where identifier is a legal Ada 95 identifier. In other words, the name of any entity name may be an Ada simple name.
- The name of any class or module may also have the form (using the same BNF notation as in the Ada 95 Reference Manual):
identifier{.identifier}
where identifier is a legal Ada 95 identifier. In other words, the name of any class or module may be either an Ada simple name or an Ada expanded name.
- The name of any normal or parameterized class (but not an utility or a bound class) may also have the form:
identifier{.identifier}:identifierIn other words, a class name must either be an Ada simple name, an Ada expanded name, or a pseudo-expanded name (an expanded name followed by a colon and an identifier: this is called the colon notation hereafter).
The code generator checks the legality of names, in particular in terms of consistency with the Ada Reference Manual.
From the name of a class the code generator derives the name of a library-level package (the package where the type and operations associated with the class are declared) and the name of a type (the type associated with the class) as follows:
- If the class is associated with a module, the package name is the name of the associated module. The type name is given by the code generation property TypeName, unless the class name uses the colon notation, in which case the type name is the segment following the colon in the class name.
- If the class is not associated with a module, and its name uses the colon notation, the package name is made of the name segments preceding the colon, and the type name is the name segment following the colon.
- If the class name does not use the colon notation, the package name is the name of the class, and the type name is given by the code generation property TypeName.
The code generation property TypeName defaults to "Object".
These rules support two different approaches to naming the classes in the Rose model: either the class name reflects the hierarchy of units, or the class name is for design purposes only, and the hierarchical unit structure is defined using the mapping to modules. In the former case, the colon notation may be used to make the type names explicit in the class diagram. Alternatively, the type names may be specified using the property TypeName.
For utilities, similar rules are used, except that there is no type declaration, so the TypeName property is irrelevant, and the colon notation is not allowed.
Note that it is possible for several classes to map to types declared in the same Ada package, either by using the colon notation, or by using associations between classes and modules. However, such a mapping is only legal if all classes that map to a given module are part of the same UML package. In the case of associations between classes and modules, the correspondence between logical and component UML packages ensure that the mapping is always legal. In the case of the colon notation, the legality of the mapping is checked by the code generator.
Name ResolutionWhile a large part of the information in a model is entered graphically by means of relationships and adornments, there are a number of situations where the user enters textually in the model a piece of information which designates a class. Examples of such situations include the definition of the type of attributes or parameters.
The code generator performs name resolution to determine the Ada type to be generated in these circumstances. To explain how the name resolution works, consider the case of class A having an operation Op with a parameter (or result) type written as "B". The code generator performs the following operations:
- It finds all the relationships originating at class A. Note that this includes in particular the dependency relationships, which are not otherwise used for code generation (except that they result in with clauses, as explained below). As a consequence, dependency relationships may be used to introduce visibility between classes for the sake of name resolution.
- It looks at the names of all classes which are the targets of these relationships.
- If any of these classes is named "B" (the comparison is case-insensitive, but must otherwise be exact), the type of the parameter in the generated code is the Ada type generated for class B. This ensures that the generated code is legal. Assuming that the default properties are used for class B, the generated code looks like:
procedure Op (X : B.Object);- If any of the target classes is named "B:T" (the comparison with the name segments preceding the colon is case insensitive, but must otherwise be exact; the name segment following the colon is ignored), the type of the parameter in the generated code is the Ada type generated for class B:T, i.e. B.T. The generated code looks like:
procedure Op (X : B.T);- If none of the target classes is named "B" or "B:T", the type of the parameter in the generated code is simply copied from the model. In this case, the generated code looks like:
procedure Op (X : B);Note that this resolution mechanism applies regardless of whether the parameter type is a simple name (like "B"), an expanded name (like "B.C") or a colon notation (like "B:T" or "B.C:T"). If the parameter type uses the colon notation, it will only match a class name that also uses the colon notation. In all cases, the generated code references the type name, not the class name.
It may be that there are ambiguities, for instance if the parameter type is given as "B" and the set of target classes includes classes named "B:T1" and "B:T2". In this case, an error message is emitted, and the parameter type has to be made more explicit.
This name resolution mechanism makes it possible to use class names everywhere in the model, and defer the mapping of class names to Ada type names by setting the TypeName code generation property and/or the mapping of classes to modules. Changing the mapping of classes to types and modules doesn't require to change the attributes, parameters, etc., scattered throughout the model.
Of course, the user may always enter an Ada type name for the type field of an attribute or parameter, since such a name will not match any class name, and will thus be copied verbatim in the generated code. This may be useful for predefined types like Integer or Calendar.Time, for which it would be cumbersome to create a class in the model. However, it is strongly recommended that class names, not type names, be used wherever possible in order to ease maintenance of the model if the mapping of classes to types ever has to change.
Code Generation Properties and ConsistencyVarious entities in a model have associated code generation properties which may be used to control the way that the code is produced. Often, there exist consistency requirements between the values of the code generation properties of one or several entities.
These requirements come most of the time from language rules, and ensure that the generated code is correct. To take an example, in Ada 95, it is not possible to specify, when declaring a derived type, if it is limited: it just inherits its limited-ness from the root of the derivation tree. In Rose/Ada, the code generation property IsLimited may be used to control whether or not the type generated for a given class is limited. Clearly, it does not make sense for a root class A to have IsLimited set to False, and for a class B, subclass of A, to have IsLimited set to True.
In practice however, having to set code generation properties in a consistent manner over large models may become burdensome. To avoid this, some code generation properties are said to be dominant over others. A dominant property determines the code generated, and the dominated property is ignored altogether, even if it specifies a different code generation. For instance, if a root class has IsLimited set to True, the code generation property IsLimited of its subclasses is not even considered: these classes will all be limited.
In some circumstances, a property is dominant only when it has a specific value (or set of values). For instance, the property TypeImplementation dominates IsLimited only when it has the values Task or Protected (because task types and protected types are always limited).
One may however wish to be able to track and correct inconsistencies where, for instance, IsLimited is set to True on the root class but to False on some of its subclasses, Such inconsistencies may turn out to be a problem in organizations having strict quality assurance policies. To ease detection of inconsistencies, the code generator emits a warning message whenever it detects that a dominated property has a value which is inconsistent with the dominant property.
ClassesIf a "normal" class is associated with a module, that module must be a non-generic package.
Normally, the type generated to represent objects of the class is a non-limited, private type. This can be controlled using the code generation properties IsLimited and TypeVisibility attached to the class:
- For a class which has no superclass, the boolean code generation property IsLimited may be set to True, in which case a limited type is generated. The property IsLimited of a root class dominates the same property for its subclasses.
- TypeVisibility can take two values: Public and Private. Setting this property to Public causes the full type declaration to be generated in the visible part of the associated library package. Setting it to Private causes a private type to be generated. TypeVisibility defaults to Private.
The scheme used to generate the code associated with a class is governed by the code generation properties TypeImplementation and TypeDefinition.
If TypeDefinition is not empty, it dominates TypeImplementation, and the type generated uses the contents of that property (technically, the contents of TypeDefinition must be an Ada type definition). If for instance TypeDefinition is set to "range -1 .. 3" then the generated type declaration is:
type Object is range -1 .. 3;If TypeDefinition is empty (the default), TypeImplementation is used to control the code generation scheme. TypeImplementation can take one of five values: Tagged, Record, Mixin, Task or Protected. In the rest of this section, we consider each of these schemes in turn. In this discussion, unless otherwise specified we assume the default values for properties IsLimited and TypeVisibility.
Tagged Implementation
The class corresponds to a tagged type. If the class has no superclass, the declaration of the corresponding type is:
type Object is tagged private;If the class has a superclass, the declaration of the corresponding type is:
type Object is new Superclass.Object with private;If the class has more than one superclass, we are in a situation of multiple inheritance, which is covered later.
If the class is abstract, the associated type declaration includes the reserved word abstract:
type Object is abstract tagged private; type Object is abstract new Superclass.Object with private;Record Implementation
In this scheme, polymorphism (if any) is implemented using records with variants. This means that if the class has any subclass, an enumeration type is created to represent all possible variants, and the record type declaration associated with the class is a variant record gathering the attributes and relationships of all the subclasses.
The properties TypeImplementation and IsLimited of the root class dominate those of the subclasses. Also, none of the classes may be marked abstract.
There are two ways that the record mapping can be implemented, so the Record scheme is further controlled by the code generation property RecordImplementation associated with the root class. This property can take the two values SingleType and MultipleTypes. The property RecordImplementation of the root class dominates the same property for its subclasses.
Regardless of the mapping chosen, for a class which has no superclass and no subclasses, the generated code is simply (assuming the default values for the properties TypeVisibility and IsLimited):
package A is type Object is private; private type Object is record ... end record; end A;When discussing the two possible record implementations in more complex cases, we'll use the following generalization hierarchy as an example:
SingleType Record Implementation
In this scheme, a single record type is created for the complete generalization hierarchy. An enumeration type is created that lists all the variants, and the structure of the record corresponds to that of the generalization tree. For each subclass, a package is created that declares a subtype or derived type with a discriminant constraint (depending on the property IsSubtype). For leaf classes, the discriminant is omitted. The code generated is as follows:
package A is -- The root package type A_Kinds is (Some_A, Some_B, Some_C, Some_D, Some_E, Some_F); type Object (Kind : A_Kinds := Some_A) is private; private type Object (Kind : A_Kinds := Some_A) is record Ca : Integer; case Kind is when Some_B => Cb : Integer; when Some_C | Some_D | Some_E | Some_F => Cc : Integer; case Kind is when Some_D => Cd : Integer; when Some_E | Some_F => Ce : Integer; case Kind is when Some_F => Cf : Integer; when others => null; end case; when others => null; end case; when others => null; end case; end record; end A; with A; package B is -- A leaf type Object is private; private type Object is new A.Object (A.Some_B); end B; with A; package C is -- An intermediate node subtype C_Kinds is A.A_Kinds range A.Some_C .. A.Some_F; type Object (Kind : C_Kinds := A.Some_C) is private; private type Object (Kind : C_Kinds := A.Some_C) is new A.Object (Kind); end C;The prefix used to generate the names of the enumeration literals is specified using the code generation property EnumerationLiteralPrefix of the class. This property defaults to "A_". In the above examples, we have assumed for readability that it was set to "Some_".
Note that the code generator orders the enumeration literals in a way that is suitable for the constraints on subtype Kinds in the intermediate nodes.
The property TypeVisibility of the root class dominates the same property for subclasses.
The SingleType mapping may result in name conflicts: if two components of two classes in a generalization hierarchy have the same name, they will clash when they are put together in the above record type declaration. It is the user's responsibility to avoid such conflicts.
MultipleTypes Record Implementation
In this scheme, one record type is created for each class in the hierarchy, and these types are aggregated in a discriminated record at each level, according to the structure of the generalization hierarchy. For subclasses, a subtype or derived type with a discriminant constraint is created (depending on the property IsSubtype). For leaf classes, the discriminant is omitted.
package A_Record_Kind is type A_Kinds is (Some_A, Some_B, Some_C, Some_D, Some_E, Some_F); end A_Record_Kind; with A_Record_Kind; with B; with C; package A is use A_Record_Kind; subtype A_Kinds is A_Record_Kind.A_Kinds; type Object (Kind : A_Kinds := Some_A) is private; private type Object (Kind : A_Kinds := Some_A) is record Ca : Integer; case Kind is when Some_B => The_B : B.Object; when Some_C | Some_D | Some_E | Some_F => The_C : C.Object (Kind); when others => null; end case; end record; end A; package B is type Object is private; private type Object is record Cb : Integer; end record; end B; with A_Record_Kind; with D; with E; package C is use A_Record_Kind; subtype C_Kinds is A_Kinds range Some_C .. Some_F; type Object (Kind : C_Kinds := Some_C) is private; private type Object (Kind : C_Kinds := Some_C) is record Cc : Integer; case Kind is when A_Record_Kind.Some_D => The_D : D.Object(Kind); when A_Record_Kind.Some_E | A_Record_Kind.Some_F => The_E : E.Object(Kind); when others => null; end case; end record; end C;As before, the prefix used to generate the names of the enumeration literals is specified using the code generation property EnumerationLiteralPrefix of the root class, which was set to "Some_" in the above example. Also, the prefix used to generate the names of the intermediate record components is given by the code generation property RecordFieldPrefix of the root class (this property defaults to "The_").
Finally, the name of the auxiliary package used to declare the enumeration type Kinds is given by the code generation property RecordKindPackageName of the root class. This property defaults to "${class}_Record_Kinds".
Mixin Implementation
A class whose TypeImplementation property is set to Mixin must be abstract. If that class has no superclass (see figure), the following code is generated:
generic type Superclass is abstract tagged private; package A is type Object is abstract new Superclass with private; -- declaration of the operations -- of the class here. private type Object is new Superclass with record -- declaration of the attributes -- and relationships -- of the class here. end record; end A;If the class has (exactly one) superclass, B, then B must have its TypeImplementation property set to Tagged (see figure), and the generic formal part above is changed as follows:
with B; generic type Superclass is abstract new B.Object with private; package A is ...Classes implemented according to the Mixin scheme are used in multiple inheritance situations as explained later on.
Task Implementation
A class whose TypeImplementation property is set to Task must not be abstract, and its code generation property IsLimited is dominated. Also, its operations must all be procedures (as opposed to functions). A task type is generated for such a class.
The operations are transformed into entries, and their SubprogramImplementation property is dominated. Depending on the visibility of each operation, the entry is declared either in the visible part or in the private part of the task type. No implicit parameter is ever generated for an operation in the Task mapping, because the implicit parameter is the task itself: TypeImplementation dominates ImplicitParameter.
For each visible operation of the class, a procedure is also generated in the visible part of the package that declares the task type. This procedure has the same profile as the corresponding entry of the task, except for an additional parameter that designates the object being operated upon. The name of this additional parameter is given by the code generation property ImplicitParameterName of the class. The body of each of these procedures simply calls the corresponding entry of the given task object.
The attributes and "has" relationships whose property RecordFieldImplementation is either Discriminant or AccessDiscriminant are transformed into discriminants, as for any composite type. The attributes and "has" relationships whose property RecordFieldImplementation is Component, and the associations, are transformed into variables declared in the task body.
Accessor operations (Get and Set) are never generated for attributes of a class whose TypeImplementation property is Task (in other words, GenerateGet and GenerateSet are dominated).
An example of code generated for the Task mapping is as follows:
package A is type Object (D : Integer := 0) is limited private; procedure Op1 (This : Object); private task type Object (D : Integer := 0) is entry Op1; private entry Op2; end Object; end A;
with B; package body A is procedure Op1 (This : Object) is begin This.Op1; end Op1; task body Object is Attr1 : Float; Attr2 : Boolean := False; Aggr1 : B.Object; Aggr2 : B.Handle; ... end Object; end A;Classes implemented according to the Task mapping cannot be used in generalization relationships.
Protected Implementation
A class whose TypeImplementation property is set to Protected must not be abstract, and its code generation property IsLimited is dominated. A protected type is generated for such a class.
The operations are transformed into protected functions or protected procedures, except that an operation whose concurrent behavior is specified as synchronous is transformed into an entry. The code generation property EntryBarrierCondition of such an operation contains the boolean expression used for the barrier of the entry body. This property defaults to "True".
Depending on the visibility of each operation, it is declared either in the visible part or in the private part of the protected type. No implicit parameter is ever generated for an operation in the Protected mapping, because the implicit parameter is the protected object itself: TypeImplementation dominates ImplicitParameter.
For each visible operation of the class, a subprogram is also generated in the visible part of the package that declares the task type. This subprogram has the same profile as the corresponding protected subprogram, except for an additional parameter that designates the object being operated upon. The name of this additional parameter is given by the code generation property ImplicitParameterName of the class. The body of each of these subprograms simply calls the corresponding protected subprogram of the given protected object.
The attributes and "has" relationships whose property RecordFieldImplementation is either Discriminant or AccessDiscriminant are transformed into discriminants, as for any composite type. The attributes and "has" relationships whose property RecordFieldImplementation is Component, and the associations, are transformed into components of the protected object (and are thus declared in the private part).
An example of code generated for the Protected mapping is as follows:
package A is type Object (D : Integer := 0) is limited private; procedure Op1 (This : Object); function Op2 (This : Object) return Integer; private protected type Object (D : Integer := 0) is entry Op1; function Op2 return Integer; private procedure Op3; Attr : Float; Aggr1 : B.Object; Aggr2 : B.Handle; end Object; end A; with B; package body A is procedure Op1 (This : Object) is begin This.Op1; end Op1; function Op2 (This : Object) return Integer is begin return This.Op2; end Op2; protected body Object is entry Op1 when Attr > 0.0 is begin ... end Op1; function Op2 return Integer is begin ... end Op2; procedure Op3 is begin ... end Op3; end Object; end A;Classes implemented according to the Protected mapping cannot be used in generalization relationships.
Parameterized ClassesThere exist two mappings for parameterized classes: either as types declared in generic units, or as types with unconstrained discriminants. Correspondingly, there exist two mappings for bound classes: generic instantiations and constrained types. The mapping is selected by the code generation property ParameterizedImplementation: if this property is set to Generic (the default), the "generic" mapping is used, if it is set to Unconstrained the "unconstrained type" mapping is used.
In all cases, if a parameterized class is associated with a module, the code generation property ParameterizedImplementation must be consistent with the nature of the associated module: if ParameterizedImplementation is Generic, the associated module must be a generic package, if it is Unconstrained it must be a non-generic package.
If a class is parameterized, all its subclasses must also be parameterized. The property ParameterizedImplementation of a root class dominates the same property for its subclasses.
Generic Implementation
The root class is transformed into a type declared in a generic library package. The exact nature of the type is controlled by the property TypeImplementation, as for normal classes. The formal part of the generic is extracted from the class specification.
Subclasses are transformed into a tagged type declared in a generic library package, but we have two cases to consider:
- If the generic library package is a child of the package that contains the superclass, then its formal part only includes the parameters extracted from the class specification of the subclass.
generic ... -- parameters of the superclass package A is type Object is tagged private; ... end A;
generic ... -- parameters of the subclass package A.B is type Object is new A.Object with private; ... end A.B;- If, on the other hand, the generic library package is not a child of the package that contains the superclass, then it must import the superclass' package as a generic formal package, as shown on the following example:
generic ... -- parameters of the superclass package A is type Object is tagged private; ... end A; with A; generic with package Superclass is new A (<>); -- parameters of the subclass package B is type Object is new Superclass.Object with private; ... end B;Unconstrained Type Implementation
The discriminant part of the type is derived from the class parameters. Each class is transformed into a type having unconstrained discriminants (without default values). For a subclass, type derivation is used to add discriminants without constraining the discriminants inherited from the parent type.
If one any of the parameters has a type of the form "access T" then the property IsLimited is dominated, and a limited type is generated for the class.
An example of code generated for the Unconstrained Type implementation is as follows (assuming the default values for other code generation properties):
package A is type Object (D1 : Integer; D2 : access String) is tagged limited private; ... end A; with A; package B is type Object (D1 : Integer; D2 : access String; D3 : Boolean) is new A.Object (D1, D2) with private; ... end B;
Bound ClassesIf a bound class is associated with a module, that module must be a non-generic package.
The value of ParameterizedImplementation for a parameterized class (Generic or Unconstrained) determines the mapping chosen for any bound class obtained by binding the parameters of that parameterized class. In other words, the property ParameterizedImplementation of a parameterized class dominates the same property for the bound classes.
Generic Implementation
The class is transformed into a library-level generic instantiation. The actual parameters are extracted from the class specification.
Consider a bound class B1 obtained by binding the parameters of a parameterized class P1. Say that P1 is not a root class, but has instead a superclass P2. Because the actual parameters of B1 only specify values for the parameters of P1, and not of P2, there must exist a bound class B2, obtained by binding the parameters of a parameterized class P2, from which B1 "inherits" the actual parameters for P1.
The UML notation does not allow inheritance relationships between bound classes, because bound classes are fully specified by their template. Therefore, the pseudo-inheritance between B1 and B2 is represented by a dependency relationship labelled "parent", as shown on the diagram below:
Based on this information, the code is generated in two different ways depending on whether P1 had visibility over its ancestor by a parent-child relationship or by a formal package (see above):
package B1 is new B2.P1 (...);
package B1 is new P1 (Superclass => B2, ...);Unconstrained Type Implementation
The class is transformed into a type declaration that provides discriminant constraints. Alternatively, a subtype is generated if the boolean code generation property IsSubtype for the class is True (this property defaults to False).
Each bound class must provide values for all parameters (i.e., constraints for all discriminants), including those inherited from the generalization hierarchy.
An example of code generated for the Unconstrained Type implementation is as follows (assuming the default values for other code generation properties):
package C is subtype Constrained_Object is B.Object (D1 => 3, D2 => Some_String'Access, D3 => False); type Object is Constrained_Object with private; ... private type Object is Constrained_Object with record ... end record; end C;
UtilitiesIf an utility is associated with a module, that module must be a non-generic package or subprogram. If an utility is not associated with a module, it is transformed into a package. Similarly, parameterized utilities are transformed into generic units, and bound utilities are transformed into library-level instantiations.
If an utility is transformed into a package, no type declaration is produced. Instead, each operation of the utility is transformed into a subprogram in that package. Attributes of such an utility become package-level declarations, regardless of the setting of the "static" button.
If an utility is transformed in a subprogram, then the utility must declare exactly one operation. Note that a bound utility must map to the same kind of program unit as its template.
MetaclassesA metaclass must not have any associated module. The attributes and operations it declares are instead used to generate code for classes that derive from that metaclass.
A metaclass attribute or relationship is transformed into a variable or constant. Depending on the visibility of the attribute or relationship, the variable is declared in the visible part (public), the private part (protected or private) or the body (implementation) of the package associated with each class that derives from the metaclass.
A metaclass operation is transformed into a subprogram, which is declared in the same package as each class which derives from the metaclass. Each parameter (or result) of such a subprogram which had a type name identical to that of the metaclass is transformed into a class-wide parameter. Depending on the visibility of the operation, the subprogram is declared in the visible part (public), the private part (protected or private) or the body (implementation) of the package associated with each class that derives from the metaclass.
An example of code generated for metaclasses is as follows. Note that no module is generated for the metaclass A. Also note the difference between class attributes and operations on one hand, and metaclass attributes and operations on the other hand:
package B is type Object is tagged private; procedure Q (This : Object); X : Integer; procedure P (This : Object'Class); private type Object is tagged record Y : Float; end record; end B;
AttributesAn attribute is generally transformed into a record component. There exists two special cases for the generation of attributes: the attributes of a metaclass are transformed into package-level declarations, as explained above. The attributes of a normal class which are marked as "static" are also transformed into package level declarations. In fact, in term of code generation, static attributes are handled exactly as attributes of metaclasses.
The record component corresponding to an attribute has a name which is given by the code generation property RecordFieldName.
The code generated for an attribute is controlled by the code generation property RecordFieldImplementation. This property can take the values Discriminant, AccessDiscriminant, and Component (the default). For a parameterized class whose ParameterizedImplementation is Unconstrained, the property RecordFieldImplementation is dominated, and all attributes are implemented as components. If a class has, in its generalization hierarchy, an attribute implemented as an AccessDiscriminant, then the property IsLimited is dominated, and a limited type is generated for that class.
The semantics of RecordFieldImplementation is as follows:
- If RecordFieldImplementation is set to Discriminant, a normal discriminant is generated, as in:
type Object (D : Integer := 3) is private;- If RecordFieldImplementation is set to AccessDiscriminant, an access discriminant is generated, as in:
type Object (D : access Integer) is limited private;- If RecordFieldImplementation is set to Component, a normal component is generated in the full type declaration, as in:
type Object is record C : Integer; end record;All attributes (and "has" relationships; see below) whose RecordFieldImplementation property is either Discriminant or AccessDiscriminant must agree on the existence of default values, and on the visibility: either all have defaults, or none have defaults, and they all have the same visibility. In addition, if the code generation property TypeImplementation of the class is Tagged, then it dominates the property InitialValue, and no default value is generated.
The discriminants always appear in the full type declaration. For private types, whether or not the discriminants appear in the private type declaration depends on their visibility and on the existence of defaults:
- If the discriminants have defaults, they appear in the private type declaration only if their visibility is public. Otherwise, the private type declaration does not include a discriminant part.
- If the discriminants don't have defaults, they appear in the private type declaration only if their visibility is public. Otherwise, the private type declaration includes an unknown discriminant part, as in:
package A is type Object (<>) is private; private type Object (D : Integer) is record ... end record; end A;The case of a class inheriting discriminants from its superclass (and possibly adding new discriminants) is handled in a manner similar to the Unconstrained Type mapping of parameterized classes.
Has Relationships"Has" relationships are not part of the UML notation. However, they can be created in Rose using the View > As Booch option. When viewed using the UML or OMT notation, they are displayed as unidirectional aggregation relationships. However, they have slightly different code generation properties than true aggregations, because they gather together the properties borne by associations and the properties borne by roles.
An "has" relationship is generally transformed into a record component. There exists two special cases for the generation of "has" relationships: the relationships of a metaclass are transformed into package-level declarations, as explained above. The relationships of a normal class which are marked as "static" are also transformed into package level declarations. In fact, in term of code generation, static "has" relationships are handled exactly as "has" relationships of metaclasses.
In the rest of this discussion, we consider the case of class A having a "has" relationship to class B.
The mapping of an "has" relationship depends on whether it is by-value or by-reference:
- A by-value relationship is represented using the type associated with B (either directly or through some container, depending on the multiplicity of the relationship; see below).
- A by-reference relationship is represented using an access type that designates the type associated with B (either directly or through some container, depending on the multiplicity of the relationship; see below). This access type is only created for those classes that are the target of some by-reference "has" relationship. There is only one such access type, even if class B is the target of several "has" relationships.
The access type used to represent by-reference relationships targeting B is declared in the package associated with class B. Its name is given by the code generation property AccessTypeName of class B (this property defaults to "Handle"). It is generated either in the public part or in the private part, based on the code generation property AccessTypeVisibility, which can take the values Public (the default) and Private.
If the code generation property AccessTypeDefinition of B is not empty, it dominates, and the declaration of the access type uses this property. Technically, AccessTypeDefinition must contain an Ada type definition. For instance, if AccessTypeDefinition is set to "access constant B.Object" the access type is declared as follows:
type Handle is access constant B.Object;If the code generation property AccessTypeDefinition of B is empty (the default), an access type is generated as follows:
- If B is associated with a tagged type, the access type is a class-wide type:
type Handle is access B.Object; -- B not tagged
type Handle is access B.Object'Class; -- B tagged- If the code generation property MaybeAliased for B is set to True (it defaults to False), the access type is a general access-to-variable type:
type Handle is access B.Object'Class; -- B tagged, not aliased
type Handle is access all B.Object'Class; -- B tagged, may be aliasedThere may be circumstances where it is useful to have an access type declaration generated for class B, even though B is not (or not yet) the target of any by-reference "has" relationship. The code generation property GenerateAccessType controls the generation of an access type. It can take the values Auto and Always. The default is Auto, and corresponds to the case where the generation of the access type depends on the existence of a by-reference "has" relationship. The value Always force the generation of an access type declaration, regardless of the existence of by-reference "has" relationships.
If the maximum allowable cardinality of the relationship is 1, the type of the record component representing the relationship is directly the object or access type associated to B, as explained above.
If, on the other hand, the maximum allowable cardinality of the relationship is larger than 1, an intermediate container type is required to support the one-to-many relationship. The scheme used to generate the code associated with a one-to-many relationship is governed by the code generation properties ContainerImplementation and ContainerType.
If ContainerType is not empty, it dominates ContainerImplementation, and specifies the container type used to represent the one-to-many relationship. The code generation property ContainerDeclarations may be used to specify auxiliary declarations that may be necessary to build the container type.
If ContainerType is empty (the default), ContainerImplementation is used to control the code generation scheme. ContainerImplementation can take the two values Generic and Array, and defaults to Array. The semantics of this property is as follows:
- If ContainerImplementation is set to Generic, the generic unit given by the property ContainerGeneric is instantiated, with a single parameter which is the type corresponding to class B, or the access type associated to B, depending on whether the relationship is by-reference or by-value.
- If ContainerImplementation is set to Array, an unconstrained array type, and an access to that array type, are declared to represent the one-to-many relationship. The array element type is either the type associated to B (if the "has" relationship is by-value) or the access type associated with B (if the relationship is by-reference). The name of the array type and access type are given by the code generation properties ArrayOfTypeName (or ArrayOfAccessTypeName) and AccessArrayOfTypeName (or AccessArrayOfAccessTypeName) of class B. These properties default to Array_Of_${type}, Array_Of_${access_type}, Access_Array_Of_${type} and Access_Array_Of_${access_type}, respectively. The index specification for the array types is given by the code generation property ArrayIndexDefinition, which defaults to "Positive range <>".
The code generation property RecordFieldImplementation which was discussed above in the context of attributes can also be applied to "has" relationships, with the same semantics, except that AccessDiscriminant is not allowed for a by-value relationship.
Note that the target of a "has" relationship must not be a class whose TypeImplementation property is Mixin.
As an illustration of the implementation of "has" relationship, consider the following class diagram:
It results into the following code (note that only the "get" accessors are shown; the "set" accessors have similar parameter types):
with B; with List_Generic; package A is type Object (Has5 : access B.Object) is tagged limited private; package B_List is new List_Generic (B.Object); function Get_Has1 (This : in Object) return B.Object; function Get_Has2 (This : in Object) return B.Handle; function Get_Has3 (This : in Object) return B.Array_Of_Object; function Get_Has4 (This : in Object) return B_List.List; -- "set" accessors go here private type Object (Has5 : access B.Object) is tagged limited record Has1 : B.Object; Has2 : B.Handle; Has3 : B.Access_Array_Of_Object; Has4 : B_List.List; end record; end A; package B is type Object is tagged private; type Handle is access all Object'Class; type Array_Of_Object is array (Positive range <>) of Object; type Access_Array_Of_Object is access Array_Of_Object; private ... end B;The following defines the generic container used by Has4:
generic type Item is private; package List_Generic is type List is tagged private; private ... end List_Generic;
AssociationsAssociations fall into two categories:
The generated code for both categories follows a number of common principles.
Code is only generated for the roles which are marked as navigable in the Rose model. If an association has no navigable role, no code is generated for that association.
Code is only generated if the two classes that participate in the association have their Type Implementation property set to Record or Tagged. An error is emitted if an association involves classes with a non-record, non-tagged implementation.
There exist many similarities between the mapping of associations and that of "has" relationships:
- A role always becomes a component in a record or tagged type.
- The name of a role determines the name of the various declarations generated for that role (record component, accessor subprograms, etc.). If a role is unnamed, the name of the class at the other end of the association is used to determine the name of the declarations generated for that role.
- If a class is the target of a navigable by-reference role, an access type is generated for that class. The characteristics of that access type depend on the code generation properties AccessTypeName, AccessTypeVisibility, AccessTypeDefinition and MaybeAliased of the class.
- The mapping of a role depends on its multiplicity. If the maximum allowable cardinality is larger than 1, a container type is declared, as specified by the code generation properties ContainerImplementation, ContainerType, ContainerGeneric and ContainerDeclarations for the role.
- The code generation properties NameIfUnlabeled, RecordFieldName, GenerateGet, GetName and InlineGet may also be applied to a role, with a semantic similar to the semantics they have for "has" relationships. The code generation properties GenerateSet, SetName and InlineSet are only used when the role belongs to a unidirectional association, i.e., an association with only one navigable role. They are not used when the role belongs to a bidirectional association, i.e., an association with two navigable roles.
Simple Associations
If a simple association has only one navigable role, the code generated for that association is exactly identical to the code that would be generated for an "has" relationship similar to that role. Such an association may be marked "static", in which case package-level declarations are generated instead of record components (again, this is identical to the case of an "has" relationship).
A warning is emitted by the code generator when it encounters a unidirectional association, because an association normally has two navigable roles (and thus the presence of only one navigable role may indicate a mistake).
The rest of this section pertains only to the case of a simple association with two navigable roles.
The two classes which participate in the association must map to the same package, either because their names use the colon notation and have the same prefix, or because they are associated with the same module (a package specification).
In both classes the TypeVisibility property must be set to Private.
An association may have keys which are used to unambiguously identify an object. Keys are handled by Rose/Ada exactly as attributes of classes: they are normally generated as record components, possibly with "get" and "set" accessors. If several associations originating from the same class declare keys with the same name, the record component is only generated once. An error is detected in this case if the various keys don't have the same type.
A bidirectional association may not be marked "static".
Data Structures
If any role of a bidirectional association is by-value, an error is detected.
If both roles of a bidirectional association are by-reference, the data structures (record, components, discriminants, etc.) generated for the association are exactly identical to the data structure that would be generated for two by-reference "has" relationships. These data structures depend on the multiplicity of the association. They are shown below, assuming that both classes use the Tagged implementation, and that arrays are used to represent relationships with maximum allowable cardinality larger than 1.
In the following examples, the AccessTypeName class property must be given a unique name since both classes map to the same package.
package A is type T1 is tagged private; type H1 is access T1'Class; type T2 is tagged private; type H2 is access T2'Class; -- Operations go here private type T1 is tagged record -- Keys and attributes go here Y : H2; end record; type T2 is tagged record -- Keys and attributes go here X : H1; end record; end A;
package A is type T1 is tagged private; type H1 is access T1'Class; type Array_Of_H1 is array (Positive range <>) of H1; type Access_Array_Of_H1 is access Array_Of_H1; type T2 is tagged private; type H2 is access T2'Class; -- Operations go here private type T1 is tagged record -- Keys and attributes go here Y : H2; end record; type T2 is tagged record -- Keys and attributes go here X : Access_Array_Of_H1; end record; end A;
package A is type T1 is tagged private; type H1 is access T1'Class; type Array_Of_H1 is array (Positive range <>) of H1; type Access_Array_Of_H1 is access Array_Of_H1; type T2 is tagged private; type H2 is access T2'Class; type Array_Of_H2 is array (Positive range <>) of H2; type Access_Array_Of_H2 is access Array_Of_H2; -- Operations go here private type T1 is tagged record -- Keys and attributes go here Y : Access_Array_Of_H2; end record; type T2 is tagged record -- Keys and attributes go here X : Access_Array_Of_H1; end record; end A;Subprograms
A "get" accessor may be generated for each role in the association, based on the code generation properties GenerateGet, GetName and InlineGet of the role.
Bidirectional associations must be created and deleted using the subprograms Associate and Dissociate as explained below. This is for integrity reasons: if two objects are linked by a bidirectional association, it is important that each of them has a pointer to the other. If "set" accessors were generated in that case, they could be used to create a situation where object A has a pointer to object B, but object B doesn't have a pointer to object A. Such a situation doesn't correspond to an association, but to two aggregation relationships. By generating Associate and Dissociate subprograms instead of "set" accessors for bidirectional associations, Rose/Ada prevents such violations of the association model.
Two families of subprograms, named Associate and Dissociate by default, may be generated for each role, under the control of the code generation properties GenerateAssociate and GenerateDissociate of the association. These subprograms are used to establish or break an association by establishing or breaking linkages between objects. The profiles of these subprograms depend on the multiplicities of both roles, and on the nature of the construct used to implement relationships with maximum allowable cardinality larger than 1. The code shown below corresponds to the case where the ContainerImplementation property of the roles is Array. If the ContainerImplementation is Generic, or if a container type is provided, the name of the container type is substituted to the name of the array type in the subprogram declarations.
Alternate names may be provided for the Associate and Dissociate subprograms using the code generation properties AssociateName and DissociateName. The code generation properties InlineAssociate and InlineDissociate control whether or not a pragma Inline is emitted for these subprograms.
procedure Associate (This_H2 : in H2; This_H1 : in H1); procedure Dissociate (This_H2 : in H2); procedure Dissociate (This_H1 : in H1);
The semantics of Associate is that it establishes a two-way linkage between the given objects. If the given objects are already part of an association, this association is not broken, but instead Associate raises the exception System.Assertion_Error.
The semantics of Dissociate is that it breaks the linkage between the given object and its correspondent (if any). Dissociate may be used for either extremity of the association: that's why there are two overloaded declarations, one taking an H1, the other taking an H2.
procedure Associate (This_H2 : in H2; This_Array_Of_H1 : in Array_Of_H1); procedure Dissociate (This : in Array_Of_H1);The semantics of Associate is that it establishes two-way linkages between the object designated by This_H2 and each of the objects designated by the pointers in This_Array_Of_H1. These linkages are added to those that might already exist between the object designated by This_H2 and other objects of type T1. If some of the objects designated by the pointers in This_Array_Of_H1 are already part of an association, the exception System.Assertion_Error is raised.
The semantics of Dissociate is that it breaks the linkages between each object designated by the pointers in This_Array_Of_H1 and the associated object of type T2.
- For a many-to-many association, the following Associate and Dissociate procedures are generated in addition to the ones described above for one-to-one and one-to-many associations:
procedure Associate (This_Array_Of_H2 : in Array_Of_H2; This_H1 : in H1); procedure Associate (This_Array_Of_H2 : in Array_Of_H2; This_Array_Of_H1 : in Array_Of_H1); procedure Dissociate (This : in Array_Of_H2);
The semantics of Associate is that it establishes two-way linkages between the object designated by This_H1 (or by the pointers in This_Array_Of_H1) and each of the objects designated by the pointers in This_Array_Of_H2. These linkages are added to those that might already exist between the designated objects designated by This_H2 and other objects of type T1. Note that the exception System.Assertion_Error is never raised by Associate for a many-to-many association (notwithstanding what was said above for one-to-one and one-to-many associations).
The semantics of Dissociate is that it breaks the linkages between each object designated by the pointers in This_Array_Of_H2 and the associated objects of type T1.
- For an association having a finite multiplicity (e.g. 1..4), the subprograms profiles and semantics are similar to those corresponding to the unlimited case (e.g. one-to-many), except that the Associate subprogram check the multiplicity constraint (e.g. it is not possible to associate more than 4 objects of type T1 to an object of type T2). The exception System.Assertion_Error is raised if this check fails.
Note that for associations having a role whose maximum allowable cardinality is 1, Associate never replaces the current association, if it turns out that the object on that role is already part of some association. Instead, the exception System.Assertion_Error is raised. On the other hand, for a role whose maximum cardinality is unlimited, it is always possible to augment the current association, so no exception is ever raised.
If replacement is needed for an association, it may be implemented by successively calling Dissociate and Associate.
If the Association and Dissociate subprograms are passed null pointers, they raise System.Assertion_Error. However, for the versions of these subprograms which take arrays of access values, it is acceptable for the arrays to contain null pointers: these null pointers are simply skipped. Still, the entire array must contain at least one non-null pointer.
For one-to-one associations, and for one-to-many or many-to-many associations with ContainerImplementation properties set to Array, the bodies of the Associate and Dissociate procedures are entirely generated by Rose/Ada, with the semantics explained above. They perform storage management by reusing empty slots in the arrays, allocating longer arrays if needed, and reclaiming storage when appropriate. They also preserve the integrity of the association by detecting the case where two of the access passed to Associate denote the same object. Because the generated code is part of a protected region, it can be modified by the user to meet special needs. It is however recommended that the above semantics be adhered to.
For one-to-many or many-to-many associations with a specific ContainerType, or with a ContainerImplementation set to Generic, the bodies of the Associate and Dissociate procedures are left empty.
Association Classes
For an association class, independent objects must be created to hold the attributes of the association. Therefore, a type is generated which corresponds to the association class. This type may be generated in any package: it doesn't have to be located in the same package which contains the two principal classes involved in the association.
Data Structures
The generated data structures are similar to what would be generated if the association class had a one-to-many association with each of the two principal classes. However, these data structures are essentially hidden, and the clients are only given operations to query, create or delete the association, and operations to read or modify the attributes of the association. This ensures that the integrity of the association is preserved.
The data structures are such that, from each end of the association, it is possible to find a list of auxiliary records. Each of these auxiliary records contains a value of the association class, and two pointers to both ends of the association. So it is possible to traverse from one end of the association to the other through the auxiliary record. The auxiliary record and the associated type declaration are not exported, to preserve the integrity of the association.
The generated data structures for a many-to-many association class are as follows:
package B is type T is tagged private; -- Operations go here private type T is tagged record -- Attributes go here end record; end B; with B; package A is type T1 is tagged private; type H1 is access T1'Class; type Array_Of_H1 is array (Positive range <>) of H1; type Access_Array_Of_H1 is access Array_Of_H1; type T2 is tagged private; type H2 is access T2'Class; type Array_Of_H2 is array (Positive range <>) of H2; type Access_Array_Of_H2 is access Array_Of_H2; -- Operations go here private type Attribute_B is record Attribute : B.T; The_T1 : H1; The_T2 : H2; end record; type Access_Attribute_B is access Attribute_B; type Array_Of_Access_Attribute_B is array (Positive range <>) of Access_Attribute_B; type Access_Array_Of_Access_Attribute_B is access Array_Of_Access_Attribute_B; type T1 is tagged record -- Keys and attributes go here The_B : Access_Array_Of_Access_Attribute_B; end record; type T2 is tagged record -- Keys and attributes go here The_B : Access_Array_Of_Access_Attribute_B; end record; end A;Similar code would be generated in the one-to-one and one-to-many cases.
Subprograms
Associate and Dissociate procedures are generated for the entire association. These procedures are similar to those corresponding to a simple association, except for that only one Associate procedure is generated, regardless of the multiplicity. That's because it is mandatory to specify, when establishing an association, the value of the association class. The variants of the Associate subprogram that would take array of accesses for the principal classes would also have to take array of values for the association class. This interface would be complex and difficult to use, so it is not supported by Rose/Ada.
Two accessor subprograms are generated to read and modify the value of the attributes of the association class. In order to determine the association to modify, these subprograms take:
- One access value designating an object on the cardinality 1 role of the association, for one-to-one and one-to-many associations.
- Two access values, designating objects of the two principal classes, for many-to-many associations.
That information makes it possible to unambiguously locate the association whose attributes must be read or modified. The generation of the "get" accessor is controlled by the properties GenerateGet, GetName and InlineGet of the association. Similarly the generation of the "set" accessor is controlled by the properties GenerateSet, SetName and InlineSet of the association.
The generated subprograms for an association class are shown below (we omit the Dissociate procedures which are exactly identical to those generated for simple associations):
- For one-to-one association classes, the generated subprograms are as follows:
procedure Associate (This_H1 : in H1; This_H2 : in H; This_T : in B.T); function Get_T (This_H1 : in H1) return B.T; function Get_T (This_H2 : in H2) return B.T; procedure Set_T (This_H1 : in H1; This_T : in B.T); procedure Set_T (This_H2 : in H2; This_T : in B.T);- For one-to-many association classes, the generated subprograms are as follows:
procedure Associate (This_H1 : in H1; This_H2 : in H; This_T : in B.T); function Get_T (This_H1 : in H1) return B.T; procedure Set_T (This_H1 : in H1; This_T : in B.T);- For many-to-many association classes, the generated subprograms are as follows:
procedure Associate (This_H1 : in H1; This_H2 : in H; This_T : in B.T); function Get_T (This_H1 : in H1; This_H2 : in H2) return B.T; function Get_T (This_H2 : in H2) return B.T; procedure Set_T (This_H1 : in H1; This_H2 : in H2; This_T : in B.T);As in the case of simple associations, Rose/Ada generates a full implementation for these subprograms if the roles with maximum allowable cardinality larger than 1 are represented by arrays. It generates a [statement] prompt otherwise. This implementation checks the consistency of the operations, and raises System.Assertions_Error if inconsistencies are detected. It also performs storage management, allocating and reclaiming the arrays and auxiliary records as appropriate.
Dependency RelationshipsA dependency relationship between two classes is transformed in a with clause between the corresponding library units, unless of course both classes happen to map to types in the same library unit. Note that in addition to dependency relationships, with clauses are also generated from the module dependencies appearing in the component diagrams.
Generalization Relationships (Inheritance)To some extend, the generalization relationship has already been discussed in the section about classes above.
The visibility of a generalization relationship is used to determine how the type derivation is declared. If the relationship is public, the derivation occurs in the visible part, with a private extension:
package Subclass is type Object is new Superclass.Object with private; private type Object is new Superclass.Object with record ... end record; end Subclass;If the relationship is not public, the derivation occurs in the private part:
package Subclass is type Object is tagged private; private type Object is new Superclass.Object with record ... end record; end Subclass;If the class Subclass has its code generation property TypeVisibility set to Public, then regardless of the visibility of the relationship, the code is simply:
package Subclass is type Object is new Superclass.Object with record ... end record; end Subclass;The case of multiple inheritance is more complex. If a class A has more than one superclass, there are two ways that this relationship can be represented in Ada 95: "mixin" inheritance or "multiple views" inheritance. The code generation properties TypeImplementation of the superclasses of A determine what mapping is used.
Mixin Inheritance
In mixin inheritance, exactly one of the superclasses of A must have its code generation property TypeImplementation set to Tagged. This superclass defines the "main" line of inheritance (or generalization). All other superclasses must have their code generation property TypeImplementation set to Mixin.
The type representing A is declared by deriving from its main superclass, and instantiating the generic packages associated with the mixin superclasses to add more primitive operations to the resulting type. Assume that the main superclass is called A1 and the mixin superclass A2. The generated code is as follows, assuming that A1 and A2 each declare an operation (we use the defaults for those code generation properties that have no direct bearing on multiple inheritance):
package A1 is type Object is tagged private; procedure Op1 (This : Object); private type Object is tagged record ... end record; end A1; generic type Superclass is abstract tagged private; package A2 is type Object is abstract new Superclass with private; procedure Op2 (This : Object); private type Object is abstract new Superclass with record ... end record; end A2;
with A1; with A2; package A is package A2_Instantiation is new A2 (Superclass => A1.Object); type Object is new A2_Instantiation.Object with private; procedure Op (This : Object); private type Object is new A2_Instantiation.Object with record ... end record; end A;The case of triple inheritance and beyond is handled similarly, with more instantiations adding more primitive operations. Assuming that we add a mixin superclass, A3, to the above example, we obtain the following code (A1 and A2 are unchanged):
generic type Superclass is abstract tagged private; package A3 is type Object is abstract new Superclass with private; procedure Op3 (This : Object); private type Object is abstract new Superclass with record ... end record; end A3; with A1; with A2; with A3; package A is package A2_Instantiation is new A2 (Superclass => A1.Object); package A3_Instantiation is new A3 (Superclass => A2_Instantiation.Object); type Object is new A3_Instantiation.Object with private; procedure Op (This : Object); private type Object is new A3_Instantiation.Object with record ... end record; end A;Note a constraint on mixin inheritance: if any of the mixins has a superclass, it is necessary for the "main" superclass to be a specialization of the same class (otherwise the instantiation would be illegal). This means that the following diagram is illegal because B is not identical to A and is not a subclass of A:
In the case of triple inheritance and beyond, this rule becomes slightly more complicated: all the mixins must either have no superclass, or have the same superclass, and the main class must be identical to this common superclass, or inherit from it.
Multiple Views Inheritance
In multiple views inheritance, all the superclasses of A must have their code generation property TypeImplementation set to Tagged. In addition, one of the inheritance (or generalization) relationships must be identified as the main line of descent by giving it the name "main".
There are a number of restrictions on multiple views inheritance. First, all superclasses must be limited, by setting their code generation property IsLimited to True (or because IsLimited is dominated by another property which forces limited-ness). Second, the main inheritance relationship cannot be "less visible" than the auxiliary relationships. For instance, it is not possible to have a private main inheritance and a public auxiliary inheritance. On the other hand, it is possible to have only private inheritance, or to have a public main inheritance, a public auxiliary inheritance, and another, private, auxiliary inheritance.
All the operations of the superclasses are inherited, and default bodies are generated if necessary. If two operations coming from different superclasses would result in homograph declarations for the class A, the operation coming from the main line of inheritance has precedence.
Assuming that the main superclass is called A1 and the auxiliary superclass is called A2, the following code is generated (again, we use the defaults for those code generation properties that have no direct bearing on multiple inheritance):
package A1 is type Object is tagged limited private; procedure Op1 (This : Object); private type Object is tagged limited record ... end record; end A1; package A2 is type Object is tagged limited private; procedure Op2 (This : Object); private type Object is tagged limited record ... end record; end A2; with A1; with A2; package A is type Views; type A2_With_Back_Pointer (Back : access Views'Class) is new A2.Object with null record; type Views is abstract new A1.Object with record A2_View : A2_With_Back_Pointer (Views'Access); end record; type Object is new Views with private; procedure Op (This : Object); procedure Op2 (This : Object); private type Object is new Views with record ... end record; end A;The body of subprogram Op2 is generated as follows, in order to call the corresponding subprogram of the superclass:
procedure Op2 (This : Object) is begin A2.Op2 (A2.Object (This.A2_View)); end Op2;The same scheme extends to triple inheritance and beyond. If we add superclass A3, we obtain:
package A3 is type Object is tagged limited private; procedure Op3 (This : Object); private type Object is tagged limited record ... end record; end A3; with A1; with A2; with A3; package A is type Views; type A2_With_Back_Pointer (Back : access Views'Class) is new A2.Object with null record; type A3_With_Back_Pointer (Back : access Views'Class) is new A3.Object with null record; type Views is abstract new A1.Object with record A2_View : A2_With_Back_Pointer (Views'Access); A3_View : A3_With_Back_Pointer (Views'Access); end record; type Object is new Views with private; procedure Op (This : Object); procedure Op2 (This : Object); procedure Op3 (This : Object); private type Object is new Views with record ... end record; end A;The interaction with the visibility of inheritance relationships is worth expressing in detail. In the first case, if the inheritances from A1 and A2 are changed to be private (or protected), we don't need the intermediate type Views anymore, and the code generated for A becomes:
with A1; with A2; package A is type Object is tagged limited private; procedure Op (This : Object); private type A2_With_Back_Pointer (Back : access Object'Class) is new A2.Object with null record; type Object is new A1.Object with record A2_View : A2_With_Back_Pointer (Object'Access); ... end record; procedure Op2 (This : Object); end A;In the case of triple inheritance, if the visibility of the inheritance from A3 is changed to private (or protected) the generated code for A becomes:
with A1; with A2; with A3; package A is type Views; type A2_With_Back_Pointer (Back : access Views'Class) is new A2.Object with null record; type Views is abstract new A1.Object with record A2_View : A2_With_Back_Pointer (Views'Access); end record; type Object is new Views with private; procedure Op (This : Object); procedure Op2 (This : Object); private type A3_With_Back_Pointer (Back : access Object'Class) is new A3.Object with null record; type Object is new Views with record A3_View : A3_With_Back_Pointer (Object'Access); ... end record; procedure Op3 (This : Object); end A;
OperationsThe operations given in a class specification are simply copied in the generated code.
If the code generation properties ImplicitParameter of the project and of the class are both True, a first parameter may be added to the profile of each operation. The type of this parameter is the type associated with the given class, its mode is given by the code generation property ImplicitParameterMode of the operation, and its name is given by the code generation property ImplicitParameterName of the class. These properties default to In and "This", respectively.
The code generation property ImplicitParameter at the project level defaults to False. The code generation property ImplicitParameter of the class defaults to True. By having two code generation properties, one at the project level and one at the class level, Rose/Ada supports the following usage patterns:
- The default is to never add this first parameter.
- By setting the code generation property ImplicitParameter to True at the project level, a user may decide to add the first parameter for all classes in the project.
- If some classes must be handled specially, and no first parameter is required for them, the code generation property ImplicitParameter of these classes may be set to False.
The code generation property ImplicitParameterMode can take the values In, InOut and Out. There are also circumstances in which it is useful to generate a subprogram taking an access parameter in addition (or instead of) the subprogram taking an object parameter. The code generation property GenerateAccessOperation controls whether a subprogram taking an access parameter is generated. This property is only used if ImplicitParameter is True.
Accessor Operations
Each attribute, "has" relationship, and association role has two code generation properties, GenerateGet and GenerateSet, which control generation of accessor operations for this attribute or relationship. These properties default to False.
- The "get" accessor is used to read the corresponding attribute or relationship. It is a function taking an object of the class and returning the type of the attribute.
- The "set" accessor is used to update the corresponding attribute or relationship. It is a procedure taking as in out parameter an object of the class, and a value of the type of the attribute.
For attributes and "has" relationships which are translated into discriminants, the "set" accessor doesn't make sense, and is therefore not generated (in other words, GenerateSet is dominated by RecordFieldImplementation). The "get" accessor is not generated either, because a discriminant is directly visible to clients, even for a private type: GenerateGet is also dominated by RecordFieldImplementation in this case.
In addition to (or instead of) the "get" and "set" accessors which take object parameters, Rose/Ada can also generate accessors which take access parameters. This is controlled by the code generation properties GenerateAccessGet and GenerateAccessSet.
The boolean code generation properties InlineGet and InlineSet of the attribute, relationship or role control whether a pragma Inline is generated for the accessor operations. These properties default to True.
Standard Operations
Standard operations, not explicitly present in the model, may be generated if the code generation property GenerateStandardOperations of the project is set to True (it defaults to False):
- A constructor is generated if the code generation property GenerateDefaultConstructor is not DoNotCreate (this property may take the values Function, Procedure and DoNotCreate; the default is Function). The name of the constructor is given by DefaultConstructorName (this property defaults to "Create").
- A copy constructor is generated if the code generation property GenerateCopyConstructor is not DoNotCreate (this property may take the values Function, Procedure and DoNotCreate; the default is Function). The name of the constructor is given by CopyConstructorName (this property defaults to "Copy").
- A destructor is generated if the code generation property GenerateDestructor is not DoNotCreate (this property may take the values Procedure and DoNotCreate; the default is Procedure). The name of the destructor is given by DestructorName (this property defaults to "Free").
- An equality operator is generated if the code generation property GenerateTypeEquality is not DoNotCreate (this property may take the values Function and DoNotCreate; the default is DoNotCreate). The name of the operator is given by TypeEqualityName (this property defaults to "${quote}=${quote}").
If an access type is generated for the class (in addition to the true object type), and the class is not abstract, then the above properties also control generation of the subprograms pertaining to this access type. For instance, if GenerateCopyConstructor is set to Function, and CopyConstructorName is set to "Copy", two Copy functions are generated: one for the object type, and one for the associated access type. This rule only applies to the subprograms described in this section: it doesn't apply to "get" and "set" accessors, or to user-defined subprograms.
On an abstract class, the above subprograms, if generated, are made abstract.
Note that making the constructors functions (as opposed to procedures) on classes which map to limited types may lead to difficulties, and is not recommended (although it may make sense in some circumstances).
The boolean code generation properties InlineDefaultConstructor, InlineDestructor, InlineCopyConstructor and InlineEquality of the class control whether a pragma Inline is generated for the above operations. All these properties default to False.
Subprogram Implementation
The code generation property SubprogramImplementation is used to control the code generated for a subprogram body. This property can take the values Body, Renaming, Separate, Abstract and Spec. The default is Body. The semantics of these choices are as follows:
- If SubprogramImplementation is set to Body, a normal body is generated.
- If SubprogramImplementation is set to Renaming, a renaming-as-body is generated for the subprogram body. The name of the renamed subprogram is obtained from the property Renames of the operation.
- If SubprogramImplementation is set to Separate, a stub is generated instead of a normal body.
- If SubprogramImplementation is set to Abstract, no body is generated, instead the specification of the subprogram includes the reserved words "is abstract" (making it an abstract subprogram). It is an error to set SubprogramImplementation to Abstract on an operation of a non-abstract class.
- If SubprogramImplementation is set to Spec, no body is generated, but the subprogram is not made abstract. This option (which doesn't result in legal code) is intended to be complemented by the insertion, in some protected region of the generated code, of a pragma (like Import or Interface) which specifies the implementation of the subprogram without providing an explicit body.
In addition, the code generation property Inline is used to control whether or not a pragma Inline is generated for the operation. This property defaults to False.
Visibility
The visibility of each operation determines where it is declared. A public operation is declared in the visible part of the associated package, a protected or private operation is declared in the private part of the package, and an operation with only implementation visibility is declared in the package body (note that such an operation is not inherited).
Overriding
The code generator takes care to generate the proper overriding subprogram declarations whenever the language requires it:
- If an abstract operation is inherited by a concrete class. This includes the case where the concrete class has several superclasses, either because of mixin inheritance, or because of multiple views inheritance.
- If a function returning a value of the superclass is inherited by a concrete class. The language rules state that such a function becomes abstract by derivation.
- If one of the "back pointer" types generated for multiple views inheritance inherits an abstract operation. That's because the "back pointer" types are always concrete.
In addition to these cases where overriding is required by the language, the code generator also generates an overriding declaration if the inherited operation has it code generation property GenerateOverriding set to True. This property defaults to True.
Each overriding subprogram declaration has the same parameter names, modes and default values as that of the original subprogram. The proper type name is substituted for each controlling operand. The types of other operands are left unchanged.
Rose/Ada generates a body for each overriding subprogram declaration. This body does a view conversion of its controlling parameters, and calls the corresponding operation of the parent type (or superclass). While this implementation in itself is not extremely useful, it turns out that most overridden subprograms first call the operation of their parent type, and then perform additional processing specific to the added record components. By generating the call to the superclass' operation, Rose/Ada makes it easy to adhere to this model. (This is similar to sending a message to super in languages like Smalltalk or Java.)
Note that there is not property GenerateOverriding for the "get" and "set" accessor. That's because most of the time the inherited implementation is appropriate. Therefore, no overriding declaration is ever generated for these accessors.
Bodies
Except for the accessor operations, the body generated for an operation contains only a [statement] prompt. This ensures that the code can be compiled under Rational Apex, but that any attempt to execute an operation whose body is incomplete raises Program_Error. Note that, if using another compiler, the prompt is likely to result in syntax errors: legal code must be written to replace these dummy bodies before the code can be compiled.
The code generation properties EntryCode and ExitCode associated with an operation contain Ada statements which are copied verbatim at the beginning and at the end, respectively, of the statement part of the generated body. These properties are empty by default.
User-Defined Initialization, Assignment and FinalizationControlled types may be produced for any type whose TypeImplementation is Tagged. In addition to producing the proper type structure, Rose/Ada is also capable of generating overriding declarations for the procedures Initialize, Adjust and Finalize, and for the operator "=".
The code generation property TypeControl of a class may take the following values:
- None: the type is not a controlled type
- InitializationOnly: the type is a controlled type, with only user-defined initialization.
- AssignmentFinalizationOnly: the type is a controlled type, with only user-defined assignment and finalization.
- All: the type is a controlled type with both user-defined initialization and user-defined assignment and finalization.
TypeControl defaults to None. For a class whose TypeImplementation is not Tagged, TypeControl is dominated, and the generated type is not a controlled type. A class whose TypeControl property is not None must not be involved in a multiple inheritance relationship.
When discussing the effect of TypeControl, we'll use the following class hierarchies as examples:
If TypeControl is not None, the declaration of the type associated with a class is changed as follows:
- If the class has no superclass, the type is derived from Ada.Finalization.Controlled or Ada.Finalization.Limited_Controlled, depending on the value of the property IsLimited. This derivation occurs on the full type declaration:
package A is type Object is tagged private; private type Object is new Ada.Finalization.Controlled with record ... -- Attributes go here end record; end A;- If the class has a superclass, an auxiliary type is introduced, which contains the attributes of the class, and is used to build the actual type associated with the class. Again, this type is derived from Ada.Finalization.Controlled or Ada.Finalization.Limited_Controlled, depending on the value of the property IsLimited:
with B; package C is type Object is new B.Object with private; private type Controlled_Object is new Ada.Finalization.Controlled with record ... -- Attributes go here end record; type Object is new B.Object with record Contents_Of_C : Controlled_Object; end record; end C;
The name of the auxiliary controlled type is given by the code generation property TypeControlName, which defaults to Controlled_${type}. The name of the intermediate record component is always Contents.
If the code generation property TypeControl is set to InitializationOnly or to All, an overriding declaration for Initialize is inserted in the private part of the package (even if the controlled type is declared in the visible part):
package A is type Object is tagged private; private type Object is ...; procedure Initialize (What : in out Object); end A; package C is type Object is new B.Object with private; private type Controlled_Object is ... procedure Initialize (What: in out Controlled_Object); type Object is new B.Object with ... end C;If the code generation property TypeControl is set to AssignmentFinalizationOnly or to All, overriding declarations are inserted for Adjust and Finalize in the private part of the package, and a declaration for the operator "=" is inserted in the visible part. Adjust is only declared if IsLimited is False:
package A is type Object is tagged private; function "=" (Left, Right : in Object) return Boolean; private type Object is ... procedure Adjust (What : in out Object); procedure Finalize (What : in out Object); end A; with B; package C is type Object is new B.Object with private; function "=" (Left, Right : in Object) return Boolean; private type Controlled_Object is ... procedure Adjust (What : in out Controlled_Object); procedure Finalize (What : in out Controlled_Object); type Object is new B.Object with ... end C;In the declaration of procedures Initialize, Adjust and Finalize, the name of the only parameter is given by the code generation property ImplicitParameterName for the class. In the declaration of operator "=", the parameters are named Left and Right.
The code generation property TypeControl, when it is not None, dominates the properties GenerateDefaultConstructor, DefaultConstructorName, GenerateCopyConstructor, CopyConstructorName, GenerateDestructor, DestructorName, GenerateTypeEquality and TypeEqualityName: no standard operation is generated, and the name of the equality operator, when it is generated, is always "=". This is because standard operations and controlled types are two different mechanisms to achieve similar effects, and they are not intended to coexist in a single class.
GenerateGet and GenerateSet may be used in conjunction with controlled types: the accessor operations which are generated correctly take into account the internal structure of the type (possibly with an auxiliary controlled type) to access the various components.
A class whose code generation property TypeControl is not None may be abstract. However, the auxiliary controlled type (if generated) is never made abstract, and the Initialize, Adjust and Finalize procedures (if generated) are not made abstract either.
Rational Software Corporation
http://www.rational.com support@rational.com techpubs@rational.com Copyright © 1993-2001, Rational Software Corporation. All rights reserved. |