Skip to content

Conversation

@AlpTorac
Copy link
Contributor

@AlpTorac AlpTorac commented Oct 25, 2024

Introduction

Implemented a new plug-in called "initialisers" (its full name is cipm.consistency.initialisers), which contains a series of classes/interfaces called initialisers. Initialisers' purpose is to ease the construction of Java-related objects (such as CompilationUnit, NamespaceClassifierReference, various expressions, etc.) used by JaMoPP from outside (JaMoPP objects), by addressing their complex and partially unintuitive construction.

The "initialisers" plug-in also contains tests for some top-level initialiser interfaces and further utility tests.

Note: Some commits, where initialisers for JaMoPP objects are implemented, are not functional because of unavoidable cyclic dependencies between certain initialiser packages. Such commits are indicated via the "(WIP)" in their message. Although it could be possible to have multiple commits for such initialiser packages, doing so would overcomplicate the pull request.

Description of Initialisers

Each initialiser has a corresponding type SomeType (which is its designated type) within the type hierarchy of the JaMoPP objects. For each initialiser with the designated type SomeType, there is an initialiser interface ISomeTypeInitialiser and its concrete implementation SomeTypeInitialiser (as a class). If SomeType is not instantiable, there will be no concrete implementation of its initialiser, only an initialiser interface. Here, a type is also considered to be instantiable, if it is an abstract class or interface that has a direct implementor. For example, CompilationUnit is directly instantiable, since CompilationUnitImpl directly implements it.

Each initialiser provides a method called instantiate(), which creates an instance of the designated type of the initialiser. Keep in mind that the instance created this way might be missing some of its required attributes and require further initialisation (see "Adaptation of Initialisers" below). The signature of the instantiate() method is overridden in each initialiser interface, in order to circumvent type inconsistencies.

Each initialiser is meant to provide modification methods, which summarise certain modifications on the initialiser's designated type that one would normally have to perform by directly calling methods of its designated objects. Because of the complex structure of JaMoPP objects, modification methods' return values are used to indicate, whether the modification was successful (similar to add(...) and remove(...) methods for some collections).

Type hierarchy of Initialisers

The one of the core ideas behind the implementation of JaMoPP objects is to encapsulate certain aspects with interfaces and to have those JaMoPP objects extend the interfaces, which encapsulate their various aspects. For example: The NamedElement interface in JaMoPP encapsulates the "name" attribute and is implemented/extended by JaMoPP objects that should have the "name" attribute. Because there are lots of different JaMoPP objects that share certain attributes (or groups of attributes), it is possible to spare large amounts of code by following that idea.

This idea is used in the initialisers as well: The modification methods provided by the initialisers are mostly implemented directly in their initialiser interface (as default methods). Since Java allows extending multiple interfaces, new initialisers can simply extend existing initialisers. Because of this, the type hierarchy of initialisers mirrors the type hierarchy of JaMoPP objects. Some implications are:

  • Changes to JaMoPP objects can be applied to initialisers in a similar way, in order to keep them synchronised with JaMoPP.

  • Adding initialisers for new JaMoPP objects is simple, as it is possible to implement/extend existing initialiser interfaces.

  • One can use the most general initialiser type in method signatures to abstract away (to a certain degree) from the concrete initialiser and also from its designated type.

    • This further implies that initialisers can be used as parameters in parameterised tests, where certain attributes of certain JaMoPP objects are relevant. For example, if an attribute A is defined in an interface IAttrA (in JaMoPP) for JaMoPP objects O1, O2 and O3 that implement IAttrA; The initialiser of IAttrA, IAttrAInitialiser, can be used as the parameter type and the test method can be parameterised over IO1Initialiser, IO2Initialiser and IO3Initialiser that extend IAttrAInitialiser. One can then abstract away from the concrete initialiser and perform tests on the attribute A.
    • The newInitialiser() method of the initialisers can be used to create a fresh initialiser instance of the same type, without having to find out the concrete type of the initialiser in question.

Adaptation of Initialisers

Although all instances created by concrete initialisers (JaMoPP objects) are functional EObject instances, they may not be "valid" in terms of JaMoPP. This is caused by the fact that EObject (a meta-metamodel) being used to represent Java constructs (Java language is a metamodel). It is therefore possible to have JaMoPP objects that do not represent "valid" Java constructs.

