Skip to main content
Jump to: navigation, search

MWE The Big Picture

Revision as of 05:33, 19 June 2008 by Sven.efftinge.typefox.io (Talk | contribs) (Type inference)

The purpose of MWE is to help orchestrate and configure code generation 'workflows'. Just like with compilers a code generator can and should be splitted into several single parts, each part doing a specific job. Such parts could be:

  • parsing / loading model
  • validating model
  • linking model
  • transforming model
  • modifying model
  • generating code out of a model

Typically there are a couple of component fulfilling such jobs included in a generator (compiler-chain), where each needs additional component-specific configuration. Also there are different solutions for each job. For instance modeltransformation can be done using QVT, ATL or Xtend, each need different configuration.

Configuration language (codename DI-Language -> DI=Dependency Injection)

The description of a generator workflow is made of a sequence of declared concrete components (e.g. EMFResourceLoaderComponent, EValidationComponent, JetComponent, XpandComponent, etc.) where each is configured.

For instance an EMFResourceLoaderComponent would need the URIs to the resources to be loaded and optionally respective ResourceFactory configuration, because workflows are run in standalone mode most of the time. XpandComponent needs to know what kind of TypeAdapter to use, where to generate the different files, how to postprocess them, etc.

Essentially such a description is sequence of components where each component, can be configured with arbitrary complex components itself. Example:

EmfResourceLoaderComponent {
  platformRoot = ".."
  uri = "platform:/resource/foo/bar/model.dsl"
  extensionToFactoryMap += { 
     ext="dsl" 
     factory= MySpecialDSLResourceFactory {
        someAdditionalConfig = "foobar"
     }
  } 
}

Previously this has been configured via XML :

<component class="EmfResourceLoaderComponent"
   platformRoot = ".."
   uri = "platform:/resource/foo/bar/model.dsl">
  <extensionToFactoryMap ext="dsl">
      <factory class="MySpecialDSLResourceFactory" 
               someAdditionalConfig="foo"/>
  </extensionToFactoryMap>
</component>

No matter what syntax being used, the core concepts remain. This part (the language) don't need to know anything about what to instantiate. The only interface to the configuration is Java reflection.

Features of the DI language

Type adapter

It is not only possible to instantiate Java classes via default constructor, but provide arbritrary implementations of TypeSystem. The di-language is shipped with implemenations for EMF (using EMF's refection layer) and for Java (using default constructor and adder and setter methods).

This hook will also be used to improve convenience for repeated tasks. For instance, it is possible to use it like a factory and have preconfigured complex object trees being injected. Another useful case is to provide a built-in TypeSystem for a specific runtime model (see next section) in order to let users write more intentional looking configurations like e.g.:

workflow {
   parallel {
       generator {
          invoke = "foo::bar()"
       }
       
       generator {
          invoke = "foo::bar2()"
       }
   }
} 

Where workflow, parallel and generator are built-in names known by a configured type adapter.

Local Variables

Local Variable can be accessed in two ways

  • via name on the right hand side of an assignment
 foo = localVarName
  • via &{name} within a string literal.
 foo = "foo${bar}"

Another example :

 my.Object {
    name= "${theName}"
    otherProp = aLocalVar
 }
Variables can be passed in as parameters

When instantiating a workflow description, a map of parameters can be passed in. The values can be any kind of Java object.

Variables can be declared

Variable declarations (and usage) look like:

 var x = "foo"
 var y = my.Object { name = "${x}bar" }
Complex Values can be stored as variable

When declaring a complex value one can assign it to a localVar by adding :varname in front of the opening curly bracket.

The following code would inject a reference to the created object in it's property 'self'.

 my.Object :myVar {
   self = myVar
 }

Invocation of other workflows

The root element of another workflow can be injected via:

 my.Object {
    coolStuff = file "scheme:/foo/workflow.mwe" {}
 }

Parameters can be passed like this:

 my.Object {
    coolStuff = file "scheme:/foo/workflow.mwe" {
        generateTo = "${project}/src-gen"
        packageRegistry = myPackageRegistry
    }
 }


Type inference

If the dependency's type is a concrete type and you want to inject an instance of that type, you don't have to eplicitely state the type's name, because it can be inferred from the dependency. Example:

If we have the following two classes

class Foo {
 setBar(Bar) ...
}
class Bar {
 setName(String) ...
}

The configuration

Foo {
 bar = Bar {name = "foo"}
}

can be abbreviated like so:

Foo {
 bar = {name = "foo"}
}

Runtime Workflow Model

For a wokflow in order to being executable, we need some kind of interface to the different components. Such an interface defines a main 'execute' method and optionally additional life cycle callback methods (like preconfigure, postconfigure, preExecute,etc.) In the past we used an interface called WorkflowComponent, having two methods : checkConfiguration(), invoke(). Also there is a workflowcontext (essentially a hashmap) being passed into the invoke method, so the different components can communicate some how. One component puts the model into a slot another reads it from there, transforms it and writes the oiutcome to another slot. And so on...

We also introduced a so called CompositeComponent, which is just a composite node being able to hold a sequence of WorkflowComponent.

The runtime workflow model could provide additional things like

  • A way to execute multiple components in parallel
  • A way to conditionally execute components

Back to the top