diff --git a/build.gradle b/build.gradle
index 9e0bde27ffc2908022aa1a0148b6dc8acec2be35..a6a7882eeee6db3cc34887d5b8cce3fe267905d5 100644
--- a/build.gradle
+++ b/build.gradle
@@ -17,10 +17,9 @@ buildscript {
 }
 
 dependencies {
-    implementation 'com.fasterxml.jackson.core:jackson-core:2.9.8'
-    implementation 'com.fasterxml.jackson.core:jackson-databind:2.9.8'
-//    api 'org.jastadd:jastadd:2.3.4'
-    runtime 'org.jastadd:jastadd:2.3.4'
+    implementation group: 'com.github.spullara.mustache.java', name: 'compiler', version: '0.9.6'
+    implementation group: 'org.apache.logging.log4j', name: 'log4j-jul', version: '2.11.2'
+    runtime group: 'org.jastadd', name: 'jastadd', version: '2.3.4'
     api group: 'net.sf.beaver', name: 'beaver-rt', version: '0.9.11'
 }
 
@@ -62,6 +61,7 @@ task relast(type: JavaExec) {
             "../libs/relast.jar",
             "../relast.preprocessor/src/main/jastadd/RelAst.relast",
             "./src/main/jastadd/Ros2Rag.relast",
+            "./src/main/jastadd/MustacheNodes.relast",
             "--listClass=java.util.ArrayList",
             "--jastAddList=JastAddList",
             "--useJastAddNames",
@@ -72,11 +72,12 @@ task relast(type: JavaExec) {
 
     inputs.files file("../libs/relast.jar"),
             file("../relast.preprocessor/src/main/jastadd/RelAST.relast"),
-            file("src/main/jastadd/Ros2Rag.relast")
+            file("./src/main/jastadd/Ros2Rag.relast")
+            file("./src/main/jastadd/MustacheNodes.relast")
     outputs.files file("./src/gen/jastadd/Ros2Rag.ast"),
-            file("src/gen/jastadd/Ros2Rag.jadd"),
-            file("src/gen/jastadd/Ros2RagRefResolver.jadd"),
-            file('src/gen/jastadd/Ros2RagResolverStubs.jrag')
+            file("./src/gen/jastadd/Ros2Rag.jadd"),
+            file("./src/gen/jastadd/Ros2RagRefResolver.jadd"),
+            file('./src/gen/jastadd/Ros2RagResolverStubs.jrag')
 }
 
 jastadd {
diff --git a/src/main/jastadd/MustacheNodes.relast b/src/main/jastadd/MustacheNodes.relast
new file mode 100644
index 0000000000000000000000000000000000000000..69d4280f9d9038c838e83767c6e72be9a8d6d5d8
--- /dev/null
+++ b/src/main/jastadd/MustacheNodes.relast
@@ -0,0 +1,22 @@
+//TypeComponentMustache ;
+//rel TypeComponentMustache.TypeComponent -> TypeComponent ;
+
+MRos2Rag ::= ReadDefinition:MReadDefinition* WriteDefinition:MWriteDefinition* MappingDefinition:MMappingDefinition* DependencyDefinition:MDependencyDefinition* RootTypeComponent:MTypeComponent* TokenComponent:MTokenComponent*;
+abstract MUpdateDefinition ::= InnerMappingDefinition:MInnerMappingDefinition*;
+MReadDefinition : MUpdateDefinition;
+MWriteDefinition : MUpdateDefinition;
+MMappingDefinition;
+MInnerMappingDefinition;
+MDependencyDefinition;
+MTypeComponent;
+MTokenComponent;
+
+rel MRos2Rag.Ros2Rag -> Ros2Rag;
+rel MInnerMappingDefinition.MappingDefinition -> MappingDefinition;
+rel MReadDefinition.ReadFromMqttDefinition -> ReadFromMqttDefinition;
+rel MWriteDefinition.WriteToMqttDefinition -> WriteToMqttDefinition;
+rel MMappingDefinition.MappingDefinition -> MappingDefinition;
+rel MDependencyDefinition.DependencyDefinition -> DependencyDefinition;
+rel MTypeComponent.TypeComponent -> TypeComponent;
+rel MTokenComponent.TokenComponent -> TokenComponent;
+rel MTokenComponent.DependencyDefinition* -> MDependencyDefinition;
diff --git a/src/main/jastadd/Navigation.jrag b/src/main/jastadd/Navigation.jrag
index f56d99825c376248ce8f9a2dae1388ed3898ea64..a4f21e6e8d79c549a63d54474792382457bf74fd 100644
--- a/src/main/jastadd/Navigation.jrag
+++ b/src/main/jastadd/Navigation.jrag
@@ -2,10 +2,12 @@ aspect Navigation {
 
   // --- program ---
   eq Ros2Rag.getChild().program() = getProgram();
+  eq MRos2Rag.getChild().program() = getRos2Rag().program();
 
-  // --- ros2rag
+  // --- ros2rag ---
   inh Ros2Rag ASTNode.ros2rag();
   eq Ros2Rag.getChild().ros2rag() = this;
+  eq MRos2Rag.getChild().ros2rag() = getRos2Rag();
 
   // --- containedFile (first equation should be in preprocessor) ---
   eq Program.getChild().containedFile() = null;
@@ -32,6 +34,7 @@ aspect Navigation {
   eq GrammarFile.getChild().containedFileName() = getFileName();
   eq Ros2Rag.getChild().containedFileName() = getFileName();
   eq Program.getChild().containedFileName() = null;
+  eq MRos2Rag.getChild().containedFileName() = null;
 
   // --- isTokenUpdateDefinition ---
   syn boolean UpdateDefinition.isTokenUpdateDefinition() = false;
@@ -49,6 +52,10 @@ aspect Navigation {
   syn WriteToMqttDefinition UpdateDefinition.asWriteToMqttDefinition() = null;
   eq WriteToMqttDefinition.asWriteToMqttDefinition() = this;
 
+  // --- asReadFromMqttDefinition ---
+  syn ReadFromMqttDefinition UpdateDefinition.asReadFromMqttDefinition() = null;
+  eq ReadFromMqttDefinition.asReadFromMqttDefinition() = this;
+
   // --- targetUpdateDefinition ---
   syn WriteToMqttDefinition DependencyDefinition.targetUpdateDefinition() {
     // resolve definition in here, as we do not need resolveMethod in any other place (yet)
diff --git a/src/main/jastadd/Ros2Rag.relast b/src/main/jastadd/Ros2Rag.relast
index a5499702f44facd5b649a811a958b890d4565d0a..e1a84e8beac44fcbc23d09fc56ed536586bde4e8 100644
--- a/src/main/jastadd/Ros2Rag.relast
+++ b/src/main/jastadd/Ros2Rag.relast
@@ -10,7 +10,6 @@ rel TokenUpdateDefinition.Token -> TokenComponent;
 ReadFromMqttDefinition : TokenUpdateDefinition;
 WriteToMqttDefinition  : TokenUpdateDefinition;
 
-// example: RobotArm._AppropriateSpeed canDependOn Joint.CurrentPosition as dependency1
 DependencyDefinition ::= <ID>;
 rel DependencyDefinition.Source <-> TokenComponent.DependencySourceDefinition*;
 rel DependencyDefinition.Target -> TokenComponent;
diff --git a/src/main/jastadd/backend/Configuration.jadd b/src/main/jastadd/backend/Configuration.jadd
index 711ba2511d1e0c8535007cacccac99872128e58f..d4f5b43f27cb329f1ff82318c6f383f0f69ebbd6 100644
--- a/src/main/jastadd/backend/Configuration.jadd
+++ b/src/main/jastadd/backend/Configuration.jadd
@@ -1,4 +1,5 @@
 aspect Configuration {
   public static boolean ASTNode.loggingEnabledForReads = false;
   public static boolean ASTNode.loggingEnabledForWrites = false;
+  public static TypeDecl ASTNode.rootNode;
 }
diff --git a/src/main/jastadd/backend/Generation.jadd b/src/main/jastadd/backend/Generation.jadd
index 1b6dfff3386d079e75163c4725681a87904f6786..7aa915a421b4034ffa7f20406b22a041a0868bb9 100644
--- a/src/main/jastadd/backend/Generation.jadd
+++ b/src/main/jastadd/backend/Generation.jadd
@@ -21,11 +21,188 @@ aspect GenerationUtils {
   }
 }
 
+/* Open questions
+- Should all string constants be defined on the normal AST, or on the special mustache AST?
+*/
+
+aspect AttributesForMustache {
+  // --- MRos2Rag ---
+  eq MRos2Rag.getChild().mqttUpdaterAttribute() = mqttUpdaterAttribute();
+  eq MRos2Rag.getChild().mqttUpdaterField() = mqttUpdaterField();
+
+  syn String MRos2Rag.mqttUpdaterAttribute() = getRos2Rag().mqttUpdaterAttribute();
+  syn String MRos2Rag.mqttUpdaterField() = getRos2Rag().mqttUpdaterField();
+  syn String MRos2Rag.mqttSetHostMethod() = getRos2Rag().mqttSetHostMethod();
+  syn String MRos2Rag.mqttWaitUntilReadyMethod() = getRos2Rag().mqttWaitUntilReadyMethod();
+  syn String MRos2Rag.mqttCloseMethod() = getRos2Rag().mqttCloseMethod();
+
+  // --- MUpdateDefinition ---
+  syn String MUpdateDefinition.preemptiveExpectedValue();
+  syn String MUpdateDefinition.preemptiveReturn();
+  syn TokenUpdateDefinition MUpdateDefinition.updateDef();
+  syn String MUpdateDefinition.firstInputVarName();
+
+  eq MUpdateDefinition.getInnerMappingDefinition(int i).isLast() = i == getNumInnerMappingDefinition() - 1;
+  eq MUpdateDefinition.getInnerMappingDefinition().resultVarPrefix() = resultVarPrefix();
+  eq MUpdateDefinition.getInnerMappingDefinition(int i).inputVarName() = i == 0 ? firstInputVarName() : resultVarPrefix() + getInnerMappingDefinition(i - 1).getMappingDefinition().methodName();
+
+  inh String MUpdateDefinition.mqttUpdaterAttribute();
+
+  syn String MUpdateDefinition.connectMethod() = updateDef().connectMethod();
+  syn TokenComponent MUpdateDefinition.token() = updateDef().getToken();
+  syn boolean MUpdateDefinition.alwaysApply() = updateDef().getAlwaysApply();
+  syn String MUpdateDefinition.resultVarPrefix() = "result";  // we do not need "_" here, because methodName begins with one
+  syn String MUpdateDefinition.parentTypeName() = token().containingTypeDecl().getName();
+  syn String MUpdateDefinition.tokenName() = token().getName();
+  syn MInnerMappingDefinition MUpdateDefinition.lastDefinition() = getInnerMappingDefinition(getNumInnerMappingDefinition() - 1);
+  syn String MUpdateDefinition.lastDefinitionToType() = lastDefinition().ToType();
+  syn String MUpdateDefinition.lastDefinitionName() = lastDefinition().methodName();
+  syn String MUpdateDefinition.lastResult() = resultVarPrefix() + lastDefinitionName();
+  syn String MUpdateDefinition.condition() {
+    if (lastDefinition().getMappingDefinition().getToType().isArray()) {
+      return "java.util.Arrays.equals(" + preemptiveExpectedValue() + ", " + lastResult() + ")";
+    }
+    if (token().isPrimitiveType() && lastDefinition().getMappingDefinition().getToType().isPrimitiveType()) {
+      return preemptiveExpectedValue() + " == " + lastResult();
+    }
+    if (lastDefinition().getMappingDefinition().isDefaultMappingDefinition()) {
+      return preemptiveExpectedValue() + " != null && " + preemptiveExpectedValue() + ".equals(" + lastResult() + ")";
+    }
+    return preemptiveExpectedValue() + " != null ? " + preemptiveExpectedValue() + ".equals(" + lastResult() + ") : " + lastResult() + " == null";
+  }
+
+  // --- MInnerMappingDefinition ---
+  inh boolean MInnerMappingDefinition.isLast();
+  inh String MInnerMappingDefinition.resultVarPrefix();
+  syn String MInnerMappingDefinition.ToType() = getMappingDefinition().getToType().prettyPrint();
+  syn String MInnerMappingDefinition.methodName() = getMappingDefinition().methodName();
+  inh String MInnerMappingDefinition.inputVarName();
+
+  // --- MReadDefinition ---
+  eq MReadDefinition.preemptiveExpectedValue() = "get" + tokenName() + "()";
+  eq MReadDefinition.preemptiveReturn() = "return;";
+  eq MReadDefinition.updateDef() = getReadFromMqttDefinition();
+  eq MReadDefinition.firstInputVarName() = "message";
+
+  // --- MWriteDefinition ---
+  eq MWriteDefinition.preemptiveExpectedValue() = lastValue();
+  eq MWriteDefinition.preemptiveReturn() = "return false;";
+  eq MWriteDefinition.updateDef() = getWriteToMqttDefinition();
+  eq MWriteDefinition.firstInputVarName() = "get" + tokenName() + "()";
+
+  syn String MWriteDefinition.writeTopic() = getWriteToMqttDefinition().writeTopic();
+  syn String MWriteDefinition.lastValue() = getWriteToMqttDefinition().lastValue();
+  syn String MWriteDefinition.updateMethod() = getWriteToMqttDefinition().updateMethod();
+  syn String MWriteDefinition.writeMethod() = getWriteToMqttDefinition().writeMethod();
+  syn String MWriteDefinition.tokenResetMethod() = getWriteToMqttDefinition().tokenResetMethod();
+
+  // --- MMappingDefinition ---
+  syn String MMappingDefinition.toType() = getMappingDefinition().getToType().prettyPrint();
+  syn String MMappingDefinition.methodName() = getMappingDefinition().methodName();
+  syn String MMappingDefinition.fromType() = getMappingDefinition().getFromType().prettyPrint();
+  syn String MMappingDefinition.fromVariableName() = getMappingDefinition().getFromVariableName();
+  syn String MMappingDefinition.content() = getMappingDefinition().getContent();
+
+  // --- MDependencyDefinition ---
+  syn String MDependencyDefinition.targetParentTypeName() = getDependencyDefinition().getTarget().containingTypeDecl().getName();
+  syn String MDependencyDefinition.dependencyMethod() = getDependencyDefinition().dependencyMethod();
+  syn String MDependencyDefinition.sourceParentTypeName() = getDependencyDefinition().getSource().containingTypeDecl().getName();
+  syn String MDependencyDefinition.internalRelationPrefix() = getDependencyDefinition().internalRelationPrefix();
+  syn nta MUpdateDefinition MDependencyDefinition.targetUpdateDefinition() {
+    return getDependencyDefinition().targetUpdateDefinition().toMustache();
+  }
+
+  // --- MTypeComponent ---
+  syn String MTypeComponent.name() = getTypeComponent().getName();
+  inh String MTypeComponent.mqttUpdaterAttribute();
+  inh String MTypeComponent.mqttUpdaterField();
+
+  // --- MTokenComponent ---
+  syn String MTokenComponent.parentTypeName() = getTokenComponent().containingTypeDecl().getName();
+  syn String MTokenComponent.name() = getTokenComponent().getName();
+  syn String MTokenComponent.javaType() = getTokenComponent().getJavaTypeUse().prettyPrint();
+  syn String MTokenComponent.internalName() = getTokenComponent().internalName();
+
+  // --- toMustache ---
+  syn lazy MRos2Rag Ros2Rag.toMustache() {
+    MRos2Rag result = new MRos2Rag();
+    result.setRos2Rag(this);
+    for (UpdateDefinition def : getUpdateDefinitionList()) {
+      if (def.isWriteToMqttDefinition()) {
+        result.addWriteDefinition(def.asWriteToMqttDefinition().toMustache());
+      } else {
+        result.addReadDefinition(def.asReadFromMqttDefinition().toMustache());
+      }
+    }
+    for (MappingDefinition def : allMappingDefinitions()) {
+      result.addMappingDefinition(def.toMustache());
+    }
+    for (DependencyDefinition def : getDependencyDefinitionList()) {
+      result.addDependencyDefinition(def.toMustache());
+    }
+    for (TokenComponent token : getProgram().allTokenComponents()) {
+      if (!token.getDependencySourceDefinitionList().isEmpty()) {
+        result.addTokenComponent(token.toMustache());
+      }
+    }
+    for (Component child : rootNode.getComponentList()) {
+      if (child.isTypeComponent()) {
+        result.addRootTypeComponent(child.asTypeComponent().toMustache());
+      }
+    }
+    return result;
+  }
+
+//MInnerMappingDefinition.MappingDefinition -> MappingDefinition;
+  protected void MUpdateDefinition.addInnerMappings() {
+    for (MappingDefinition def : updateDef().effectiveMappings()) {
+      MInnerMappingDefinition inner = new MInnerMappingDefinition();
+      inner.setMappingDefinition(def);
+      addInnerMappingDefinition(inner);
+    }
+  }
+  syn lazy MReadDefinition ReadFromMqttDefinition.toMustache() {
+    MReadDefinition result = new MReadDefinition();
+    result.setReadFromMqttDefinition(this);
+    result.addInnerMappings();
+    return result;
+  }
+  syn lazy MWriteDefinition WriteToMqttDefinition.toMustache() {
+    MWriteDefinition result = new MWriteDefinition();
+    result.setWriteToMqttDefinition(this);
+    result.addInnerMappings();
+    return result;
+  }
+  syn lazy MMappingDefinition MappingDefinition.toMustache() {
+    MMappingDefinition result = new MMappingDefinition();
+    result.setMappingDefinition(this);
+    return result;
+  }
+  syn lazy MDependencyDefinition DependencyDefinition.toMustache() {
+    MDependencyDefinition result = new MDependencyDefinition();
+    result.setDependencyDefinition(this);
+    return result;
+  }
+  syn lazy MTypeComponent TypeComponent.toMustache() {
+    MTypeComponent result = new MTypeComponent();
+    result.setTypeComponent(this);
+    return result;
+  }
+  syn lazy MTokenComponent TokenComponent.toMustache() {
+    MTokenComponent result = new MTokenComponent();
+    result.setTokenComponent(this);
+    for (DependencyDefinition def : getDependencySourceDefinitionList()) {
+      result.addDependencyDefinition(def.toMustache());
+    }
+    return result;
+  }
+}
+
 aspect AspectGeneration {
+  // naming convention attributes
   syn String TokenComponent.internalName() = getDependencySourceDefinitionList().isEmpty() ? externalName() : "_internal_" + getName();
   syn String TokenComponent.externalName() = getName();
 
-  // naming convention attributes
   syn String TokenUpdateDefinition.connectMethod() = "connect" + getToken().getName();
   syn String WriteToMqttDefinition.writeTopic() = "_topic_" + getToken().getName();
   syn String WriteToMqttDefinition.lastValue() = "_lastValue" + getToken().getName();
@@ -39,10 +216,6 @@ aspect AspectGeneration {
   syn String DependencyDefinition.internalRelationPrefix() = "_internal_" + getID();
   syn String DependencyDefinition.internalTokenName() = getSource().internalName();
 
-  inh String UpdateDefinition.mqttUpdaterAttribute();
-  inh String MappingDefinition.mqttUpdaterAttribute();
-  inh String DependencyDefinition.mqttUpdaterAttribute();
-  eq Ros2Rag.getChild().mqttUpdaterAttribute() = mqttUpdaterAttribute();
   syn String Ros2Rag.mqttUpdaterAttribute() = "_mqttUpdater";
   syn String Ros2Rag.mqttUpdaterField() = "_mqttUpdater";
 
@@ -50,247 +223,43 @@ aspect AspectGeneration {
   syn String Ros2Rag.mqttWaitUntilReadyMethod() = "MqttWaitUntilReady";
   syn String Ros2Rag.mqttCloseMethod() = "MqttCloseConnections";
 
-  public String Ros2Rag.generateAspect(String rootNodeName) {
-    StringBuilder sb = new StringBuilder();
-    TypeDecl rootNode = getProgram().resolveTypeDecl(rootNodeName);
-    generateMqttAspect(sb, rootNode);
-    generateGrammarExtension(sb);
-    return sb.toString();
-  }
-
-  public void Ros2Rag.generateMqttAspect(StringBuilder sb, TypeDecl rootNode) {
-    String rootNodeName = rootNode.getName();
-    sb.append("aspect MQTT {\n");
-    sb.append(ind(1)).append("private MqttUpdater ").append(rootNodeName)
-      .append(".").append(mqttUpdaterField()).append(" = new MqttUpdater();\n");
-
-    // mqttSetHost(String host)
-    sb.append(ind(1)).append("public void ").append(rootNodeName).append(".")
-      .append(mqttSetHostMethod()).append("(String host) throws java.io.IOException {\n");
-    sb.append(ind(2)).append(mqttUpdaterField()).append(".setHost(host);\n");
-    sb.append(ind(1)).append("}\n");
-
-    // mqttSetHost(String host, int port)
-    sb.append(ind(1)).append("public void ").append(rootNodeName).append(".")
-      .append(mqttSetHostMethod()).append("(String host, int port) throws java.io.IOException {\n");
-    sb.append(ind(2)).append(mqttUpdaterField()).append(".setHost(host, port);\n");
-    sb.append(ind(1)).append("}\n\n");
-
-    // mqttWaitUntilReady
-    sb.append(ind(1)).append("public boolean ").append(rootNodeName).append(".")
-      .append(mqttWaitUntilReadyMethod()).append("(long time, java.util.concurrent.TimeUnit unit) {\n");
-    sb.append(ind(2)).append("return ").append(mqttUpdaterField()).append(".waitUntilReady(time, unit);\n");
-    sb.append(ind(1)).append("}\n\n");
-
-    // mqttClose
-    sb.append(ind(1)).append("public void ").append(rootNodeName).append(".")
-      .append(mqttCloseMethod()).append("() {\n");
-    sb.append(ind(2)).append(mqttUpdaterField()).append(".close();\n");
-    sb.append(ind(1)).append("}\n\n");
-
-    // mqttUpdater
-    sb.append(ind(1)).append("inh MqttUpdater ASTNode.").append(mqttUpdaterAttribute()).append("();\n");
-    for (Component child : rootNode.getComponentList()) {
-      if (child.isTypeComponent()) {
-        sb.append(ind(1)).append("eq ").append(rootNodeName)
-          .append(".get").append(child.getName()).append("().")
-          .append(mqttUpdaterAttribute()).append("() = ").append(mqttUpdaterField()).append(";\n");
-      }
-    }
-    sb.append("}\n\n");
-  }
-
-  public void Ros2Rag.generateGrammarExtension(StringBuilder sb) {
-    sb.append("aspect ROS2RAG {\n");
-
-    for (UpdateDefinition def : getUpdateDefinitionList()) {
-      def.generateAspect(sb);
-    }
-    for (MappingDefinition def : allMappingDefinitions()) {
-      def.generateAspect(sb);
-    }
-    for (DependencyDefinition def : getDependencyDefinitionList()) {
-      def.generateAspect(sb);
-    }
-    for (TokenComponent token : getProgram().allTokenComponents()) {
-      token.generateAspect(sb);
-    }
-
-    sb.append("}\n");
-  }
-
-  abstract void UpdateDefinition.generateAspect(StringBuilder sb);
-
-  String TokenUpdateDefinition.generateMappingApplication(StringBuilder sb, int indent,
-      String initialInputVariableName) {
-    final String resultVariablePrefix = "result";  // we do not need "_" here, because methodName begins with one
-    String inputVariableName = initialInputVariableName;
-    // last variable need to be declared before begin of try
-    MappingDefinition lastDefinition = effectiveMappings().get(effectiveMappings().size() - 1);
-    sb.append(ind(indent)).append(lastDefinition.getToType().prettyPrint()).append(" ")
-      .append(resultVariablePrefix).append(lastDefinition.methodName()).append(";\n");
-    sb.append(ind(indent)).append("try {\n");
-    for (MappingDefinition mappingDefinition : effectiveMappings()) {
-      String resultVariableName = resultVariablePrefix + mappingDefinition.methodName();
-      sb.append(ind(indent + 1));
-      if (mappingDefinition != lastDefinition) {
-        sb.append(mappingDefinition.getToType().prettyPrint()).append(" ");
-      }
-      sb.append(resultVariablePrefix).append(mappingDefinition.methodName())
-        .append(" = ").append(mappingDefinition.methodName()).append("(")
-        .append(inputVariableName).append(");\n");
-      inputVariableName = resultVariableName;
-    }
-    sb.append(ind(indent)).append("} catch (Exception e) {\n");
-    sb.append(ind(indent + 1)).append("e.printStackTrace();\n");
-    sb.append(ind(indent + 1)).append(preemptiveReturnStatement()).append("\n");
-    sb.append(ind(indent)).append("}\n");
-    if (!getAlwaysApply()) {
-      MappingDefinition lastMapping = effectiveMappings().get(effectiveMappings().size() - 1);
-      sb.append(ind(indent)).append("if (");
-      if (lastMapping.getToType().isArray()) {
-        sb.append("java.util.Arrays.equals(").append(preemptiveExpectedValue())
-          .append(", ").append(inputVariableName).append(")");
-      } else {
-        sb.append(preemptiveExpectedValue());
-        if (getToken().isPrimitiveType() && lastMapping.getToType().isPrimitiveType()) {
-          sb.append(" == ").append(inputVariableName);
-        } else if (lastMapping.isDefaultMappingDefinition()) {
-          sb.append(" != null && ").append(preemptiveExpectedValue()).append(".equals(")
-            .append(inputVariableName).append(")");
-        } else {
-          sb.append(" != null ? ").append(preemptiveExpectedValue()).append(".equals(")
-            .append(inputVariableName).append(")").append(" : ")
-            .append(inputVariableName).append(" == null");
-        }
-      }
-      sb.append(") { ").append(preemptiveReturnStatement()).append(" }\n");
-    }
-    return inputVariableName;
-  }
-
-  syn String TokenUpdateDefinition.preemptiveExpectedValue();
-  eq ReadFromMqttDefinition.preemptiveExpectedValue() = "get" + getToken().getName() + "()";
-  eq WriteToMqttDefinition.preemptiveExpectedValue() = lastValue();
-
-  syn String TokenUpdateDefinition.preemptiveReturnStatement();
-  eq ReadFromMqttDefinition.preemptiveReturnStatement() = "return;";
-  eq WriteToMqttDefinition.preemptiveReturnStatement() = "return false;";
-
-  @Override
-  void ReadFromMqttDefinition.generateAspect(StringBuilder sb) {
-    sb.append(ind(1)).append("public void ").append(getToken().containingTypeDecl().getName()).append(".")
-      .append(connectMethod()).append("(String topic) {\n");
-    sb.append(ind(2)).append(mqttUpdaterAttribute()).append("().newConnection(topic, message -> {\n");
-    String lastResult = generateMappingApplication(sb, 3, "message");
-    if (loggingEnabledForReads) {
-      sb.append(ind(3)).append("System.out.println(\"[Read] \" + topic + \" -> ")
-        .append(getToken().getName()).append(" = \" + ").append(lastResult)
-        .append(");\n");
-    }
-    sb.append(ind(3)).append("set").append(getToken().getName()).append("(").append(lastResult).append(");\n");
-    sb.append(ind(2)).append("});\n");
-    sb.append(ind(1)).append("}\n\n");
-  }
-
-  @Override
-  void WriteToMqttDefinition.generateAspect(StringBuilder sb) {
-    String parentTypeName = getToken().containingTypeDecl().getName();
-    // fields
-    sb.append(ind(1)).append("private String ").append(parentTypeName).append(".")
-      .append(writeTopic()).append(" = null;\n");
-    sb.append(ind(1)).append("private byte[] ").append(parentTypeName).append(".")
-      .append(lastValue()).append(" = null;\n");
-
-    // connect method
-    sb.append(ind(1)).append("public void ").append(parentTypeName).append(".")
-      .append(connectMethod()).append("(String topic, boolean writeCurrentValue) {\n");
-    sb.append(ind(2)).append(writeTopic()).append(" = topic;\n");
-    sb.append(ind(2)).append(updateMethod()).append("();\n");
-    sb.append(ind(2)).append("if (writeCurrentValue) {\n");
-    sb.append(ind(3)).append(writeMethod()).append("();\n");
-    sb.append(ind(2)).append("}\n");
-    sb.append(ind(1)).append("}\n\n");
+  // naming copy attributes
+  // --- mqttUpdaterAttribute ---
+  inh String UpdateDefinition.mqttUpdaterAttribute();
+  inh String MappingDefinition.mqttUpdaterAttribute();
+  inh String DependencyDefinition.mqttUpdaterAttribute();
+  eq Ros2Rag.getChild().mqttUpdaterAttribute() = mqttUpdaterAttribute();
 
-    // update method
-    sb.append(ind(1)).append("protected boolean ").append(parentTypeName).append(".")
-      .append(updateMethod()).append("() {\n");
-    sb.append(ind(2)).append(tokenResetMethod()).append("();\n");
-    String lastResult = generateMappingApplication(sb, 2, "get" + getToken().getName() + "()");
-    sb.append(ind(2)).append(lastValue()).append(" = ").append(lastResult).append(";\n");
-    sb.append(ind(2)).append("return true;\n");
-    sb.append(ind(1)).append("}\n\n");
+  // --- rootNodeName ---
+  syn String ASTNode.rootNodeName() = rootNode.getName();
 
-    // write method
-    sb.append(ind(1)).append("protected void ").append(parentTypeName).append(".")
-      .append(writeMethod()).append("() {\n");
-    if (loggingEnabledForWrites) {
-      sb.append(ind(2)).append("System.out.println(\"[Write] ").append(getToken().getName())
-        .append(" = \" + ")
-        .append("get").append(getToken().getName()).append("() + \" -> \" + ")
-        .append(writeTopic()).append(");\n");
-    }
-    // _mqttUpdater().publish(${writeTopic()}, ${lastValue()});
-    sb.append(ind(2)).append(mqttUpdaterAttribute()).append("().publish(")
-      .append(writeTopic()).append(", ").append(lastValue()).append(");\n");
-    sb.append(ind(1)).append("}\n\n");
+  public String Ros2Rag.generateAspect(String rootNodeName) {
+    rootNode = getProgram().resolveTypeDecl(rootNodeName);
+    return toMustache().generateAspect();
   }
 
-  void MappingDefinition.generateAspect(StringBuilder sb) {
-    sb.append(ind(1)).append("protected static ").append(getToType().prettyPrint())
-      .append(" ASTNode.").append(methodName()).append("(")
-      .append(getFromType().prettyPrint()).append(" ").append(getFromVariableName())
-      .append(") throws Exception {\n");
-    for (String line : getContent().split("\n")) {
-      if (!line.trim().isEmpty()) {
-        sb.append(ind(2)).append(line).append("\n");
+  public String MRos2Rag.generateAspect() {
+    StringBuilder sb = new StringBuilder();
+    com.github.mustachejava.reflect.ReflectionObjectHandler roh = new com.github.mustachejava.reflect.ReflectionObjectHandler() {
+      @Override
+      public com.github.mustachejava.Binding createBinding(String name, final com.github.mustachejava.TemplateContext tc, com.github.mustachejava.Code code) {
+        return new com.github.mustachejava.reflect.GuardedBinding(this, name, tc, code) {
+          @Override
+          protected synchronized com.github.mustachejava.util.Wrapper getWrapper(String name, java.util.List<Object> scopes) {
+            com.github.mustachejava.util.Wrapper wrapper = super.getWrapper(name, scopes);
+            if (wrapper instanceof com.github.mustachejava.reflect.MissingWrapper) {
+              throw new com.github.mustachejava.MustacheException(name + " not found in " + tc);
+            }
+            return wrapper;
+          }
+        };
       }
-    }
-    sb.append(ind(1)).append("}\n\n");
-  }
-
-  void DependencyDefinition.generateAspect(StringBuilder sb) {
-    String targetParentTypeName = getTarget().containingTypeDecl().getName();
-    String sourceParentTypeName = getSource().containingTypeDecl().getName();
-
-    // dependency method
-    sb.append(ind(1)).append("public void ").append(targetParentTypeName).append(".")
-      .append(dependencyMethod()).append("(").append(sourceParentTypeName).append(" source) {\n");
-    sb.append(ind(2)).append("add").append(internalRelationPrefix()).append("Source(source);\n");
-    sb.append(ind(1)).append("}\n\n");
-  }
-
-  void TokenComponent.generateAspect(StringBuilder sb) {
-    if (getDependencySourceDefinitionList().isEmpty()) { return; }
-
-    String parentTypeName = containingTypeDecl().getName();
-    // virtual setter
-    sb.append(ind(1)).append("public ").append(parentTypeName).append(" ")
-      .append(parentTypeName).append(".set").append(getName()).append("(")
-      .append(getJavaTypeUse().prettyPrint()).append(" value) {\n");
-    sb.append(ind(2)).append("set").append(internalName()).append("(value);\n");
-
-    for (DependencyDefinition dependencyDefinition : getDependencySourceDefinitionList()) {
-      String targetParentTypeName = dependencyDefinition.getTarget().containingTypeDecl().getName();
-      sb.append(ind(2)).append("for (").append(targetParentTypeName).append(" target : get")
-        .append(dependencyDefinition.internalRelationPrefix()).append("TargetList()) {\n");
-      sb.append(ind(3)).append("if (target.")
-        .append(dependencyDefinition.targetUpdateDefinition().updateMethod())
-        .append("()) {\n");
-      sb.append(ind(4)).append("target.")
-        .append(dependencyDefinition.targetUpdateDefinition().writeMethod())
-        .append("();\n");
-      sb.append(ind(3)).append("}\n");
-      sb.append(ind(2)).append("}\n");
-    }
-    sb.append(ind(2)).append("return this;\n");
-    sb.append(ind(1)).append("}\n\n");
-
-    // virtual getter
-    sb.append(ind(1)).append("public ").append(getJavaTypeUse().prettyPrint())
-      .append(" ").append(parentTypeName).append(".get").append(getName()).append("() {\n");
-    sb.append(ind(2)).append("return get").append(internalName()).append("();\n");
-    sb.append(ind(1)).append("}\n\n");
+    };
+    com.github.mustachejava.DefaultMustacheFactory mf = new com.github.mustachejava.DefaultMustacheFactory();
+    mf.setObjectHandler(roh);
+    com.github.mustachejava.Mustache m = mf.compile("ros2rag.mustache");
+    m.execute(new java.io.PrintWriter(new org.jastadd.ros2rag.compiler.AppendableWriter(sb)), this);
+    return sb.toString();
   }
 }
 
diff --git a/src/main/java/org/jastadd/ros2rag/compiler/AppendableWriter.java b/src/main/java/org/jastadd/ros2rag/compiler/AppendableWriter.java
new file mode 100644
index 0000000000000000000000000000000000000000..97680b18dc4fd1d8af3df75f35221e059355e19e
--- /dev/null
+++ b/src/main/java/org/jastadd/ros2rag/compiler/AppendableWriter.java
@@ -0,0 +1,37 @@
+package org.jastadd.ros2rag.compiler;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * Writer appending to a StringBuilder.
+ *
+ * @author rschoene - Initial contribution
+ */
+public class AppendableWriter extends Writer {
+  private final StringBuilder sb;
+
+  public AppendableWriter(StringBuilder sb) {
+    this.sb = sb;
+  }
+
+  @Override
+  public void write(char[] chars, int off, int len) throws IOException {
+    sb.append(chars, off, len);
+  }
+
+  @Override
+  public void write(String str) throws IOException {
+    sb.append(str);
+  }
+
+  @Override
+  public void flush() {
+
+  }
+
+  @Override
+  public void close() {
+
+  }
+}
diff --git a/src/main/java/org/jastadd/ros2rag/compiler/Compiler.java b/src/main/java/org/jastadd/ros2rag/compiler/Compiler.java
index ee575b9495f53eca9925c6828ba96225734fe31a..8428bf27961070afa6410a349b3412e7bff729e6 100644
--- a/src/main/java/org/jastadd/ros2rag/compiler/Compiler.java
+++ b/src/main/java/org/jastadd/ros2rag/compiler/Compiler.java
@@ -37,6 +37,8 @@ public class Compiler {
   }
 
   public void run(String[] args) throws CommandLineException, CompilerException {
+    System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager");
+    System.setProperty("mustache.debug", "true");
     options = new ArrayList<>();
     addOptions();
     commandLine = new CommandLine(options);
diff --git a/src/main/resources/dependencyDefinition.mustache b/src/main/resources/dependencyDefinition.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..b8d74ffe8f8aa5aaa8979682e4442215af1974e8
--- /dev/null
+++ b/src/main/resources/dependencyDefinition.mustache
@@ -0,0 +1,3 @@
+  public void {{targetParentTypeName}}.{{dependencyMethod}}({{sourceParentTypeName}} source) {
+    add{{internalRelationPrefix}}Source(source);
+  }
diff --git a/src/main/resources/mappingApplication.mustache b/src/main/resources/mappingApplication.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..95ce2fe29329d87f880497b37697d50c8c0687be
--- /dev/null
+++ b/src/main/resources/mappingApplication.mustache
@@ -0,0 +1,14 @@
+{{lastDefinitionToType}} {{resultVarPrefix}}{{lastDefinitionName}};
+try {
+  {{#InnerMappingDefinitions}}
+  {{^last}}{{ToType}} {{/last}}{{resultVarPrefix}}{{methodName}} = {{methodName}}({{inputVarName}});{{!inputVarName has to be computed beforehand}}
+  {{/InnerMappingDefinitions}}
+} catch (Exception e) {
+  e.printStackTrace();
+  {{preemptiveReturn}}
+}
+{{^alwaysApply}}
+if ({{{condition}}}) {
+  {{preemptiveReturn}}
+}
+{{/alwaysApply}}
diff --git a/src/main/resources/mappingDefinition.mustache b/src/main/resources/mappingDefinition.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..920c5a610b0ce0cff1687122c97b6e18ce63bf7f
--- /dev/null
+++ b/src/main/resources/mappingDefinition.mustache
@@ -0,0 +1,3 @@
+  protected static {{toType}} ASTNode.{{methodName}}({{fromType}} {{fromVariableName}}) throws Exception {
+    {{{content}}}{{!maybe print line by line to get better indentation}}
+  }
diff --git a/src/main/resources/mqtt.mustache b/src/main/resources/mqtt.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..2474e16525f6058540c3d73191369f4d2d056c4f
--- /dev/null
+++ b/src/main/resources/mqtt.mustache
@@ -0,0 +1,22 @@
+aspect MQTT {
+  private MqttUpdater {{rootNodeName}}.{{mqttUpdaterField}} = new MqttUpdater();
+  public void {{rootNodeName}}.{{mqttSetHostMethod}}(String host) throws java.io.IOException {
+    {{mqttUpdaterField}}.setHost(host);
+  }
+  public void {{rootNodeName}}.{{mqttSetHostMethod}}(String host, int port) throws java.io.IOException {
+    {{mqttUpdaterField}}.setHost(host, port);
+  }
+
+  public boolean {{rootNodeName}}.{{mqttWaitUntilReadyMethod}}(long time, java.util.concurrent.TimeUnit unit) {
+    return {{mqttUpdaterField}}.waitUntilReady(time, unit);
+  }
+
+  public void {{rootNodeName}}.{{mqttCloseMethod}}() {
+    {{mqttUpdaterField}}.close();
+  }
+
+  inh MqttUpdater ASTNode.{{mqttUpdaterAttribute}}();
+  {{#getRootTypeComponents}}
+  eq {{rootNodeName}}.get{{name}}().{{mqttUpdaterAttribute}}() = {{mqttUpdaterField}};
+  {{/getRootTypeComponents}}
+}
diff --git a/src/main/resources/readDefinition.mustache b/src/main/resources/readDefinition.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..f908ef166db8eb781d7b7d46e7bae1719ea5fb1d
--- /dev/null
+++ b/src/main/resources/readDefinition.mustache
@@ -0,0 +1,9 @@
+  public void {{parentTypeName}}.{{connectMethod}}(String topic) {
+    {{mqttUpdaterAttribute}}().newConnection(topic, message -> {
+      {{> mappingApplication}}
+      {{#loggingEnabledForReads}}
+      System.out.println("[Read] " + topic + " -> {{tokenName}} = " + {{lastResult}});{{!lastResult has to be a new attribute}}
+      {{/loggingEnabledForReads}}
+      set{{tokenName}}({{lastResult}});
+    });
+  }
diff --git a/src/main/resources/ros2rag.mustache b/src/main/resources/ros2rag.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..dd5e73cf554e35cc20da014b2d83f0db1b88521b
--- /dev/null
+++ b/src/main/resources/ros2rag.mustache
@@ -0,0 +1,22 @@
+{{> mqtt}}
+aspect ROS2RAG {
+  {{#ReadDefinitions}}
+    {{> readDefinition}}
+  {{/ReadDefinitions}}
+
+  {{#WriteDefinitions}}
+    {{> writeDefinition}}
+  {{/WriteDefinitions}}
+
+  {{#MappingDefinitions}}
+    {{> mappingDefinition}}
+  {{/MappingDefinitions}}
+
+  {{#DependencyDefinitions}}
+    {{> dependencyDefinition}}
+  {{/DependencyDefinitions}}
+
+  {{#TokenComponents}}
+    {{> tokenComponent}}
+  {{/TokenComponents}}
+}
diff --git a/src/main/resources/tokenComponent.mustache b/src/main/resources/tokenComponent.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..a3615c4526e5725bdd49121b16a87a011562f0cd
--- /dev/null
+++ b/src/main/resources/tokenComponent.mustache
@@ -0,0 +1,17 @@
+  public {{parentTypeName}} {{parentTypeName}}.set{{name}}({{javaType}} value) {
+    set{{internalName}}(value);
+    {{#DependencyDefinitions}}
+    for ({{targetParentTypeName}} target : get{{internalRelationPrefix}}TargetList()) {
+      {{#targetUpdateDefinition}}
+      if (target.{{updateMethod}}()) {
+        target.{{writeMethod}}();
+      }
+      {{/targetUpdateDefinition}}
+    }
+    {{/DependencyDefinitions}}
+    return this;
+  }
+
+  public {{javaType}} {{parentTypeName}}.get{{name}}() {
+    return get{{internalName}}();
+  }
diff --git a/src/main/resources/writeDefinition.mustache b/src/main/resources/writeDefinition.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..3d8aab888ed0a39131f5cf00b20cfa2e9ade9048
--- /dev/null
+++ b/src/main/resources/writeDefinition.mustache
@@ -0,0 +1,24 @@
+  private String {{parentTypeName}}.{{writeTopic}} = null;
+  private byte[] {{parentTypeName}}.{{lastValue}} = null;
+
+  public void {{parentTypeName}}.{{connectMethod}}(String topic, boolean writeCurrentValue) {
+    {{writeTopic}} = topic;
+    {{updateMethod}}();
+    if (writeCurrentValue) {
+      {{writeMethod}}();
+    }
+  }
+
+  protected boolean {{parentTypeName}}.{{updateMethod}}() {
+    {{tokenResetMethod}}();
+    {{> mappingApplication}}
+    {{lastValue}} = {{lastResult}};
+    return true;
+  }
+
+  protected void {{parentTypeName}}.{{writeMethod}}() {
+    {{#loggingEnabledForWrites}}
+    System.out.println("[Write] {{tokenName}} = " + get{{tokenName}}() + " -> " + {{writeTopic}});
+    {{/loggingEnabledForWrites}}
+    {{mqttUpdaterAttribute}}().publish({{writeTopic}}, {{lastValue}});
+  }