Introduction
Purpose
This tutorial illustrates how to extend the Cecilia ADL toolchain for supporting for instance a new programming model in addition to the default one which is called thinkMC. To keep the tutorial as simple as possible, we will assume that the new programming model is the same as the thinkMC programming model except that it will be called myLanguage. This way, we will identify in this tutorial the set of plugin components that need to be developed for supporting such a new programming model without going into the implementation details of these plugin components. Developers are then encouraged to use the extension module that is provided with this tutorial for implementing their own extensions.
Dependencies This tutorial has been successfully run considering the following tools and versions:
- Eclipse version 3.3.2 (Europa) * maven plugin version 0.9.3.20080421-2352
Getting Started
- First, download the source code of the tutorial from here downloads/cecilia-2.1.0-tutorial.zip or here downloads/cecilia-2.1.0-tutorial.tar.gz.
- Extract the source code. A directory called cecilia-2.1.0-tutorial will be created.
- Launch your Eclipse with maven plugin installed
- Import the SimpleExtension project found in the cecilia-2.1.0-tutorial into your Eclipse workspace.
- Test the execution of the extension
- First execute the goal process-test-resources of the maven project. An execution target is already prepared for that purpose. To find it, open the dialog box Run -> Open Run Dialog.... You should find an execution target called SimpleExtension process-test-resources under the Maven Build tree. Select it, and then click on Run. This will unpack the test resources. This step has to be repeated each time the project is cleaned.
- Then, launch the unit tests that verify the execution of the extension. An execution target is already prepared for that purpose. To find it, open the dialog box 'Run -> Open Run Dialog...'. You should find an execution target called SimpleExtension under the JUnit tree. Select it, and the click on Run.
You should see the following output on the console window:
[step] [basic-factory] Load ADL 'test.extension.SimpleComponent' [org.objectweb.fractal.cecilia.primitive.myLanguage.CodeLoader::loadImplementation()] Hello World [org.objectweb.fractal.cecilia.primitive.myLanguage.controllers.PrimitiveControllerLoader::checkComponent()] Hello World [org.objectweb.fractal.cecilia.primitive.myLanguage.controllers.PrimitiveControllerLoader::getControllerInterfaces()] Hello World [step] [basic-factory] ADL loaded successfully, build task graph [org.objectweb.fractal.cecilia.primitive.myLanguage.SimpleVisitor::visit()] Hello World [step] [basic-factory] Task graph built successfully, execute tasks [io] gcc: C:\work\ozcan\objectweb\cecilia\toolchain\cecilia-adl\target\test-build\obj\fractal\lib\defaultComposite.o [io] gcc: C:\work\ozcan\objectweb\cecilia\toolchain\cecilia-adl\target\test-build\obj\fractal\lib\CIdelegate.o [io] gcc: C:\work\ozcan\objectweb\cecilia\toolchain\cecilia-adl\target\test-build\obj\fractal\lib\BCdelegate.o [io] gcc: C:\work\ozcan\objectweb\cecilia\toolchain\cecilia-adl\target\test-build\obj\test\extension\SimpleComponent.o [io] gcc: C:\work\ozcan\objectweb\cecilia\toolchain\cecilia-adl\target\test-build\obj\test\extension\SimpleComponent\test.o [io] gcc: C:\work\ozcan\objectweb\cecilia\toolchain\cecilia-adl\target\test-build\obj\test\extension\SimpleComponent\runner.o [io] gcc: C:\work\ozcan\objectweb\cecilia\toolchain\cecilia-adl\target\test-build\obj\test\extension\SimpleComponent\runner\second-stage.o [io] gcc: C:\work\ozcan\objectweb\cecilia\toolchain\cecilia-adl\target\test-build\obj\test\extension\SimpleComponent\runner\first-stage.o [io] ld: C:\work\ozcan\objectweb\cecilia\toolchain\cecilia-adl\target\test-build\obj\test_extension_SimpleComponent.exe
Overview
As presented in the toolchain architecture document, the Cecilia ADL toolchain is composed of two main modules: the loader module and the processing module. Both modules will be involved in the extension of the toolchain for a new programming module support. The loader module should be extended to load the implementation files that are written in the new programming module, while the processing module should be extended for implementing the new processing operations (e.g. code generation for new macros).
The following sections present the extension of both modules respectively. Then, the unit test mechanism provided to test the extension is presented, followed by a discussion about how to go further for turning this tutorial project into a real extension.
Extension of the Loader Module
The loader module is designed for accepting extensions supporting new programming models. That is, the support for loading an implementation files written in a new programming module can be added as a plugin into the default loader component chain.
Two plugin components must be provided for that purpose: a plugin component for loading the implementation files written in myLanguage, and another plugin component for adding the controllers to the components implemented in myLanguage.
Implementation Code Loader
A plugin selector is already present in the loader module for loading plugins according to the implementation language used for writing the primitive components. This plugin selector looks for plugins respecting the following naming convention: org.objectweb.fractal.cecilia.primitive.<nameOfTheProgrammingModel>.ImplementationCodeLoader.
According to this convention, when a primitive component written in myLanguage will be found in the architecture being compiled, the toolchain's plugin selector will look for a plugin component called org.objectweb.fractal.cecilia.primitive.<myLanguage>.ImplementationCodeLoader within the class path. If such a plugin is found, it will be automatically loaded, and the load operation for loading the corresponding implementation file will be delegated to this plugin.
The architecture description of the implementation code loader plugin component for myLanguage is as follows. As depicted in the below snippet, an implementation code loader component implements the ImplementationCodeLoader interface. This interface will be invoked by the plugin selector for delegating the load operation to the loaded plugin.
<definition name="org.objectweb.fractal.cecilia.primitive.myLanguage.ImplementationCodeLoader" > <interface name="plugin" role="server" signature="org.objectweb.fractal.adl.implementations.ImplementationCodeLoader" /> <content class="org.objectweb.fractal.cecilia.primitive.myLanguage.CodeLoader" /> </definition>
The implementation that we provide for that plugin within the context of this tutorial is very simple. It implements exactly the same behavior as the loader component that loads default thinkMC files and just prints Hello World to signal that it is correctly invoked. As depicted in the below snippet, the implementation myLanguage.ImplementationCodeLoader class just inherits from the default CCodeLoader class and prints its message before invoking the loadImplementation() method provided by its parent.
public class CodeLoader extends CCodeLoader { @Override public Object loadImplementation(final String signature, final String language, final Map<Object, Object> context) throws ADLException { System.out.println("[" + this.getClass().getName() + "::loadImplementation()] Hello World"); return super.loadImplementation(signature, language, context) ; } }
As an exercise, we propose to developers to try changing the extensions of the implementation files that are written in myLanguage from .c to .ml. To do this exercise, CCodeLoader class should be a good source of inspiration.
Controller loader
The control behavior of a component and/or its implementation rules may depend on the programming model used for implementing the component. For this reason, when the toolchain is extended with a new programming model, a default controller loader must be provided as well for adding at least the default control interfaces to the components that are implemented using this new programming model. A plugin selector is already present for that purpose. This plugin selector looks for plugins respecting the following naming convention: org.objectweb.fractal.cecilia.primitive.<nameOfTheProgrammingModel>.C<nameOfTheMembrane>ControllerCodeLoader.
Another naming convention to keep in mind about the controller plugins is that the default membrane for primitive components is called primitive. The same way, the default membrane for composite components is called composite. In other words, if no membrane (i.e. controller tag) is specified for a primitive component, the membrane implementation called primitive is considered to be provided. Hence, when the toolchain is extended for supporting myLanguage, at least plugin called org.objectweb.fractal.cecilia.primitive.<myLanguage>.C<Primitive>ControllerCodeLoader must be provided in the class path.
The architecture description for this controller loader is as follows.
<definition name="org.objectweb.fractal.cecilia.primitive.myLanguage.CPrimitiveControllerLoader" extends="org.objectweb.fractal.cecilia.adl.controllers.ControllerLoaderPluginType"> <content class="org.objectweb.fractal.cecilia.primitive.myLanguage.PrimitiveControllerLoader" /> </definition>
The above plugin component implements the type ControllerLoaderPluginType. That is, as depicted below, it implements the PrimitiveChecker interface and may use the XMLNodeFactory.java and the ImplementationCodeLoader interfaces.
Within the context of a controller loader component, the XMLNodeFactory.java is typically used for enhancing the architecture of the loaded component with some control interfaces at the AST level, and the ImplementationCodeLoader is used for loading the implementation files of some generic control interfaces, if required.
<definition name="org.objectweb.fractal.cecilia.adl.controllers.ControllerLoaderPluginType"> <!-- The "plugin" server interface --> <interface name="plugin" role="server" signature="org.objectweb.fractal.cecilia.adl.components.PrimitiveChecker" /> <interface name="node-factory" role="client" signature="org.objectweb.fractal.adl.xml.XMLNodeFactory" /> <interface name="implementation-code-loader" role="client" signature="org.objectweb.fractal.adl.implementations.ImplementationCodeLoader" /> </definition>
As for the above implementation code loader plugin, the implementation of the controller loader plugin that we provide is very simple. It inherits from the default PrimitiveControllerLoader component that is provided for thinkMC components, and just prints Hello World before delegating the checkComponent() and getControllerInterfaces() methods of its parent.
public class PrimitiveControllerLoader extends org.objectweb.fractal.cecilia.primitive.thinkMC.controllers.PrimitiveControllerLoader { @Override protected void checkComponent(final ComponentContainer container) throws ADLException { // TODO Auto-generated method stub System.out.println("[" + this.getClass().getName() + "::checkComponent()] Hello World"); super.checkComponent(container); } @Override protected List<Interface> getControllerInterfaces( final ComponentContainer container, final Map<Object, Object> context) throws ADLException { // TODO Auto-generated method stub System.out.println("[" + this.getClass().getName() + "::getControllerInterfaces()] Hello World"); return super.getControllerInterfaces(container, context); } }
As an exercise, we propose to developers to modify the implementation of this controller loader plugin, and to add a new control interface to each primitive component. This new control interface that we call MyLanguageController can implement a method called sayHello that prints Hello World when it is invoked at runtime. For such an extension, the AST will have to be extended. For that purpose, reading the AST documentation may be useful.
Another exercise may consist in implementing another plugin component for a new membrane behavior, say we call MyMembraneBehavior. Writing such a new controller loader plugin will contribute to the understanding of the above naming conventions to be respected. The same way, developers may try to provide a controller loader for primitive components that are written in thinkMC.
Extension of the Processing Module
The processing module of the Cecilia ADL toolchain is designed for loading plugins according to the implementation language (or programming model) that is used for programming the components. Hence, the extension of the processing module for supporting myLanguage consists in providing a visitor plugin that will be loaded when a primitive component whose implementation is in myLanguage is found in the compiled architecture.
To keep this tutorial as simple as possible, we will assume that the processing to be done for myLanguage is fully compliant with the one for thinkMC. Thus, the visitor plugin for myLanguage will inherit from the plugin for thinkMC and will just add a simple visitor component that prints Hello World when it is invoked.
The plugin selector for loading visitor components used to process primitive components written in a given programming model looks for plugins in respect to the following naming convention: org.objectweb.fractal.cecilia.primitive.<nameOfTheProgrammingModel>.Visitor. According to this naming convention, when a component written in myLanguage is found in the compiled architecture, the selector will look for a plugin component called org.objectweb.fractal.cecilia.primitive.myLanguage.Visitor in order to delegate the visit operation.
The architecture description of the visitor plugin for myLanguage is as follows:
<definition name="org.objectweb.fractal.cecilia.primitive.myLanguage.Visitor" extends="org.objectweb.fractal.cecilia.primitive.thinkMC.Visitor"> <component name="dispatcher"> <attributes> <attribute name="taskCompositionFileName" value="org.objectweb.fractal.cecilia.primitive.myLanguage.Visitor" /> </attributes> </component> <component name="simple-visitor" definition="org.objectweb.fractal.adl.ComponentVisitorType"> <content class="org.objectweb.fractal.cecilia.primitive.myLanguage.SimpleVisitor" /> </component> <!-- =================================================== --> <!-- Dispatcher Bindings --> <!-- =================================================== --> <binding client="dispatcher.client-visitor-10" server="simple-visitor.visitor" /> <!-- =================================================== --> <!-- Other Bindings --> <!-- =================================================== --> <binding client="simple-visitor.task-factory" server="this.task-factory" /> </definition>
As depicted in the above snippet, the plugin extends the visitor plugin for thinkMC and makes only two component declarations.
- The first component declaration, depicted above, overrides the dispatcher component that was declared in the thinkMC visitor in order to declare its own task graph composition rules file. This is done by overriding the corresponding attribute of the parent dispatcher component. Note that, since this simple plugin doesn't define any new processing tasks, the task graph composition descriptor that is set to the dispatcher component is the same as the one provided for thinkMC. This overriding is indeed done only for illustrating how to define a new task composition descriptor.
- The second component declaration defines a new visitor component that is bound to the dispatcher. The implementation class of this visitor component is depicted below:
public class SimpleVisitor extends AbstractTaskFactoryUser implements ComponentVisitor { public Component visit(final List<Node> path, final ComponentContainer node, final Map<Object, Object> context) throws ADLException, TaskException { System.out.println("[" + this.getClass().getName() + "::visit()] Hello World"); return null; } }
The SimpleVisitor component implements the ComponentVisitor interface. Its visit() method is programmed for printing a Hello World message when it is invoked by the dispatcher.
As an exercise to continue with the processing module extensions, we propose to developers to modify this simple visitor for defining a new macro that can be used in the implementation files written in myLanguage. For that purpose, the reading of the Cecilia ADL Toolchain Architecture, the Task Framework Specification and the Task Framework Tutorial may be useful. In addition, the MacroDefinitionVisitor can be an important source of inspiration for such an extension.
Testing the extension
A unit test environment is provided to start testing the extension that is implemented within this tutorial. This environment is based on the JUnit framework. Please refer to the above Getting Started section to get how to launch these tests.
The test class depicted below is executed when the test suite is launched. This class inherits from the CeciliaAdlTestCase class to get access to the helper methods such as compileADL(), compileAndRun(), etc.
public class SimpleTest extends CeciliaAdlTestCase { public void testComponent() throws Exception { compileADL("test.extension.SimpleComponent"); } }
According to the JUnit framework, each method whose name is prefixed by test specifies a test to be executed. The single test that is provided, namely testComponent() specifies that the architecture definition called test.extension.SimpleComponent should be compiled. If this compilation doesn't throw any exception, it is considered to have succeeded.
As depicted in the below snippet, the architecture definition called test.extension.SimpleComponent specifies an architecture where the component called test is implemented in myLanguage. The presence of such a component in the architecture triggers the plugins presented above, therefore allowing testing our extension.
<definition name="test.extension.SimpleComponent" extends="test.AbstractTest"> <component name="test"> <content class="test.extension.SimpleComponent" language="myLanguage"/> </component> </definition>
Note that, new tests can be added in this test suite by adding new methods in the SimpleTest class, respecting the naming conventions of the JUnit framework.
How to proceed for other types of extensions?
This tutorial only focused on how to extend the Cecilia ADL toolchain for supporting a new programming model. Since the toolchain is designed to support such extensions, by mean of specifically identified extension points, it was sufficient to provide plugin components respecting some naming conventions. Those extension points have been identified and designed for toolchain adaptation serving the same class of objective than the current one, namely code generation. This might be insufficient for advanced developers pursuing different objectives than code generation, requiring deeper adaptation of the chain.
A more advanced way to modify the Cecilia ADL toolchain is possible, exploiting the component based design of the tool and its related modularity: modify directly the architecture description (ADL files) to explicitly add the extension components at strategic points of the architecture. This way, new plugin selectors can be inserted as well for easing further extensions.
For further support, please send an email to the Fractal Mailing List.