Since some concrete initialisers can create "invalid" instances (obj) of their designated type, they have the means to be adapted by Initialiser Adapter Strategies. Initialiser adapter strategies can be added to certain initialisers, in order to add additional steps to their initialise(obj) method and perform other necessary modifications, which ultimately make obj "valid". Even though it would be possible to perform those necessary modifications manually, it may not always be feasible to do so, if the type of the concrete initialiser instance is not known (in parameterised tests explained previously, for instance).

Initialiser adapter strategies can also be used to extract construction steps, such as adding a "default" container to a JaMoPP object.

Future notes

The top-level initialiser interfaces defined in this plug-in can be re-used in other technologies. The initialisers defined here for JaMoPP objects can also be used in other projects that make use them. Therefore, it could be a good idea to eventually move this plug-in to its own repository.

@AlpTorac
Copy link
Contributor Author

A pull request filled with tests utilising the initialisers implemented here will follow soon.

@AlpTorac
Copy link
Contributor Author

The attempt to create a stacked pull request failed and the pull-request branch was reset to its previous state, hence the force-push.

The abstract class encapsulates the logic behind storing adaptation
strategies for initialisers
Initialiser packages (IInitialiserPackage implementors) can be used to
grant centralised access to related groups of initialisers. They mimic
org.emftext.language.java.JavaPackage and allow package-based access to
initialisers. The content of initialiser packages is to be defined by
overriding certain methods (refer to the documentation within the
IInitialiserPackage interface).
for Java-related classes (all of them extend EObject) used by JaMoPP.
The main purpose of this interface is to make it easier to declare
methods for the said classes.

This interface can also be used to filter JaMoPP-related initialisers
from EObject initialisers, if initialisers for other EObject utilising
techologies are implemented in the future.
org.emftext.language.java.classifiers
org.emftext.language.java.containers
org.emftext.language.java.expressions
org.emftext.language.java.generics
org.emftext.language.java.instantiations
org.emftext.language.java.literals
org.emftext.language.java.members
org.emftext.language.java.modules
org.emftext.language.java.parameters
org.emftext.language.java.references
org.emftext.language.java.statements
These strategies can be used to adapt the initialisers, so that they can
successfully initialise the objects they create within tests (to be
implemented in another plug-in in the future).
Implemented to extract some mutual method implementations.

Provides default methods that can be used by tests, which check whether
necessary initialisers are present and can be accessed.
Contains information about the naming convention used while naming
initialisers
Contains some information about the naming convention used in JaMoPP

Provides methods to access EObject implementations of Java-related
elements in JaMoPP. Includes methods that can be used to retrieve
initialisers corresponding to those JaMoPP-elements by using the name of
the objects they are supposed to create.
These tests can be run to ensure that there are initialisers for each
Java-related element in JaMoPP.

Note: Relative paths are used within tests to access initialiser files. Therefore, if the position of cipm.consistency.initialisers.jamopp.utiltests changes relatively to the initialiser files, the said path will have to be changed for tests to work.
Those tests will aim to ensure that initialiser discovery by using
initialiser packages works as intended. They will furthermore check,
whether initialiser adaptation works as expected.
Their purpose is to be used to verify that initialisers can modify the
objects they create as intended. They can also be used to test some
static methods of IInitialiser (the top-level initialiser interface).

The said dummy constructs also include a re-usable abstract class for
further dummy constructs (to be implemented in the following commits).
Add a package that contains 4 dummy objects, as well as their dummy
initialisers. Neither the said objects nor their initialisers form a
type hierarchy (i.e. they do not extend one another). They are meant to
be used in tests, which verify some static methods of IInitialiser (the
top-level initialiser interface).
Add a package that contains some dummy objects and their dummy
initialisers, which extend one another. They are to be used in tests
that verify some static methods of IInitialiser (the
top-level initialiser interface).
Use the same JUnit related dependencies as vsum tests

Add JUnit Argument dependencies as well
Use the utility method in isInitialiserFor

to retrieve the type of object they instantiate.

Added to spare code duplication in tests
in order to make sure that the tests are working as intended. Computing and determining this dynamically can lead to misleading results. It is better to synchronise initialiser counts upon adding new initialisers, since adding new initialisers is not expected to be common.
to retrieve types, which should have a (concrete) initialiser. Although using the suffix each implementation has ("Impl") is possible, having a dynamic way of separating such types is more desirable.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant