Developer documentation
If you want to find out how to extend the Generator and adapt it to fit your own needs, this page is for you.
General architecture and workflow
When invoking the Generator, it will (roughly) perform the following actions:
Each of these steps has a corresponding Scala class (called Services hereafter) to take care of it. So, when extending the Generator there are two basic approaches:
-
modifying one of the Services (either directly or by means of subclassing)
-
intercepting the workflow by adding more Services
To give you a rough idea of what Services are available and where they will be located, here is an overview of the Generator’s architecture:
The Scala Abstract class representation
The Abstract class representation (ACR) is the hearth of the Generator. It forms the meta-model for the code to generate and provides facilities to adapt model instances for different purposes.
In short each element of a Scala class has a matching class in the ACR. E.g. a method will be
represented by an instance of SMethod
, attributes become SAttribute
s and a class itself will
be mapped to a SClass
. As the main purpose of the Generator is to provide executable software
models, an abstraction of a model, the SModel
is provided as well. It is pretty straightforward
and simply provides all the classes in the model.
Types and Classes
Models seldom start from scratch. Instead, they will reuse certain data types, such as String
s,
Integer
s, etc. as attributes in the classes that are specified by the model. Usually these data
types are provided by some library and may be mapped to a basic data types of the target programming
language (or a converter is provided along with them).
The Generator follows a similar approach: it provides an abstraction for user-supplied classes (i.e.
the classes specified within the model). This is the SClass
class already mentioned above.
Additionally a SType
class is provided to handle predefined data types. When generating source
code these types are expected to exist "as are" and will not be tackled further. I.e. no .scala
files will be created for SType
instances.
As types and classes are concepts only found at a more abstract level and are irrelevant when it
comes to Scala source code, they share a common superclass - STypedElement
- which will be used
whenever type information (no matter if it is user-supplied or generic) is needed.
Working with the ACR on a more abstract level
The ACR forms a trade-off between an abstract meta-model on one side, and a straightforward (hence
simplified) usage on the other side. Therefore low-level abstraction will oftentimes be wrapped by
some higher abstraction. E.g. a SMethod
consists of abstract SMethodStatement
s and may thus
provide abstract functionality for modification. However a SMethodStatement
simply wraps a
String
(the actual statement). Dealing with the statements themselves may therefore become
pretty cumbersome at a certain point. To circumvent this issue, the ACR's elements should be
extended by means of subclassing to provide more tailored versions which handle the additional
complexity.
A good example for this approach is the extension of the SMethod
class to easily generate
Getters and Setters for attributes:
class SMethod(val name: String,
val result: STypedElement,
val params: Seq[SMethodParameter],
var implementation: Seq[SMethodStatement]) {
// implementation omitted
}
class SGetter(attr: SAttribute) extends SMethod(
name = s"get${attr.name.firstLetterToUpperCase}",
result = attr.attrType,
params = Seq.empty,
implementation = Seq(SMethodStatement(content = attr.name, usedTypes = Set(attr.attrType))) {
// implementation omitted
}
Getters may now be generated by only specifying the attribute they are created for - the cumbersome creation of the actual method statements is hidden within the class implementation.
Working with models
a general introduction should be added |
Incremental construction of SModel
instances
Adding new classes to an SModel
instance is pretty straightforward: its default implementation
(SimpleSModel
) already provides a addModelClass(clazz)
method.
However one often encouters the issue of some kind of cyclic reference in a model, i.e. a class C0
referencing other classes C1, … Cn
which in turn keep references until one of these references
C0
again. In this case only calling SimpleSModel.addModelClass(C0)
will not work as it requires
C1, … Cn
to already be created. This leads to C0
requiring itself to be created already.
Thus a SClass
does not need to be created in one go but can be extended later on. To keep track
of all classes which are already (at least to some extend) build, the STypeRegistry
was created.
It will work as a repository for all SClass
es
So instead of creating a SClass
from scratch each time it is referenced, the STypeRegistry
should be queried. Only if the query does not yield any results, a new class has to be constructed.
SClass
only if requiredval clazz: SClass = STypeRegistry
.query(clazzName, clazzPackage)
.getOrElse {
val newClazz: SClass = new SClass(name = clazzName, sPackage = clazzPackage)
STypeRegistry.addType(newClazz)
newClazz
}
The STypeRegistry
already provides instances for some default data types so they do not need to be
created.
Building a model from EMF
Currently the generator only supports generating models from EMF. When dealing with an ecore some additional problems have to be faced. The most prominent one is that EMF does not use the basic data types of Java and Scala directly but wraps them with an EMF specific class instead.
When resolving such a type without further attention, it will simply be converted to an SType
.
Hence compilation (see Writing to the File system) will fail as the required types are not present and they do
not work as expected (e.g. the +
operator may not be applied to an EString
).
To solve this issue, the EMF types need to be mapped to their Java-equivalents. This is what the
EmfTypeTranslator
should be used for. Its getSClassFromEmf
method will provide the matching
types for all EMF wrappers and nothing for all other classes.
Modifying SModel
instances
To change the contents of a SModel
, the whole ACR implements the _Visitor pattern_[1].
This approach is used by the whole Generator workflow.
More precisely the following Visitors are predefined:
-
GetterSetterGeneratingVisitor
will generate the necessary methods for all attributes -
SyncEnhancingVisitor
will make the setters notify the Synchronization context when an attribute is updated and insert the model’s classes into the Synchronization’s inheritance hierarchy -
SModelFSWriter
will write the model’s classes to the file systems, compile them and generate a JAR file for the whole model