Papyrus SW designer supports the generation of C/C++ and Java code. This section deals with some common aspects and code generation for component-based models
Handling of multiplicities
Attributes and parameters have a multiplicity, denoted by a lower and upper bound, such as [0..*]. By default the multiplicity is [1..1] with the shorthand notation . The mapping of such a multiplicity towards a programming language attribute or parameter is straight forward. However, the mapping is less clear in case for other multiplicities in the following list.
- [n]: fixed with n elements
- [0..n]: a set with 0-n elements. mapped to an array of
- [m..n]: a set with minimum n and up to m elements
- [0..*]: an unbounded list
The first three cases are by default mapped to an array of fixed size n, i.e. the different lower bounds are ignored. The last case is an array with undefined size ("" in C++ or Java, but the user needs to specify a size during instantiation).
This is not always suitable, the standard libraries for C++ and Java have different variants of lists that might be more appropriate, for instance [0..*] elements of type T could be be mapped to an ArrayList<T>. Therefore, the C++ and Java code generator support a <<ListHint>> stereotype that enables the user to specify how the different elements are mapped. The stereotype has an attribute for the cases above, but unifies the cases [0..n] and [m..n] cases (it is still possible to take the lower multiplicity into account, see below). The stereotype distinguishes three cases:
- [n]: fixed with n elements
- [0..n], [m..n]: bound with max. n elements
- [0..*]: variable / an unbounded list
The user can specify the type to use for each of the cases, the "magic" word [typeName] gets replaced with the element type, e.g. the string "java.util.ArrayList<[typeName]>" for the attribute "variable" of the stereotype will produce an ArrayList of the given type. The code generator also replaces the magic strings [lower] and [upper] with the respective choices from the model, if present in the hint. The stereotype is applied to a package and applies to all contained element. The idea is that mapping should be consistent within a model or at least sub-packages.
Template bindings in programming languages are done "on the fly" when declaring the type of an attribute or parameter. Instead of declaring a new bound type and then typing attributes with it, bindings are done while typing.
For instance, in Java we can type an attribute directly as a Set<int>. UML only supports an explicit declaration of a new type (e.g. a class) which has a template-binding relationship in which formal template parameters (that are part of the a template signature) are substituted with concrete types. This could be tedious and lead to a large number of additional types. Therefore, the common code generation profile supports a <<TemplateBinding>> stereotype that can be applied to an attribute or parameter. The "actuals" attribute of this stereotype contains a list of actuals. It's the responsibility of the user that the number and order of actuals corresponds to the expectation in the template signature.
Code generation for component based models
The result of the previous phase is a component model of the application, enriched with reified connectors and expanded containers. The code generation starting from this model requires two actions: (1) the realization of the component deployment consisting of a splitting the global model into sub models for each execution node and (2) the transformation of ports and connectors that do not have a direct equivalent concepts in object-oriented programming languages.
The former is not as trivial as it may seem since dependencies have to be taken into account and composites may have to be deployed on multiple nodes due to allocations of theirs parts. This imposes constraints such as only read-only attributes in these composites to ensure consistency. We do not discuss this issue further in the context of this paper and focus on the second aspect, the transformation of ports and connectors.
Ports and connectors do not possess a direct equivalent in an object-oriented programming language. Thus, it is necessary to relate component-oriented concepts to object-oriented concepts, i.e. classes, interfaces, attributes and operations. We distinguish ports with provided interfaces and ports with required interfaces (a port might also have both). A port providing an interface is an access point to a service and a caller needs to obtain a reference to this service, in our implementation pattern via a specific operation. For instance, if a component owns a port "p" providing interface "I", the realization of a component needs an operation "get_p" returning a reference to the service. The implementation of this operation is determined automatically: in case of a delegation connector between the port and an internal part of a component, this reference is returned, otherwise a reference to the component reference itself is returned.
A port with a required interface is an interaction point which requires a reference of another component that provides the interface. Thus, the component needs to store this reference and provide an operation to initialize the reference in the moment of instantiation. For instance, a port "q" with an required interface is transformed into an attribute which stores a reference to a port providing the interface and an operation "connect_q" which initializes this reference.
Connectors within a composite are transformed into a realization of a specific operation that creates the connections between parts, i.e. contains suitable combinations of some-part.connect_some-port other-part.get_other-port.
Once the transformation of component-based models to object-oriented models is done, a "classical" code generator taking an object-oriented UML model as input is sufficient for the code generation (in our case C++). For each class or interface, a C++ class is generated. The UML packages are transformed into C++ name spaces. The organization of the files follows the same as in Java. A name space corresponds to a directory and thus reflects the package structure in UML.
The dependencies to the external packages are translated into include directives to libraries. The generated code can now be compiled, for instance in the context of the Eclipse CDT environment.