diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 2bd5703df929c32cdef66c41e99cb66092c9adca..56c0e1b7d49ac9c19ac72b6e1bbc0d7be7b1191c 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -32,7 +32,7 @@ test:
   image: openjdk:11
   stage: test
   services:
-    - name: "eclipse-mosquitto:1.6.9"
+    - name: "eclipse-mosquitto:1.6"
       alias: "mqtt"
   needs:
     - build
diff --git a/.gitmodules b/.gitmodules
index 0163ef86549d9ee0ba792961799f705881d1f893..58efd108e858ae06dde5f626286bd3d73f9dde4e 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,7 +1,4 @@
 [submodule "relast-preprocessor"]
 	path = relast-preprocessor
 	url = ../relast-preprocessor.git
-  branch = jastadd-fix-inc-param-debug
-[submodule "ragconnect.base/src/main/jastadd/mustache"]
-	path = ragconnect.base/src/main/jastadd/mustache
-	url = ../mustache
+	branch = develop
diff --git a/libs/jastadd2.jar b/libs/jastadd2.jar
new file mode 100644
index 0000000000000000000000000000000000000000..d615b895453d660f0e7397fffad58a05029169fd
Binary files /dev/null and b/libs/jastadd2.jar differ
diff --git a/pages/docs/changelog.md b/pages/docs/changelog.md
new file mode 100644
index 0000000000000000000000000000000000000000..eb0fe4d555048fb90f71cec631baafeaef35a4d7
--- /dev/null
+++ b/pages/docs/changelog.md
@@ -0,0 +1,17 @@
+# Changelog
+
+## 0.3.1
+
+- Full support for incremental dependency tracking
+- Full support for subtree endpoint definitions ([#9](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/9))
+- Internal: Use updated gradle plugin for tests ([#18](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/18))
+- Bugfix [#22](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/22): Correct handling of malformed URIs passed when connecting an endpoint
+- Bugfix [#23](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/23): Correct handling of OptComponents as endpoints
+- Bugfix [#27](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/27): Correctly handle whitespaces in grammars
+
+## 0.3.0
+
+- Added [API documentation](ragdoc/index.html) to documentation
+- Add methods to `disconnect` an endpoint
+- Internal: PoC for incremental dependency tracking and subtree endpoint definitions ([#14](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/14))
+- Bugfix [#17](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/17): Added missing support for `boolean`
diff --git a/pages/mkdocs.yml b/pages/mkdocs.yml
index a2f77dc9ba0b011450d8c6326ac3880998b1b793..6363c73ae7ac43b9c4d04c4eb0036ef36fb5019a 100644
--- a/pages/mkdocs.yml
+++ b/pages/mkdocs.yml
@@ -5,6 +5,7 @@ nav:
   - inner-workings.md
   - using.md
   - extending.md
+  - changelog.md
   - API documentation: ragdoc/index.html
 theme:
   name: readthedocs
diff --git a/ragconnect.base/build.gradle b/ragconnect.base/build.gradle
index 0a96c5864e96fd1746cd8b2f9e1fa66da21f46a7..77bafe579a620ca243e1061785fcf1ddef8a3108 100644
--- a/ragconnect.base/build.gradle
+++ b/ragconnect.base/build.gradle
@@ -27,11 +27,9 @@ repositories {
 
 dependencies {
     implementation project(':relast-preprocessor')
-    implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: "${log4j_version}"
-    implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: "${log4j_version}"
-    implementation group: 'org.apache.logging.log4j', name: 'log4j-jul', version: "${log4j_version}"
     implementation group: 'com.github.spullara.mustache.java', name: 'compiler', version: "${mustache_java_version}"
-    runtimeOnly group: 'org.jastadd', name: 'jastadd', version: '2.3.5'
+//    runtimeOnly group: 'org.jastadd', name: 'jastadd', version: '2.3.5'
+    runtimeOnly fileTree(include: ['jastadd2.jar'], dir: '../libs')
     api group: 'net.sf.beaver', name: 'beaver-rt', version: '0.9.11'
 }
 
@@ -67,14 +65,12 @@ idea.module.generatedSourceDirs += genSrc
 jar {
     manifest {
         attributes "Main-Class": 'org.jastadd.ragconnect.compiler.Compiler'
-
-        // Log4J + Java 11 compatibility, see https://stackoverflow.com/q/53049346/2493208
-        attributes "Multi-Release": true
     }
 
     from {
         configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
     }
+    duplicatesStrategy = DuplicatesStrategy.EXCLUDE
 
     archiveBaseName = 'ragconnect'
 }
@@ -82,7 +78,7 @@ jar {
 File preprocessorGrammar = file('../relast-preprocessor/src/main/jastadd/RelAst.relast')
 File ragConnectGrammar = file('./src/main/jastadd/RagConnect.relast')
 File intermediateGrammar = file('./src/main/jastadd/intermediate/MustacheNodes.relast')
-File mustacheGrammar = file('./src/main/jastadd/mustache/Mustache.relast')
+File mustacheGrammar = file('../relast-preprocessor/src/main/jastadd/mustache/Mustache.relast')
 task relast(type: JavaExec) {
     group = 'Build'
     main = "-jar"
@@ -224,3 +220,4 @@ publishing {
 }
 
 publish.dependsOn jar
+jar.dependsOn ":relast-preprocessor:jar"
diff --git a/ragconnect.base/src/main/jastadd/Analysis.jrag b/ragconnect.base/src/main/jastadd/Analysis.jrag
index f55044dddb1582d41689a138818ad2114d2d6fd7..e8afb8ba2d4bb1485be30b543812b48e291dd4b3 100644
--- a/ragconnect.base/src/main/jastadd/Analysis.jrag
+++ b/ragconnect.base/src/main/jastadd/Analysis.jrag
@@ -1,10 +1,10 @@
 aspect Analysis {
   // --- lookupTokenEndpointDefinition ---
   inh java.util.List<TokenEndpointDefinition> TokenEndpointDefinition.lookupTokenEndpointDefinitions(TokenComponent token);
-  eq RagConnect.getEndpointDefinition().lookupTokenEndpointDefinitions(TokenComponent token) = lookupTokenEndpointDefinitions(token);
+  eq RagConnect.getConnectSpecificationFile().lookupTokenEndpointDefinitions(TokenComponent token) = lookupTokenEndpointDefinitions(token);
   syn java.util.List<TokenEndpointDefinition> RagConnect.lookupTokenEndpointDefinitions(TokenComponent token) {
     java.util.List<TokenEndpointDefinition> result = new java.util.ArrayList<>();
-    for (EndpointDefinition def : getEndpointDefinitionList()) {
+    for (EndpointDefinition def : allEndpointDefinitionList()) {
       if (def.isTokenEndpointDefinition() && def.asTokenEndpointDefinition().getToken().equals(token)) {
         result.add(def.asTokenEndpointDefinition());
       }
@@ -14,10 +14,10 @@ aspect Analysis {
 
   // --- lookupTypeEndpointDefinition ---
   inh java.util.List<TypeEndpointDefinition> TypeEndpointDefinition.lookupTypeEndpointDefinitions(TypeComponent type);
-  eq RagConnect.getEndpointDefinition().lookupTypeEndpointDefinitions(TypeComponent type) = lookupTypeEndpointDefinitions(type);
+  eq RagConnect.getConnectSpecificationFile().lookupTypeEndpointDefinitions(TypeComponent type) = lookupTypeEndpointDefinitions(type);
   syn java.util.List<TypeEndpointDefinition> RagConnect.lookupTypeEndpointDefinitions(TypeComponent type) {
     java.util.List<TypeEndpointDefinition> result = new java.util.ArrayList<>();
-    for (EndpointDefinition def : getEndpointDefinitionList()) {
+    for (EndpointDefinition def : allEndpointDefinitionList()) {
       if (def.isTypeEndpointDefinition() && def.asTypeEndpointDefinition().getType().equals(type)) {
         result.add(def.asTypeEndpointDefinition());
       }
@@ -27,8 +27,8 @@ aspect Analysis {
 
   // --- lookupDependencyDefinition ---
   inh DependencyDefinition DependencyDefinition.lookupDependencyDefinition(TypeDecl source, String id);
-  eq RagConnect.getDependencyDefinition().lookupDependencyDefinition(TypeDecl source, String id) {
-    for (DependencyDefinition def : getDependencyDefinitionList()) {
+  eq RagConnect.getConnectSpecificationFile().lookupDependencyDefinition(TypeDecl source, String id) {
+    for (DependencyDefinition def : allDependencyDefinitionList()) {
       if (def.getID().equals(id) && def.getSource().containingTypeDecl().equals(source)) {
         return def;
       }
diff --git a/ragconnect.base/src/main/jastadd/Configuration.jadd b/ragconnect.base/src/main/jastadd/Configuration.jadd
index 4116807c5b921b74bd645a992c849d841b43bcf2..e7e1268df443a278496a499c59e896d1ee27c80a 100644
--- a/ragconnect.base/src/main/jastadd/Configuration.jadd
+++ b/ragconnect.base/src/main/jastadd/Configuration.jadd
@@ -1,8 +1,10 @@
 aspect Configuration {
   public static boolean ASTNode.loggingEnabledForReads = false;
   public static boolean ASTNode.loggingEnabledForWrites = false;
+  public static boolean ASTNode.loggingEnabledForIncremental = false;
   public static TypeDecl ASTNode.rootNode;
   public static boolean ASTNode.usesMqtt;
   public static boolean ASTNode.usesRest;
   public static boolean ASTNode.incrementalOptionActive;
+  public static boolean ASTNode.experimentalJastAdd329;
 }
diff --git a/ragconnect.base/src/main/jastadd/NameResolution.jrag b/ragconnect.base/src/main/jastadd/NameResolution.jrag
index 6b9171df4e85464b6711aa80e76b34f952d6864b..3b75bc0eb633830cb0e63c80d5d51e7d04e1b4f0 100644
--- a/ragconnect.base/src/main/jastadd/NameResolution.jrag
+++ b/ragconnect.base/src/main/jastadd/NameResolution.jrag
@@ -2,7 +2,7 @@ aspect NameResolution {
 
   refine RefResolverStubs eq EndpointDefinition.resolveMappingByToken(String id, int position) {
     // return a MappingDefinition
-    for (MappingDefinition mappingDefinition : ragconnect().getMappingDefinitionList()) {
+    for (MappingDefinition mappingDefinition : ragconnect().allMappingDefinitionList()) {
       if (mappingDefinition.getID().equals(id)) {
         return mappingDefinition;
       }
diff --git a/ragconnect.base/src/main/jastadd/Navigation.jrag b/ragconnect.base/src/main/jastadd/Navigation.jrag
index 5bddf79ea3c633d7fa5933dde4ab6b665000630d..d3890f7d22a195127e7688435f9168aa64a37eac 100644
--- a/ragconnect.base/src/main/jastadd/Navigation.jrag
+++ b/ragconnect.base/src/main/jastadd/Navigation.jrag
@@ -1,4 +1,7 @@
-aspect Navigation {
+import java.util.List;
+import java.util.ArrayList;
+
+aspect RagConnectNavigation {
 
   // --- program ---
   eq RagConnect.getChild().program() = getProgram();
@@ -9,15 +12,54 @@ aspect Navigation {
   eq RagConnect.getChild().ragconnect() = this;
   eq MRagConnect.getChild().ragconnect() = getRagConnect();
 
+  // --- containedConnectSpecification ---
+  inh ConnectSpecification ASTNode.containedConnectSpecification();
+  eq RagConnect.getChild().containedConnectSpecification() = null;
+  eq MRagConnect.getChild().containedConnectSpecification() = null;
+  eq Document.getChild().containedConnectSpecification() = null;
+  eq Program.getChild().containedConnectSpecification() = null;
+  eq ConnectSpecification.getChild().containedConnectSpecification() = this;
+
   // --- containedFile
-  eq Grammar.getChild().containedFile() = null;
   eq RagConnect.getChild().containedFile() = null;
   eq MRagConnect.getChild().containedFile() = null;
 
   // --- containedFileName ---
-  eq Grammar.getChild().containedFileName() = null;  // should be in PP
-  eq RagConnect.getChild().containedFileName() = getFileName();
-  eq MRagConnect.getChild().containedFileName() = null;
+  eq ConnectSpecificationFile.containedFileName() = getFileName();
+  refine Navigation eq ASTNode.containedFileName() {
+    if (containedFile() == null) {
+      return containedConnectSpecification().containedFileName();
+    }
+    return refined();
+//    return containedFile().getFileName();
+  }
+
+  //--- allEndpointDefinitionList ---
+  syn List<EndpointDefinition> RagConnect.allEndpointDefinitionList() {
+    List<EndpointDefinition> result = new ArrayList<>();
+    for (var spec : getConnectSpecificationFileList()) {
+      spec.getEndpointDefinitionList().forEach(result::add);
+    }
+    return result;
+  }
+
+  //--- allDependencyDefinitionList ---
+  syn List<DependencyDefinition> RagConnect.allDependencyDefinitionList() {
+    List<DependencyDefinition> result = new ArrayList<>();
+    for (var spec : getConnectSpecificationFileList()) {
+      spec.getDependencyDefinitionList().forEach(result::add);
+    }
+    return result;
+  }
+
+  //--- allMappingDefinitionList ---
+  syn List<MappingDefinition> RagConnect.allMappingDefinitionList() {
+    List<MappingDefinition> result = new ArrayList<>();
+    for (var spec : getConnectSpecificationFileList()) {
+      spec.getMappingDefinitionList().forEach(result::add);
+    }
+    return result;
+  }
 
   // --- isTokenEndpointDefinition ---
   syn boolean EndpointDefinition.isTokenEndpointDefinition() = false;
@@ -70,7 +112,7 @@ aspect Navigation {
   // --- targetEndpointDefinition ---
   syn EndpointDefinition DependencyDefinition.targetEndpointDefinition() {
     // resolve definition in here, as we do not need resolveMethod in any other place (yet)
-    for (EndpointDefinition endpointDefinition : ragconnect().getEndpointDefinitionList()) {
+    for (EndpointDefinition endpointDefinition : ragconnect().allEndpointDefinitionList()) {
       if (endpointDefinition.isSendTokenEndpointDefinition() &&
           endpointDefinition.asSendTokenEndpointDefinition().getToken().equals(this.getTarget())) {
         return endpointDefinition;
@@ -96,4 +138,7 @@ aspect Navigation {
 
   // --- rootTypeComponents ---
   syn JastAddList<MTypeComponent> MHandler.rootTypeComponents() = mragconnect().getRootTypeComponents();
+
+  // --- isListComponent --- (defined in PP, but only on TypeComponent)
+  syn boolean Component.isListComponent() = false;
 }
diff --git a/ragconnect.base/src/main/jastadd/RagConnect.relast b/ragconnect.base/src/main/jastadd/RagConnect.relast
index 426b90ee22b0aeeb1a9d0cb7a5d77e52838bc0ff..309c4776d923a6afd7b98b7bec190d96db74f28e 100644
--- a/ragconnect.base/src/main/jastadd/RagConnect.relast
+++ b/ragconnect.base/src/main/jastadd/RagConnect.relast
@@ -1,4 +1,7 @@
-RagConnect ::= EndpointDefinition* DependencyDefinition* MappingDefinition* Program <FileName> ;
+RagConnect ::= ConnectSpecificationFile* Program ;
+
+abstract ConnectSpecification ::= EndpointDefinition* DependencyDefinition* MappingDefinition* ;
+ConnectSpecificationFile : ConnectSpecification ::= <FileName> ;
 
 abstract EndpointDefinition ::= <AlwaysApply:boolean> ;
 
diff --git a/ragconnect.base/src/main/jastadd/intermediate/Generation.jadd b/ragconnect.base/src/main/jastadd/intermediate/Generation.jadd
index 444f0214af4821f704a3307227c9f1b04b072e47..3b63bddfd2079518793308e0320fe2311724828e 100644
--- a/ragconnect.base/src/main/jastadd/intermediate/Generation.jadd
+++ b/ragconnect.base/src/main/jastadd/intermediate/Generation.jadd
@@ -62,12 +62,17 @@ aspect AttributesForMustache {
   syn String MEndpointDefinition.lastDefinitionToType() = lastDefinition().toType();
   syn String MEndpointDefinition.lastResult() = lastDefinition().outputVarName();
   syn String MEndpointDefinition.condition() {
+    // TODO probably, this has to be structured in a better way
     if (lastDefinition().mappingDef().getToType().isArray()) {
       return "java.util.Arrays.equals(" + preemptiveExpectedValue() + ", " + lastResult() + ")";
     }
     if (endpointDef().isTokenEndpointDefinition() && token().isPrimitiveType() && lastDefinition().mappingDef().getToType().isPrimitiveType()) {
       return preemptiveExpectedValue() + " == " + lastResult();
     }
+    if (endpointDef().isTypeEndpointDefinition() && type().isOptComponent()) {
+      // use "hasX()" instead of "getX() != null" for optionals
+      return "has" + typeName() + "()" + " && " + preemptiveExpectedValue() + ".equals(" + lastResult() + ")";
+    }
     if (lastDefinition().mappingDef().getToType().isPrimitiveType() || lastDefinition().mappingDef().isDefaultMappingDefinition()) {
       return preemptiveExpectedValue() + " != null && " + preemptiveExpectedValue() + ".equals(" + lastResult() + ")";
     }
@@ -172,7 +177,7 @@ aspect AttributesForMustache {
   syn lazy MRagConnect RagConnect.toMustache() {
     MRagConnect result = new MRagConnect();
     result.setRagConnect(this);
-    for (EndpointDefinition def : getEndpointDefinitionList()) {
+    for (EndpointDefinition def : allEndpointDefinitionList()) {
       if (def.isReceiveTokenEndpointDefinition()) {
         result.addTokenReceiveDefinition(def.asReceiveTokenEndpointDefinition().toMustache());
       } else if (def.isSendTokenEndpointDefinition()) {
@@ -188,7 +193,7 @@ aspect AttributesForMustache {
     for (MappingDefinition def : allMappingDefinitions()) {
       result.addMappingDefinition(def.toMustache());
     }
-    for (DependencyDefinition def : getDependencyDefinitionList()) {
+    for (DependencyDefinition def : allDependencyDefinitionList()) {
       result.addDependencyDefinition(def.toMustache());
     }
     for (TokenComponent token : getProgram().allTokenComponents()) {
@@ -311,7 +316,7 @@ aspect AspectGeneration {
 aspect RelationGeneration {
   syn java.util.List<Relation> RagConnect.additionalRelations() {
     java.util.List<Relation> result = new java.util.ArrayList<>();
-    for (DependencyDefinition dd : getDependencyDefinitionList()) {
+    for (DependencyDefinition dd : allDependencyDefinitionList()) {
       result.add(dd.getRelationToCreate());
     }
     return result;
diff --git a/ragconnect.base/src/main/jastadd/intermediate/Mappings.jrag b/ragconnect.base/src/main/jastadd/intermediate/Mappings.jrag
index 93f74e51d8acbf140b56e99e279a337506c3a6c0..47379bd06921509023f686ab05621b63eb5729d9 100644
--- a/ragconnect.base/src/main/jastadd/intermediate/Mappings.jrag
+++ b/ragconnect.base/src/main/jastadd/intermediate/Mappings.jrag
@@ -298,7 +298,7 @@ aspect Mappings {
   syn java.util.List<MappingDefinition> RagConnect.allMappingDefinitions() {
     java.util.List<MappingDefinition> result = new java.util.ArrayList<>();
     // user-defined mappings
-    getMappingDefinitionList().iterator().forEachRemaining(result::add);
+    allMappingDefinitionList().iterator().forEachRemaining(result::add);
     // byte[] <-> primitive conversion
     result.add(defaultBytesToBooleanMapping());
     result.add(defaultBytesToIntMapping());
diff --git a/ragconnect.base/src/main/jastadd/intermediate2mustache/MustacheNodesToYAML.jrag b/ragconnect.base/src/main/jastadd/intermediate2mustache/MustacheNodesToYAML.jrag
index 9de1c1e6ecf3e2f05580b3c324316708e353ad40..ef54a74e6fd57f11f17530e53524c63446277ca3 100644
--- a/ragconnect.base/src/main/jastadd/intermediate2mustache/MustacheNodesToYAML.jrag
+++ b/ragconnect.base/src/main/jastadd/intermediate2mustache/MustacheNodesToYAML.jrag
@@ -206,5 +206,5 @@ aspect Navigation {
   eq Document.getChild().program() = null;
   eq Document.getChild().ragconnect() = null;
   eq Document.getChild().containedFile() = null;
-  eq Document.getChild().containedFileName() = getFileName();
+  eq Document.containedFileName() = getFileName();
 }
diff --git a/ragconnect.base/src/main/jastadd/mustache b/ragconnect.base/src/main/jastadd/mustache
deleted file mode 160000
index c10bed0d03e3fa18b8133ce1de48de7646899615..0000000000000000000000000000000000000000
--- a/ragconnect.base/src/main/jastadd/mustache
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit c10bed0d03e3fa18b8133ce1de48de7646899615
diff --git a/ragconnect.base/src/main/jastadd/parser/Preamble.parser b/ragconnect.base/src/main/jastadd/parser/Preamble.parser
index d701db19d6edb6d6c616bc79fc0145ea59d2713d..c157ae91f69b0e8d41c40a5bd7426bb2d7fae803 100644
--- a/ragconnect.base/src/main/jastadd/parser/Preamble.parser
+++ b/ragconnect.base/src/main/jastadd/parser/Preamble.parser
@@ -4,4 +4,4 @@ import org.jastadd.ragconnect.ast.*;
 :};
 
 %goal goal;
-%goal ragconnect;
+%goal connect_specification_file;
diff --git a/ragconnect.base/src/main/jastadd/parser/RagConnect.parser b/ragconnect.base/src/main/jastadd/parser/RagConnect.parser
index a9d4809331c0ea20e11b0ec2a04dfd1f2c76e95b..eefb64e8ec713991b1c8d214025e71586ad4a645 100644
--- a/ragconnect.base/src/main/jastadd/parser/RagConnect.parser
+++ b/ragconnect.base/src/main/jastadd/parser/RagConnect.parser
@@ -1,9 +1,21 @@
-RagConnect ragconnect
-  = endpoint_definition.d ragconnect.r       {: r.getEndpointDefinitionList().insertChild(d, 0); return r; :}
-  | dependency_definition.d ragconnect.r   {: r.getDependencyDefinitionList().insertChild(d, 0); return r; :}
-  | mapping_definition.d ragconnect.r      {: r.getMappingDefinitionList().insertChild(d, 0); return r; :}
-  | comment ragconnect.r                   {: return r; :}
-  |                                     {: return new RagConnect(); :}
+ConnectSpecificationFile connect_specification_file
+  = endpoint_definition.d connect_specification_file.r
+    {:
+      r.getEndpointDefinitionList().insertChild(d, 0); return r;
+    :}
+  | dependency_definition.d connect_specification_file.r
+    {:
+      r.getDependencyDefinitionList().insertChild(d, 0); return r;
+    :}
+  | mapping_definition.d connect_specification_file.r
+    {:
+      r.getMappingDefinitionList().insertChild(d, 0); return r;
+    :}
+  | comment connect_specification_file.r
+    {:
+      return r;
+    :}
+  | {: return new ConnectSpecificationFile(); :}
 ;
 
 %embed {:
diff --git a/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/Compiler.java b/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/Compiler.java
index 88bfbdc1689fe0c8040efc952832c0bc9ac58b02..8035d76e424cd813e62c400ffd70f84a6167a98f 100644
--- a/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/Compiler.java
+++ b/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/Compiler.java
@@ -3,11 +3,11 @@ package org.jastadd.ragconnect.compiler;
 import beaver.Parser;
 import org.jastadd.option.BooleanOption;
 import org.jastadd.option.ValueOption;
-import org.jastadd.relast.compiler.AbstractCompiler;
-import org.jastadd.relast.compiler.CompilerException;
 import org.jastadd.ragconnect.ast.*;
 import org.jastadd.ragconnect.parser.RagConnectParser;
 import org.jastadd.ragconnect.scanner.RagConnectScanner;
+import org.jastadd.relast.compiler.AbstractCompiler;
+import org.jastadd.relast.compiler.CompilerException;
 
 import java.io.*;
 import java.nio.file.Files;
@@ -15,6 +15,8 @@ import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.nio.file.StandardCopyOption;
 import java.util.*;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 
 public class Compiler extends AbstractCompiler {
 
@@ -25,10 +27,14 @@ public class Compiler extends AbstractCompiler {
   private BooleanOption optionVerbose;
   private BooleanOption optionLogReads;
   private BooleanOption optionLogWrites;
+  private BooleanOption optionLogIncremental;
+  private BooleanOption optionExperimentalJastAdd329;
 
   private static final String OPTION_PROTOCOL_MQTT = "mqtt";
   private static final String OPTION_PROTOCOL_REST = "rest";
 
+  private final static Logger LOGGER = Logger.getLogger(Compiler.class.getName());
+
   public Compiler() {
     super("ragconnect", true);
   }
@@ -43,8 +49,11 @@ public class Compiler extends AbstractCompiler {
       getConfiguration().printHelp(System.out);
       return 0;
     }
+    if (optionVerbose.value()) {
+      LOGGER.setLevel(Level.FINE);
+    }
 
-    printMessage("Running RagConnect " + readVersion());
+    LOGGER.info(() -> "Running RagConnect " + readVersion());
 
     if (!getConfiguration().outputDir().exists()) {
       try {
@@ -61,10 +70,11 @@ public class Compiler extends AbstractCompiler {
     RagConnect ragConnect = parseProgram(getConfiguration().getFiles());
 
     if (!ragConnect.errors().isEmpty()) {
-      System.err.println("Errors:");
+      StringBuilder sb = new StringBuilder("Errors:\n");
       for (ErrorMessage e : ragConnect.errors()) {
-        System.err.println(e);
+        sb.append(e).append("\n");
       }
+      LOGGER.severe(sb::toString);
       System.exit(1);
     }
 
@@ -76,7 +86,7 @@ public class Compiler extends AbstractCompiler {
       return 0;
     }
 
-    printMessage("Writing output files");
+    LOGGER.fine("Writing output files");
     final List<String> handlers = new ArrayList<>();
     if (ASTNode.usesMqtt) {
       handlers.add("MqttHandler.jadd");
@@ -107,12 +117,11 @@ public class Compiler extends AbstractCompiler {
   }
 
   public static void main(String[] args) {
-    System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager");
     System.setProperty("mustache.debug", "true");
     try {
       new Compiler().run(args);
     } catch (CompilerException e) {
-      e.printStackTrace();
+      LOGGER.log(Level.SEVERE, e.getMessage(), e);
       System.exit(1);
     }
   }
@@ -137,12 +146,6 @@ public class Compiler extends AbstractCompiler {
     }
   }
 
-  private void printMessage(String message) {
-    if (optionVerbose.value()) {
-      System.out.println(message);
-    }
-  }
-
   private void writeToFile(Path path, String str) throws CompilerException {
     try (BufferedWriter writer = Files.newBufferedWriter(path)) {
       writer.append(str);
@@ -175,6 +178,12 @@ public class Compiler extends AbstractCompiler {
     optionLogWrites = addOption(
         new BooleanOption("logWrites", "Enable logging for every write.")
         .defaultValue(false));
+    optionLogIncremental = addOption(
+        new BooleanOption("logIncremental", "Enable logging for observer in incremental dependency tracking.")
+            .defaultValue(false));
+    optionExperimentalJastAdd329 = addOption(
+        new BooleanOption("experimental-jastadd-329", "Use trace events INC_FLUSH_START and INC_FLUSH_END (JastAdd issue #329).")
+            .defaultValue(false));
   }
 
   private RagConnect parseProgram(Collection<String> files) throws CompilerException {
@@ -195,14 +204,13 @@ public class Compiler extends AbstractCompiler {
         case "ast":
         case "relast":
           // processGrammar
-          parseGrammar(program, filename);
+          program.addGrammarFile(parseGrammar(filename));
           atLeastOneGrammar = true;
           break;
         case "connect":
         case "ragconnect":
           // process ragConnect
-          RagConnect parsedRagConnect = parseRagConnect(program, filename);
-          mergeRagConnectDefinitions(ragConnect, parsedRagConnect);
+          ragConnect.addConnectSpecificationFile(parseConnectSpec(filename));
           atLeastOneRagConnect = true;
           break;
         default:
@@ -210,10 +218,10 @@ public class Compiler extends AbstractCompiler {
       }
     }
     if (!atLeastOneGrammar) {
-      System.err.println("No grammar file specified! (*.ast, *.relast)");
+      LOGGER.severe(() -> "No grammar file specified! (*.ast, *.relast)");
     }
     if (!atLeastOneRagConnect) {
-      System.err.println("No ragconnect file specified! (*.connect, *.ragconnect)");
+      LOGGER.severe(() -> "No ragconnect file specified! (*.connect, *.ragconnect)");
     }
     if (!atLeastOneGrammar && !atLeastOneRagConnect) {
       System.exit(1);
@@ -226,39 +234,40 @@ public class Compiler extends AbstractCompiler {
     ragConnect.additionalRelations().forEach(ragConnectGrammarPart::addDeclaration);
     ASTNode.loggingEnabledForReads = optionLogReads.value();
     ASTNode.loggingEnabledForWrites = optionLogWrites.value();
+    ASTNode.loggingEnabledForIncremental = optionLogIncremental.value();
+    ASTNode.experimentalJastAdd329 = optionExperimentalJastAdd329.value();
 
     // reuse "--incremental" option of JastAdd
     ASTNode.incrementalOptionActive = getConfiguration().incremental() && getConfiguration().traceFlush();
-    printMessage("ASTNode.incrementalOptionActive = " + ASTNode.incrementalOptionActive);
+    LOGGER.fine(() -> "ASTNode.incrementalOptionActive = " + ASTNode.incrementalOptionActive);
 
     ASTNode.usesMqtt = optionProtocols.hasValue(OPTION_PROTOCOL_MQTT);
     ASTNode.usesRest = optionProtocols.hasValue(OPTION_PROTOCOL_REST);
     return ragConnect;
   }
 
-  private void parseGrammar(Program program, String filename) throws CompilerException {
+  private GrammarFile parseGrammar(String filename) throws CompilerException {
     try (BufferedReader reader = Files.newBufferedReader(Paths.get(filename))) {
       RagConnectScanner scanner = new RagConnectScanner(reader);
       RagConnectParser parser = new RagConnectParser();
       GrammarFile grammarFile = (GrammarFile) parser.parse(scanner);
       if (optionVerbose.value()) {
-        grammarFile.dumpTree(System.out);
+        LOGGER.fine(grammarFile::dumpTree);
       }
-      program.addGrammarFile(grammarFile);
       grammarFile.setFileName(toBaseName(filename));
+      return grammarFile;
     } catch (IOException | Parser.Exception e) {
       throw new CompilerException("Could not parse grammar file " + filename, e);
     }
   }
 
-  private RagConnect parseRagConnect(Program program, String filename) throws CompilerException {
+  private ConnectSpecificationFile parseConnectSpec(String filename) throws CompilerException {
     try (BufferedReader reader = Files.newBufferedReader(Paths.get(filename))) {
       RagConnectScanner scanner = new RagConnectScanner(reader);
       RagConnectParser parser = new RagConnectParser();
-      RagConnect ragConnect = (RagConnect) parser.parse(scanner, RagConnectParser.AltGoals.ragconnect);
-      ragConnect.setProgram(program);
-      ragConnect.setFileName(toBaseName(filename));
-      return ragConnect;
+      ConnectSpecificationFile specificationFile = (ConnectSpecificationFile) parser.parse(scanner, RagConnectParser.AltGoals.connect_specification_file);
+      specificationFile.setFileName(toBaseName(filename));
+      return specificationFile;
     } catch (IOException | Parser.Exception e) {
       throw new CompilerException("Could not parse connect file " + filename, e);
     }
@@ -273,13 +282,6 @@ public class Compiler extends AbstractCompiler {
     return new File(filename).getName();
   }
 
-  private void mergeRagConnectDefinitions(RagConnect ragConnect, RagConnect ragConnectToIntegrate) {
-    ragConnectToIntegrate.getEndpointDefinitionList().forEach(ragConnect::addEndpointDefinition);
-    ragConnectToIntegrate.getMappingDefinitionList().forEach(ragConnect::addMappingDefinition);
-    ragConnectToIntegrate.getDependencyDefinitionList().forEach(ragConnect::addDependencyDefinition);
-    ragConnect.setFileName(ragConnect.getFileName().isEmpty() ? ragConnectToIntegrate.getFileName() : ragConnect.getFileName() + " + " + ragConnectToIntegrate.getFileName());
-  }
-
 //  protected void printUsage() {
 //    System.out.println("Usage: java -jar ragconnect.jar [--option1] [--option2=value] ...  <filename1> <filename2> ... ");
 //    System.out.println("Options:");
diff --git a/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/SimpleMain.java b/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/SimpleMain.java
deleted file mode 100644
index 83c282f205af2ff337c3eea7d2b1b7fda8e3cc47..0000000000000000000000000000000000000000
--- a/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/SimpleMain.java
+++ /dev/null
@@ -1,81 +0,0 @@
-package org.jastadd.ragconnect.compiler;
-
-import org.jastadd.ragconnect.ast.Document;
-import org.jastadd.ragconnect.ast.ListElement;
-import org.jastadd.ragconnect.ast.MappingElement;
-
-/**
- * Testing Ros2Rag without parser.
- *
- * @author rschoene - Initial contribution
- */
-public class SimpleMain {
-
-  private static void printManualYAML() {
-    Document doc = new Document();
-    MappingElement root = new MappingElement();
-    MappingElement firstLevel = new MappingElement();
-    firstLevel.put("server", "tcp://localhost:1883");
-    firstLevel.put("robot_speed_factor", ".7");
-
-    MappingElement theTopics = new MappingElement();
-    theTopics.put("robotConfig", "robotconfig");
-    theTopics.put("trajectory", "trajectory");
-    theTopics.put("nextStep", "ros2rag/nextStep");
-    firstLevel.put("topics", theTopics);
-
-    firstLevel.put("zone_size", "0.5");
-
-    ListElement theZones = new ListElement();
-    theZones.add("1 1");
-    theZones.add("0 1");
-    theZones.add("-1 1");
-    firstLevel.put("zones", theZones);
-
-    MappingElement pandaParts = new MappingElement();
-    MappingElement thePanda = new MappingElement();
-    thePanda.put("Link0", "panda_link0");
-    thePanda.put("Link1", "panda_link1");
-    thePanda.put("Link2", "panda_link2");
-    thePanda.put("Link3", "panda_link3");
-    thePanda.put("Link4", "panda_link4");
-    thePanda.put("Link5", "panda_link5");
-    thePanda.put("Link6", "panda_link6");
-    thePanda.put("RightFinger", "panda_rightfinger");
-    thePanda.put("LeftFinger", "panda_leftfinger");
-    pandaParts.put("panda", thePanda);
-    firstLevel.put("parts", pandaParts);
-
-    MappingElement endEffectorParts = new MappingElement();
-    MappingElement endEffector = new MappingElement();
-    endEffector.put("EndEffector", "panda_hand");
-    endEffectorParts.put("panda", endEffector);
-    firstLevel.put("end_effectors", endEffectorParts);
-
-    ListElement theGoalPoses = new ListElement();
-    theGoalPoses.add(makePose("0.4 0.4 0.3"));
-    theGoalPoses.add(makePose("-0.4 0.4 0.3"));
-    theGoalPoses.add(makePose("-0.4 -0.4 0.3"));
-    theGoalPoses.add(makePose("0.4 0.4 0.3"));
-    theGoalPoses.add(makePose("-0.4 0.4 0.3"));
-    theGoalPoses.add(makePose("0.4 0.4 0.3"));
-    firstLevel.put("goal_poses", theGoalPoses);
-
-    root.put("panda_mqtt_connector", firstLevel);
-    doc.setRootElement(root);
-
-    System.out.println(doc.prettyPrint());
-  }
-
-  private static MappingElement makePose(String position) {
-    MappingElement goalPose = new MappingElement();
-    goalPose.put("position", position);
-    goalPose.put("orientation", "1 1 0 0");
-    goalPose.put("work", "20000");
-    return goalPose;
-  }
-
-  public static void main(String[] args) {
-    printManualYAML();
-  }
-}
diff --git a/ragconnect.base/src/main/resources/MqttHandler.jadd b/ragconnect.base/src/main/resources/MqttHandler.jadd
index ab56c0405dd518204378d840d85954e0e27b784f..f84d8657ca7db7be868205b11cd939f27c08965d 100644
--- a/ragconnect.base/src/main/resources/MqttHandler.jadd
+++ b/ragconnect.base/src/main/resources/MqttHandler.jadd
@@ -120,11 +120,17 @@ public class MqttHandler {
   }
 
   /**
-   * Sets the host (with default port) to receive messages from, and connects to it.
+   * Sets the host to receive messages from, and connects to it.
+   * @param host name of the host to connect to, format is either <code>"$name"</code> or <code>"$name:$port"</code>
    * @throws IOException if could not connect, or could not subscribe to a topic
    * @return self
    */
   public MqttHandler setHost(String host) throws java.io.IOException {
+    if (host.contains(":")) {
+      int colon_index = host.indexOf(":");
+      return setHost(host.substring(0, colon_index),
+                     Integer.parseInt(host.substring(colon_index + 1)));
+    }
     return setHost(host, DEFAULT_PORT);
   }
 
@@ -167,7 +173,11 @@ public class MqttHandler {
         } else {
           byte[] message = body.toByteArray();
           for (java.util.function.Consumer<byte[]> callback : callbackList) {
-            callback.accept(message);
+            try {
+              callback.accept(message);
+            } catch (Exception e) {
+              logger.catching(e);
+            }
           }
         }
         ack.onSuccess(null);  // always acknowledge message
diff --git a/ragconnect.base/src/main/resources/handleUri.mustache b/ragconnect.base/src/main/resources/handleUri.mustache
index 1a20e7c4df2a4c54ccba4c825fff55ca89a51dec..aa4176ef0b067bca3c54ca754096f633cadcfa71 100644
--- a/ragconnect.base/src/main/resources/handleUri.mustache
+++ b/ragconnect.base/src/main/resources/handleUri.mustache
@@ -1,4 +1,4 @@
-String scheme,host, path;
+String scheme, host, path;
 java.net.URI uri;
 try {
   uri = new java.net.URI({{connectParameterName}});
@@ -9,3 +9,15 @@ try {
   System.err.println(e.getMessage());  // Maybe re-throw error?
   return false;
 }
+if (scheme == null || scheme.isBlank()) {
+  System.err.println("Missing or empty scheme in " + uri);
+  return false;
+}
+if (host == null || host.isBlank()) {
+  System.err.println("Missing or empty host in " + uri);
+  return false;
+}
+if (path == null || path.isBlank()) {
+  System.err.println("Missing or empty path in " + uri);
+  return false;
+}
diff --git a/ragconnect.base/src/main/resources/log4j2.xml b/ragconnect.base/src/main/resources/log4j2.xml
deleted file mode 100644
index 98cfd73c75df58d8598521bc10b043e214ec4ad8..0000000000000000000000000000000000000000
--- a/ragconnect.base/src/main/resources/log4j2.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<Configuration status="INFO">
-    <Appenders>
-        <Console name="Console" target="SYSTEM_OUT">
-            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
-        </Console>
-    </Appenders>
-    <Loggers>
-        <Root level="info">
-            <AppenderRef ref="Console"/>
-        </Root>
-    </Loggers>
-</Configuration>
\ No newline at end of file
diff --git a/ragconnect.base/src/main/resources/mappingApplication.mustache b/ragconnect.base/src/main/resources/mappingApplication.mustache
index 064004b94886c155be883e7b186557c6fa87d38c..47ec31ee58b27485a51e2b7e3a7de196cbe130f3 100644
--- a/ragconnect.base/src/main/resources/mappingApplication.mustache
+++ b/ragconnect.base/src/main/resources/mappingApplication.mustache
@@ -3,6 +3,9 @@ try {
   {{#InnerMappingDefinitions}}
   {{^last}}{{toType}} {{/last}}{{outputVarName}} = {{methodName}}({{inputVarName}});
   {{/InnerMappingDefinitions}}
+} catch (RagConnectRejectMappingException e) {
+  // do not print message in case of rejection
+  {{preemptiveReturn}}
 } catch (Exception e) {
   e.printStackTrace();
   {{preemptiveReturn}}
diff --git a/ragconnect.base/src/main/resources/ragConnectVersion.properties b/ragconnect.base/src/main/resources/ragConnectVersion.properties
index f1b4c621c5fc52474900a7a204a52a82baa0c87f..cc1f011163b2d40f2569f47f8c4151154d2fab63 100644
--- a/ragconnect.base/src/main/resources/ragConnectVersion.properties
+++ b/ragconnect.base/src/main/resources/ragConnectVersion.properties
@@ -1,2 +1,2 @@
-#Wed Apr 14 09:40:24 CEST 2021
-version=0.3.0
+#Thu Jun 03 11:17:05 CEST 2021
+version=0.3.1
diff --git a/ragconnect.base/src/main/resources/ragconnect.mustache b/ragconnect.base/src/main/resources/ragconnect.mustache
index 2cc2423cc2008ea80f6e614cd0eabea471692cbe..50cd5cece4429e3529c64f5896548e50bbe564b0 100644
--- a/ragconnect.base/src/main/resources/ragconnect.mustache
+++ b/ragconnect.base/src/main/resources/ragconnect.mustache
@@ -17,6 +17,11 @@ aspect RagConnect {
   {{> sendDefinition}}
   {{/TypeSendDefinitions}}
 
+  class RagConnectRejectMappingException extends RuntimeException {}
+  private static void ASTNode.reject() {
+    throw new RagConnectRejectMappingException();
+  }
+
   {{#MappingDefinitions}}
   {{#isUsed}}
   {{> mappingDefinition}}
@@ -46,13 +51,15 @@ aspect RagConnect {
 
 {{#incrementalOptionActive}}
 aspect RagConnectObserver {
+
   class RagConnectObserver implements ASTState.Trace.Receiver {
-    ASTState.Trace.Receiver oldReceiver;
+
     class RagConnectObserverEntry {
       final ConnectToken connectToken;
       final ASTNode node;
       final String attributeString;
       final Runnable attributeCall;
+
       RagConnectObserverEntry(ConnectToken connectToken, ASTNode node, String attributeString, Runnable attributeCall) {
         this.connectToken = connectToken;
         this.node = node;
@@ -60,16 +67,39 @@ aspect RagConnectObserver {
         this.attributeCall = attributeCall;
       }
     }
+
+{{#experimentalJastAdd329}}
+    class RagConnectObserverStartEntry {
+      final ASTNode node;
+      final String attributeString;
+      final Object flushIncToken;
+      RagConnectObserverStartEntry(ASTNode node, String attributeString, Object flushIncToken) {
+        this.node = node;
+        this.attributeString = attributeString;
+        this.flushIncToken = flushIncToken;
+      }
+    }
+{{/experimentalJastAdd329}}
+
+    ASTState.Trace.Receiver oldReceiver;
+
     java.util.List<RagConnectObserverEntry> observedNodes = new java.util.ArrayList<>();
+
+{{#experimentalJastAdd329}}
+    java.util.Set<RagConnectObserverEntry> entryQueue = new java.util.HashSet<>();
+    RagConnectObserverStartEntry startEntry = null;
+{{/experimentalJastAdd329}}
+
     RagConnectObserver(ASTNode node) {
       // set the receiver. potentially dangerous because overriding existing receiver!
       oldReceiver = node.trace().getReceiver();
       node.trace().setReceiver(this);
     }
+
     void add(ConnectToken connectToken, ASTNode node, String attributeString, Runnable attributeCall) {
-      {{#loggingEnabledForWrites}}
+      {{#loggingEnabledForIncremental}}
       System.out.println("** observer add: " + node + " on " + attributeString);
-      {{/loggingEnabledForWrites}}
+      {{/loggingEnabledForIncremental}}
       observedNodes.add(new RagConnectObserverEntry(connectToken, node, attributeString, attributeCall));
     }
     void remove(ConnectToken connectToken) {
@@ -78,21 +108,56 @@ aspect RagConnectObserver {
     @Override
     public void accept(ASTState.Trace.Event event, ASTNode node, String attribute, Object params, Object value) {
       oldReceiver.accept(event, node, attribute, params, value);
-      // ignore all events but INC_FLUSH_ATTR
+{{#experimentalJastAdd329}}
+      // react to INC_FLUSH_START and remember entry
+      if (event == ASTState.Trace.Event.INC_FLUSH_START && startEntry == null) {
+        {{#loggingEnabledForIncremental}}
+        System.out.println("** observer start: " + node + " on " + attribute);
+        {{/loggingEnabledForIncremental}}
+        startEntry = new RagConnectObserverStartEntry(node, attribute, value);
+        return;
+      }
+
+      // react to INC_FLUSH_END and process queued entries, if it matches start entry
+      if (event == ASTState.Trace.Event.INC_FLUSH_END &&
+            node == startEntry.node &&
+            attribute == startEntry.attributeString &&
+            value == startEntry.flushIncToken) {
+        // create a copy of the queue to avoid entering this again causing an endless recursion
+        RagConnectObserverEntry[] entriesToProcess = entryQueue.toArray(new RagConnectObserverEntry[entryQueue.size()]);
+        entryQueue.clear();
+        startEntry = null;
+        {{#loggingEnabledForIncremental}}
+        System.out.println("** observer process (" + entriesToProcess.length + "): " + node + " on " + attribute);
+        {{/loggingEnabledForIncremental}}
+        for (RagConnectObserverEntry entry : entriesToProcess) {
+          entry.attributeCall.run();
+        }
+        return;
+      }
+
+{{/experimentalJastAdd329}}
+      // ignore all other events but INC_FLUSH_ATTR
       if (event != ASTState.Trace.Event.INC_FLUSH_ATTR) {
         return;
       }
-      {{#loggingEnabledForWrites}}
+
+      {{#loggingEnabledForIncremental}}
       System.out.println("** observer check INC_FLUSH_ATTR event: " + node + " on " + attribute);
-      {{/loggingEnabledForWrites}}
+      {{/loggingEnabledForIncremental}}
       // iterate through list, if matching pair. could maybe be more efficient.
       for (RagConnectObserverEntry entry : observedNodes) {
         if (entry.node.equals(node) && entry.attributeString.equals(attribute)) {
           // hit. call the attribute/nta-token
-          {{#loggingEnabledForWrites}}
+          {{#loggingEnabledForIncremental}}
           System.out.println("** observer hit: " + entry.node + " on " + entry.attributeString);
-          {{/loggingEnabledForWrites}}
+          {{/loggingEnabledForIncremental}}
+{{#experimentalJastAdd329}}
+          entryQueue.add(entry);
+{{/experimentalJastAdd329}}
+{{^experimentalJastAdd329}}
           entry.attributeCall.run();
+{{/experimentalJastAdd329}}
         }
       }
     }
diff --git a/ragconnect.tests/build.gradle b/ragconnect.tests/build.gradle
index 30fb2237ee167f74879bbb8903553d4eaaf1ceb1..485a59d61efefe9f77592c1f62ca485db087da98 100644
--- a/ragconnect.tests/build.gradle
+++ b/ragconnect.tests/build.gradle
@@ -36,7 +36,8 @@ repositories {
 dependencies {
     implementation project(':ragconnect.base')
 
-    runtime group: 'org.jastadd', name: 'jastadd', version: '2.3.5'
+    runtimeOnly group: 'org.jastadd', name: 'jastadd', version: '2.3.5'
+//    runtimeOnly fileTree(include: ['jastadd2.jar'], dir: '../libs')
 
     testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.4.0'
     testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.4.0'
@@ -92,11 +93,13 @@ task allTests(type: Test, dependsOn: testClasses) {
 }
 
 task specificTest(type: Test, dependsOn: testClasses) {
-    description = 'Run test tagged with "NewTest"'
+    description = 'Run test tagged with tag given by "-PincludeTags="'
     group = 'verification'
+    String tags = project.hasProperty("includeTags") ?
+                   project.property("includeTags") : ''
 
     useJUnitPlatform {
-        includeTags 'SpecificTest'
+        includeTags tags
     }
 }
 
@@ -275,6 +278,7 @@ task compileIncremental(type: RagConnectTest) {
         inputFiles = [file('src/test/01-input/incremental/Test.relast'),
                       file('src/test/01-input/incremental/Test.connect')]
         rootNode = 'A'
+        logWrites = true
     }
     relast {
         useJastAddNames = true
@@ -342,6 +346,7 @@ task compileTreeIncremental(type: RagConnectTest) {
         inputFiles = [file('src/test/01-input/tree/Test.relast'),
                       file('src/test/01-input/tree/Test.connect')]
         rootNode = 'Root'
+        logWrites = true
     }
     relast {
         useJastAddNames = true
@@ -388,6 +393,7 @@ task compileTreeAllowedTokensIncremental(type: RagConnectTest) {
         inputFiles = [file('src/test/01-input/treeAllowedTokens/Test.relast'),
                       file('src/test/01-input/treeAllowedTokens/Test.connect')]
         rootNode = 'Root'
+        logWrites = true
     }
     relast {
         useJastAddNames = true
diff --git a/ragconnect.tests/src/test/01-input/errors/Errors.expected b/ragconnect.tests/src/test/01-input/errors/Errors.expected
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/ragconnect.tests/src/test/01-input/errors/Part.expected b/ragconnect.tests/src/test/01-input/errors/Part.expected
new file mode 100644
index 0000000000000000000000000000000000000000..70ea4a4baeaffb5b35df672c03e90821eb3cec82
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/errors/Part.expected
@@ -0,0 +1,11 @@
+Part1.connect Line 3, column 1: Receive definition already defined for DoubledValue
+Part1.connect Line 4, column 1: Receive definition already defined for DoubledValue
+Part1.connect Line 10, column 1: Receiving target token must not be an NTA token!
+Part1.connect Line 13, column 1: No suitable default mapping found for type java.util.List
+Part1.connect Line 13, column 1: to-type of last mapping (java.util.List) not assignable to type of the Token (String)!
+Part1.connect Line 16, column 1: to-type of last mapping (List) not assignable to type of the Token (String)!
+Part1.connect Line 19, column 1: to-type of last mapping (int) not assignable to type of the Token (String)!
+Part2.connect Line 5, column 1: Send definition already defined for DoubledValue
+Part2.connect Line 6, column 1: Send definition already defined for DoubledValue
+Part2.connect Line 17, column 1: The name of a dependency definition must not be equal to a list-node on the source
+Part2.connect Line 22, column 1: Dependency definition already defined for D with name DoubledValue
diff --git a/ragconnect.tests/src/test/01-input/errors/Part1.connect b/ragconnect.tests/src/test/01-input/errors/Part1.connect
new file mode 100644
index 0000000000000000000000000000000000000000..a1be3918b36ee19561a278639555db14efb8da06
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/errors/Part1.connect
@@ -0,0 +1,33 @@
+// --- update receive definitions ---
+// Error: there must not be two receive definitions for the same token
+receive B.DoubledValue ;
+receive B.DoubledValue using IntToInt ;
+
+// NOT HANDLED \\ Error: the token must be resolvable within the parent type
+// NOT HANDLED \\ receive B.NonExisting ;
+
+// Error: the Token must not be a TokenNTA (i.e., check for !Token.getNTA())
+receive B.ErrorNTA ;
+
+// Error: from-type of first mapping must be byte[] or a supported primitive type
+receive B.ErrorTypeOfFirstMapping using ListToList ;
+
+// Error: to-type of last mapping must be type of the Token
+receive B.ErrorTypeOfLastMapping using StringToList ;
+
+// Error: types of mappings must match (modulo inheritance)
+receive B.ErrorTypeMismatch using StringToList, IntToInt ;
+
+// --- update send definitions ---
+// NOT HANDLED \\ Error: the token must be resolvable within the parent type
+// NOT HANDLED \\ receive C.NonExisting ;
+
+// Error: Token must be a TokenNTA (i.e., check for Token.getNTA())
+send C.ErrorNotNTA ;
+
+// Error: from-type of first mapping must be type of Token
+send C.ErrorTypeOfFirstMapping using IntToInt ;
+
+// Error: to-type of last mapping must be byte[] or a supported primitive type
+send C.ErrorTypeOfLastMapping1 using StringToList ;
+send C.ErrorTypeOfLastMapping2 ;
diff --git a/ragconnect.tests/src/test/01-input/errors/Part2.connect b/ragconnect.tests/src/test/01-input/errors/Part2.connect
new file mode 100644
index 0000000000000000000000000000000000000000..4d1148260a36855528b37f8b45020e076ff9ff1e
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/errors/Part2.connect
@@ -0,0 +1,38 @@
+// Error: types of mappings must match (modulo inheritance)
+send C.ErrorTypeMismatch using StringToList, IntToInt ;
+
+// Error: no more than one send mapping for each TokenComponent
+send C.DoubledValue ;
+send C.DoubledValue using IntToInt ;
+
+// --- dependency definitions ---
+// NOT HANDLED \\ Error: Both, source and target must be resolvable within the parent type
+// NOT HANDLED \\ D.SourceNonExistingTarget canDependOn D.NonExisting as NonExistingTarget ;
+// NOT HANDLED \\ D.NonExisting canDependOn D.TargetNonExistingSource as NonExistingSource ;
+
+// Error: There must be a send update definition for the target token
+D.SourceNoWriteDef canDependOn D.TargetNoWriteDef as NoWriteDef ;
+
+// Error: The name of a dependency definition must not be equal to a list-node on the source
+D.SourceSameAsListNode canDependOn D.TargetSameAsListNode as MyList ;
+send D.TargetSameAsListNode;
+
+// Error: There must not be two dependency definitions with the same name
+D.SourceDoubledValue canDependOn D.TargetDoubledValue as DoubledValue ;
+D.SourceDoubledValue canDependOn D.TargetDoubledValue as DoubledValue ;
+send D.TargetDoubledValue;
+
+// --- mapping definitions ---
+ListToList maps java.util.List<String> list to java.util.List<String> {:
+  return list;
+:}
+
+StringToList maps String s to List<String> {:
+  java.util.List<String> result = new java.util.ArrayList<>();
+  result.add(s);
+  return result;
+:}
+
+IntToInt maps int number to int {:
+  return number + 1;
+:}
diff --git a/ragconnect.tests/src/test/01-input/errors/Errors.connect b/ragconnect.tests/src/test/01-input/errors/Standard.connect
similarity index 100%
rename from ragconnect.tests/src/test/01-input/errors/Errors.connect
rename to ragconnect.tests/src/test/01-input/errors/Standard.connect
diff --git a/ragconnect.tests/src/test/01-input/errors/Standard.expected b/ragconnect.tests/src/test/01-input/errors/Standard.expected
new file mode 100644
index 0000000000000000000000000000000000000000..3ee660570525834a63c41f26820966a7504f7789
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/errors/Standard.expected
@@ -0,0 +1,11 @@
+Standard.connect Line 3, column 1: Receive definition already defined for DoubledValue
+Standard.connect Line 4, column 1: Receive definition already defined for DoubledValue
+Standard.connect Line 10, column 1: Receiving target token must not be an NTA token!
+Standard.connect Line 13, column 1: No suitable default mapping found for type java.util.List
+Standard.connect Line 13, column 1: to-type of last mapping (java.util.List) not assignable to type of the Token (String)!
+Standard.connect Line 16, column 1: to-type of last mapping (List) not assignable to type of the Token (String)!
+Standard.connect Line 19, column 1: to-type of last mapping (int) not assignable to type of the Token (String)!
+Standard.connect Line 39, column 1: Send definition already defined for DoubledValue
+Standard.connect Line 40, column 1: Send definition already defined for DoubledValue
+Standard.connect Line 51, column 1: The name of a dependency definition must not be equal to a list-node on the source
+Standard.connect Line 56, column 1: Dependency definition already defined for D with name DoubledValue
diff --git a/ragconnect.tests/src/test/01-input/regression-tests/issue27/.gitignore b/ragconnect.tests/src/test/01-input/regression-tests/issue27/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..607b7610731b1ddf9dc05e605bd374a4b59ea360
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/regression-tests/issue27/.gitignore
@@ -0,0 +1 @@
+/*.noNewLine.*
diff --git a/ragconnect.tests/src/test/01-input/regression-tests/issue27/README.md b/ragconnect.tests/src/test/01-input/regression-tests/issue27/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..96595af1b32ba8487f2e8e663a944b0be52dff28
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/regression-tests/issue27/README.md
@@ -0,0 +1,3 @@
+# Issue27
+
+Regression test for failing parser when missing newline at end of specification.
diff --git a/ragconnect.tests/src/test/01-input/regression-tests/issue27/Test.connect b/ragconnect.tests/src/test/01-input/regression-tests/issue27/Test.connect
new file mode 100644
index 0000000000000000000000000000000000000000..28827bb62d7b801cf2f8bc605afc90c122f0cb6a
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/regression-tests/issue27/Test.connect
@@ -0,0 +1 @@
+receive A.Name ;
diff --git a/ragconnect.tests/src/test/01-input/regression-tests/issue27/Test.relast b/ragconnect.tests/src/test/01-input/regression-tests/issue27/Test.relast
new file mode 100644
index 0000000000000000000000000000000000000000..4c479a5756a37e42c74459c22006912445baa9b3
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/regression-tests/issue27/Test.relast
@@ -0,0 +1 @@
+A ::= <Name:String> ;
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/DefaultOnlyReadTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/DefaultOnlyReadTest.java
index cf5e02f956ae55258f08458bcb88130e1afca4b0..d9cf9d92a456a02cd2acba9e5f8990527d5842e9 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/DefaultOnlyReadTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/DefaultOnlyReadTest.java
@@ -2,7 +2,6 @@ package org.jastadd.ragconnect.tests;
 
 import defaultOnlyRead.ast.A;
 import defaultOnlyRead.ast.BoxedTypes;
-import defaultOnlyRead.ast.MqttHandler;
 import defaultOnlyRead.ast.NativeTypes;
 import org.junit.jupiter.api.Test;
 
@@ -10,7 +9,8 @@ import java.io.IOException;
 import java.nio.file.Paths;
 import java.util.concurrent.TimeUnit;
 
-import static org.jastadd.ragconnect.tests.TestUtils.*;
+import static org.jastadd.ragconnect.tests.TestUtils.mqttUri;
+import static org.jastadd.ragconnect.tests.TestUtils.testJaddContainReferenceToJackson;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
@@ -68,39 +68,39 @@ public class DefaultOnlyReadTest extends AbstractMqttTest {
   protected void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException {
     model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS);
 
-    integers.connectBooleanValue(mqttUri(TOPIC_NATIVE_BOOLEAN));
-    integers.connectIntValue(mqttUri(TOPIC_NATIVE_INT));
-    integers.connectShortValue(mqttUri(TOPIC_NATIVE_SHORT));
-    integers.connectLongValue(mqttUri(TOPIC_NATIVE_LONG));
-    floats.connectFloatValue(mqttUri(TOPIC_NATIVE_FLOAT));
-    floats.connectDoubleValue(mqttUri(TOPIC_NATIVE_DOUBLE));
-    chars.connectCharValue(mqttUri(TOPIC_NATIVE_CHAR));
-    chars.connectStringValue(mqttUri(TOPIC_NATIVE_STRING));
-
-    integers.connectBooleanValueTransformed(mqttUri(TOPIC_NATIVE_BOOLEAN));
-    integers.connectIntValueTransformed(mqttUri(TOPIC_NATIVE_INT));
-    integers.connectShortValueTransformed(mqttUri(TOPIC_NATIVE_SHORT));
-    integers.connectLongValueTransformed(mqttUri(TOPIC_NATIVE_LONG));
-    floats.connectFloatValueTransformed(mqttUri(TOPIC_NATIVE_FLOAT));
-    floats.connectDoubleValueTransformed(mqttUri(TOPIC_NATIVE_DOUBLE));
-    chars.connectCharValueTransformed(mqttUri(TOPIC_NATIVE_CHAR));
-    chars.connectStringValueTransformed(mqttUri(TOPIC_NATIVE_STRING));
-
-    allBoxed.connectBooleanValue(mqttUri(TOPIC_BOXED_BOOLEAN));
-    allBoxed.connectIntValue(mqttUri(TOPIC_BOXED_INTEGER));
-    allBoxed.connectShortValue(mqttUri(TOPIC_BOXED_SHORT));
-    allBoxed.connectLongValue(mqttUri(TOPIC_BOXED_LONG));
-    allBoxed.connectFloatValue(mqttUri(TOPIC_BOXED_FLOAT));
-    allBoxed.connectDoubleValue(mqttUri(TOPIC_BOXED_DOUBLE));
-    allBoxed.connectCharValue(mqttUri(TOPIC_BOXED_CHARACTER));
-
-    allBoxed.connectBooleanValueTransformed(mqttUri(TOPIC_BOXED_BOOLEAN));
-    allBoxed.connectIntValueTransformed(mqttUri(TOPIC_BOXED_INTEGER));
-    allBoxed.connectShortValueTransformed(mqttUri(TOPIC_BOXED_SHORT));
-    allBoxed.connectLongValueTransformed(mqttUri(TOPIC_BOXED_LONG));
-    allBoxed.connectFloatValueTransformed(mqttUri(TOPIC_BOXED_FLOAT));
-    allBoxed.connectDoubleValueTransformed(mqttUri(TOPIC_BOXED_DOUBLE));
-    allBoxed.connectCharValueTransformed(mqttUri(TOPIC_BOXED_CHARACTER));
+    assertTrue(integers.connectBooleanValue(mqttUri(TOPIC_NATIVE_BOOLEAN)));
+    assertTrue(integers.connectIntValue(mqttUri(TOPIC_NATIVE_INT)));
+    assertTrue(integers.connectShortValue(mqttUri(TOPIC_NATIVE_SHORT)));
+    assertTrue(integers.connectLongValue(mqttUri(TOPIC_NATIVE_LONG)));
+    assertTrue(floats.connectFloatValue(mqttUri(TOPIC_NATIVE_FLOAT)));
+    assertTrue(floats.connectDoubleValue(mqttUri(TOPIC_NATIVE_DOUBLE)));
+    assertTrue(chars.connectCharValue(mqttUri(TOPIC_NATIVE_CHAR)));
+    assertTrue(chars.connectStringValue(mqttUri(TOPIC_NATIVE_STRING)));
+
+    assertTrue(integers.connectBooleanValueTransformed(mqttUri(TOPIC_NATIVE_BOOLEAN)));
+    assertTrue(integers.connectIntValueTransformed(mqttUri(TOPIC_NATIVE_INT)));
+    assertTrue(integers.connectShortValueTransformed(mqttUri(TOPIC_NATIVE_SHORT)));
+    assertTrue(integers.connectLongValueTransformed(mqttUri(TOPIC_NATIVE_LONG)));
+    assertTrue(floats.connectFloatValueTransformed(mqttUri(TOPIC_NATIVE_FLOAT)));
+    assertTrue(floats.connectDoubleValueTransformed(mqttUri(TOPIC_NATIVE_DOUBLE)));
+    assertTrue(chars.connectCharValueTransformed(mqttUri(TOPIC_NATIVE_CHAR)));
+    assertTrue(chars.connectStringValueTransformed(mqttUri(TOPIC_NATIVE_STRING)));
+
+    assertTrue(allBoxed.connectBooleanValue(mqttUri(TOPIC_BOXED_BOOLEAN)));
+    assertTrue(allBoxed.connectIntValue(mqttUri(TOPIC_BOXED_INTEGER)));
+    assertTrue(allBoxed.connectShortValue(mqttUri(TOPIC_BOXED_SHORT)));
+    assertTrue(allBoxed.connectLongValue(mqttUri(TOPIC_BOXED_LONG)));
+    assertTrue(allBoxed.connectFloatValue(mqttUri(TOPIC_BOXED_FLOAT)));
+    assertTrue(allBoxed.connectDoubleValue(mqttUri(TOPIC_BOXED_DOUBLE)));
+    assertTrue(allBoxed.connectCharValue(mqttUri(TOPIC_BOXED_CHARACTER)));
+
+    assertTrue(allBoxed.connectBooleanValueTransformed(mqttUri(TOPIC_BOXED_BOOLEAN)));
+    assertTrue(allBoxed.connectIntValueTransformed(mqttUri(TOPIC_BOXED_INTEGER)));
+    assertTrue(allBoxed.connectShortValueTransformed(mqttUri(TOPIC_BOXED_SHORT)));
+    assertTrue(allBoxed.connectLongValueTransformed(mqttUri(TOPIC_BOXED_LONG)));
+    assertTrue(allBoxed.connectFloatValueTransformed(mqttUri(TOPIC_BOXED_FLOAT)));
+    assertTrue(allBoxed.connectDoubleValueTransformed(mqttUri(TOPIC_BOXED_DOUBLE)));
+    assertTrue(allBoxed.connectCharValueTransformed(mqttUri(TOPIC_BOXED_CHARACTER)));
   }
 
   @Override
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/DefaultOnlyWriteTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/DefaultOnlyWriteTest.java
index c18b185503053ec03428f620ea1aaa5df2505354..d8ce0c083148b671590c6129d16f8d3ed071e313 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/DefaultOnlyWriteTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/DefaultOnlyWriteTest.java
@@ -137,39 +137,39 @@ public class DefaultOnlyWriteTest extends AbstractMqttTest {
     dataNormal = createReceiver(false);
     dataTransformed = createReceiver(true);
 
-    nativeIntegers.connectBooleanValue(mqttUri(TOPIC_NATIVE_BOOLEAN), writeCurrentValue);
-    nativeIntegers.connectIntValue(mqttUri(TOPIC_NATIVE_INT), writeCurrentValue);
-    nativeIntegers.connectShortValue(mqttUri(TOPIC_NATIVE_SHORT), writeCurrentValue);
-    nativeIntegers.connectLongValue(mqttUri(TOPIC_NATIVE_LONG), writeCurrentValue);
-    nativeFloats.connectFloatValue(mqttUri(TOPIC_NATIVE_FLOAT), writeCurrentValue);
-    nativeFloats.connectDoubleValue(mqttUri(TOPIC_NATIVE_DOUBLE), writeCurrentValue);
-    nativeChars.connectCharValue(mqttUri(TOPIC_NATIVE_CHAR), writeCurrentValue);
-    nativeChars.connectStringValue(mqttUri(TOPIC_NATIVE_STRING), writeCurrentValue);
-
-    nativeIntegers.connectBooleanValueTransformed(mqttUri(TOPIC_NATIVE_BOOLEAN_TRANSFORMED), writeCurrentValue);
-    nativeIntegers.connectIntValueTransformed(mqttUri(TOPIC_NATIVE_INT_TRANSFORMED), writeCurrentValue);
-    nativeIntegers.connectShortValueTransformed(mqttUri(TOPIC_NATIVE_SHORT_TRANSFORMED), writeCurrentValue);
-    nativeIntegers.connectLongValueTransformed(mqttUri(TOPIC_NATIVE_LONG_TRANSFORMED), writeCurrentValue);
-    nativeFloats.connectFloatValueTransformed(mqttUri(TOPIC_NATIVE_FLOAT_TRANSFORMED), writeCurrentValue);
-    nativeFloats.connectDoubleValueTransformed(mqttUri(TOPIC_NATIVE_DOUBLE_TRANSFORMED), writeCurrentValue);
-    nativeChars.connectCharValueTransformed(mqttUri(TOPIC_NATIVE_CHAR_TRANSFORMED), writeCurrentValue);
-    nativeChars.connectStringValueTransformed(mqttUri(TOPIC_NATIVE_STRING_TRANSFORMED), writeCurrentValue);
-
-    boxedIntegers.connectBooleanValue(mqttUri(TOPIC_BOXED_BOOLEAN), writeCurrentValue);
-    boxedIntegers.connectIntValue(mqttUri(TOPIC_BOXED_INTEGER), writeCurrentValue);
-    boxedIntegers.connectShortValue(mqttUri(TOPIC_BOXED_SHORT), writeCurrentValue);
-    boxedIntegers.connectLongValue(mqttUri(TOPIC_BOXED_LONG), writeCurrentValue);
-    boxedFloats.connectFloatValue(mqttUri(TOPIC_BOXED_FLOAT), writeCurrentValue);
-    boxedFloats.connectDoubleValue(mqttUri(TOPIC_BOXED_DOUBLE), writeCurrentValue);
-    boxedChars.connectCharValue(mqttUri(TOPIC_BOXED_CHARACTER), writeCurrentValue);
-
-    boxedIntegers.connectBooleanValueTransformed(mqttUri(TOPIC_BOXED_BOOLEAN_TRANSFORMED), writeCurrentValue);
-    boxedIntegers.connectIntValueTransformed(mqttUri(TOPIC_BOXED_INTEGER_TRANSFORMED), writeCurrentValue);
-    boxedIntegers.connectShortValueTransformed(mqttUri(TOPIC_BOXED_SHORT_TRANSFORMED), writeCurrentValue);
-    boxedIntegers.connectLongValueTransformed(mqttUri(TOPIC_BOXED_LONG_TRANSFORMED), writeCurrentValue);
-    boxedFloats.connectFloatValueTransformed(mqttUri(TOPIC_BOXED_FLOAT_TRANSFORMED), writeCurrentValue);
-    boxedFloats.connectDoubleValueTransformed(mqttUri(TOPIC_BOXED_DOUBLE_TRANSFORMED), writeCurrentValue);
-    boxedChars.connectCharValueTransformed(mqttUri(TOPIC_BOXED_CHARACTER_TRANSFORMED), writeCurrentValue);
+    assertTrue(nativeIntegers.connectBooleanValue(mqttUri(TOPIC_NATIVE_BOOLEAN), writeCurrentValue));
+    assertTrue(nativeIntegers.connectIntValue(mqttUri(TOPIC_NATIVE_INT), writeCurrentValue));
+    assertTrue(nativeIntegers.connectShortValue(mqttUri(TOPIC_NATIVE_SHORT), writeCurrentValue));
+    assertTrue(nativeIntegers.connectLongValue(mqttUri(TOPIC_NATIVE_LONG), writeCurrentValue));
+    assertTrue(nativeFloats.connectFloatValue(mqttUri(TOPIC_NATIVE_FLOAT), writeCurrentValue));
+    assertTrue(nativeFloats.connectDoubleValue(mqttUri(TOPIC_NATIVE_DOUBLE), writeCurrentValue));
+    assertTrue(nativeChars.connectCharValue(mqttUri(TOPIC_NATIVE_CHAR), writeCurrentValue));
+    assertTrue(nativeChars.connectStringValue(mqttUri(TOPIC_NATIVE_STRING), writeCurrentValue));
+
+    assertTrue(nativeIntegers.connectBooleanValueTransformed(mqttUri(TOPIC_NATIVE_BOOLEAN_TRANSFORMED), writeCurrentValue));
+    assertTrue(nativeIntegers.connectIntValueTransformed(mqttUri(TOPIC_NATIVE_INT_TRANSFORMED), writeCurrentValue));
+    assertTrue(nativeIntegers.connectShortValueTransformed(mqttUri(TOPIC_NATIVE_SHORT_TRANSFORMED), writeCurrentValue));
+    assertTrue(nativeIntegers.connectLongValueTransformed(mqttUri(TOPIC_NATIVE_LONG_TRANSFORMED), writeCurrentValue));
+    assertTrue(nativeFloats.connectFloatValueTransformed(mqttUri(TOPIC_NATIVE_FLOAT_TRANSFORMED), writeCurrentValue));
+    assertTrue(nativeFloats.connectDoubleValueTransformed(mqttUri(TOPIC_NATIVE_DOUBLE_TRANSFORMED), writeCurrentValue));
+    assertTrue(nativeChars.connectCharValueTransformed(mqttUri(TOPIC_NATIVE_CHAR_TRANSFORMED), writeCurrentValue));
+    assertTrue(nativeChars.connectStringValueTransformed(mqttUri(TOPIC_NATIVE_STRING_TRANSFORMED), writeCurrentValue));
+
+    assertTrue(boxedIntegers.connectBooleanValue(mqttUri(TOPIC_BOXED_BOOLEAN), writeCurrentValue));
+    assertTrue(boxedIntegers.connectIntValue(mqttUri(TOPIC_BOXED_INTEGER), writeCurrentValue));
+    assertTrue(boxedIntegers.connectShortValue(mqttUri(TOPIC_BOXED_SHORT), writeCurrentValue));
+    assertTrue(boxedIntegers.connectLongValue(mqttUri(TOPIC_BOXED_LONG), writeCurrentValue));
+    assertTrue(boxedFloats.connectFloatValue(mqttUri(TOPIC_BOXED_FLOAT), writeCurrentValue));
+    assertTrue(boxedFloats.connectDoubleValue(mqttUri(TOPIC_BOXED_DOUBLE), writeCurrentValue));
+    assertTrue(boxedChars.connectCharValue(mqttUri(TOPIC_BOXED_CHARACTER), writeCurrentValue));
+
+    assertTrue(boxedIntegers.connectBooleanValueTransformed(mqttUri(TOPIC_BOXED_BOOLEAN_TRANSFORMED), writeCurrentValue));
+    assertTrue(boxedIntegers.connectIntValueTransformed(mqttUri(TOPIC_BOXED_INTEGER_TRANSFORMED), writeCurrentValue));
+    assertTrue(boxedIntegers.connectShortValueTransformed(mqttUri(TOPIC_BOXED_SHORT_TRANSFORMED), writeCurrentValue));
+    assertTrue(boxedIntegers.connectLongValueTransformed(mqttUri(TOPIC_BOXED_LONG_TRANSFORMED), writeCurrentValue));
+    assertTrue(boxedFloats.connectFloatValueTransformed(mqttUri(TOPIC_BOXED_FLOAT_TRANSFORMED), writeCurrentValue));
+    assertTrue(boxedFloats.connectDoubleValueTransformed(mqttUri(TOPIC_BOXED_DOUBLE_TRANSFORMED), writeCurrentValue));
+    assertTrue(boxedChars.connectCharValueTransformed(mqttUri(TOPIC_BOXED_CHARACTER_TRANSFORMED), writeCurrentValue));
   }
 
   private ReceiverData createReceiver(boolean transformed) {
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Errors.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Errors.java
index cc0596482e7559166947febb30144118ab4dbe10..4d32adff23abd12f2052f320466b79dc0f8673f5 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Errors.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Errors.java
@@ -2,7 +2,6 @@ package org.jastadd.ragconnect.tests;
 
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
-import org.jastadd.ragconnect.compiler.Compiler;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
@@ -10,12 +9,14 @@ import org.junit.jupiter.api.Test;
 import java.io.File;
 import java.io.IOException;
 import java.nio.charset.Charset;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.stream.Collectors;
 
-import static org.jastadd.ragconnect.tests.TestUtils.exec;
+import static org.assertj.core.api.Assertions.assertThat;
 import static org.jastadd.ragconnect.tests.TestUtils.readFile;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
@@ -23,8 +24,10 @@ public class Errors {
 
   private static final Logger logger = LogManager.getLogger(Errors.class);
   private static final String FILENAME_PATTERN = "$FILENAME";
-  private static final String INPUT_DIRECTORY = "./src/test/01-input/errors/";
-  private static final String OUTPUT_DIRECTORY = "./src/test/02-after-ragconnect/errors/";
+  private static final String ERROR_DIRECTORY = "errors/";
+  private static final String OUTPUT_DIRECTORY = TestUtils.OUTPUT_DIRECTORY_PREFIX + ERROR_DIRECTORY;
+
+  private static final String DEFAULT_GRAMMAR_NAME = "Errors";
 
   @BeforeAll
   public static void createOutputDirectory() {
@@ -34,40 +37,31 @@ public class Errors {
 
   @Test
   void testStandardErrors() throws IOException {
-    test("Errors", "A");
+    test("Standard", "A","Standard");
+  }
+
+  @Test
+  void testTwoPartsErrors() throws IOException {
+    test("Part", "A","Part1", "Part2");
   }
 
   @SuppressWarnings("SameParameterValue")
-  private void test(String name, String rootNode) throws IOException {
-    String grammarFile = INPUT_DIRECTORY + name + ".relast";
-    String ragconnectFile = INPUT_DIRECTORY + name + ".connect";
-    String outFile = OUTPUT_DIRECTORY + name + ".out";
-    String expectedFile = INPUT_DIRECTORY + name + ".expected";
+  private void test(String expectedName, String rootNode, String... connectNames) throws IOException {
+    String grammarFile = ERROR_DIRECTORY + DEFAULT_GRAMMAR_NAME + ".relast";
+    List<String> connectFiles = Arrays.stream(connectNames)
+        .map(connectName -> ERROR_DIRECTORY + connectName + ".connect")
+        .collect(Collectors.toList());
+    Path outPath = TestUtils.runCompiler(grammarFile, connectFiles, rootNode, ERROR_DIRECTORY, 1);
 
-    try {
-      logger.debug("user.dir: {}", System.getProperty("user.dir"));
-      String[] args = {
-          "--o=" + OUTPUT_DIRECTORY,
-          grammarFile,
-          ragconnectFile,
-          "--rootNode=" + rootNode,
-          "--verbose"
-      };
-      int returnValue = exec(Compiler.class, args, new File(outFile));
-      Assertions.assertEquals(1, returnValue, "RagConnect did not return with value 1");
-    } catch (IOException | InterruptedException e) {
-      e.printStackTrace();
-    }
+    final String startOfErrorsPattern = "SEVERE: Errors:";
+    String out = readFile(outPath, Charset.defaultCharset());
+    assertThat(out).contains(startOfErrorsPattern);
+    out = out.substring(out.indexOf(startOfErrorsPattern) + 16);
 
-    String out = readFile(outFile, Charset.defaultCharset());
-    String expected = readFile(expectedFile, Charset.defaultCharset());
-//    if (inFiles.size() == 1) {
-      expected = expected.replace(FILENAME_PATTERN, name);
-//    } else {
-//      for (int i = 0; i < inFiles.size(); i++) {
-//        expected = expected.replace(FILENAME_PATTERN + (i + 1), inFiles.get(i));
-//      }
-//    }
+    Path expectedPath = Paths.get(TestUtils.INPUT_DIRECTORY_PREFIX)
+        .resolve(ERROR_DIRECTORY)
+        .resolve(expectedName + ".expected");
+    String expected = readFile(expectedPath, Charset.defaultCharset());
     List<String> outList = Arrays.asList(out.split("\n"));
     Collections.sort(outList);
     List<String> expectedList = Arrays.stream(expected.split("\n"))
@@ -75,10 +69,9 @@ public class Errors {
         .filter(s -> !s.isEmpty() && !s.startsWith("//"))
         .collect(Collectors.toList());
 
-    // FIXME errors not handled correctly at the moment
-//    Assertions.assertLinesMatch(expectedList, outList);
+    Assertions.assertLinesMatch(expectedList, outList);
 
-    logger.info("ragconnect for " + name + " returned:\n{}", out);
+    logger.info("ragconnect for " + expectedName + " returned:\n{}", out);
   }
 
 }
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ExampleTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ExampleTest.java
index 17bc074545fd2815c750d957dd1038586c5ff018..5ad824bcd8de42d998d7825c6930892ecc57e37d 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ExampleTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ExampleTest.java
@@ -97,9 +97,9 @@ public class ExampleTest extends AbstractMqttTest {
       }
     });
 
-    robotArm.connectAppropriateSpeed(mqttUri(TOPIC_CONFIG), writeCurrentValue);
-    link1.connectCurrentPosition(mqttUri(TOPIC_JOINT1));
-    link2.connectCurrentPosition(mqttUri(TOPIC_JOINT2));
+    assertTrue(robotArm.connectAppropriateSpeed(mqttUri(TOPIC_CONFIG), writeCurrentValue));
+    assertTrue(link1.connectCurrentPosition(mqttUri(TOPIC_JOINT1)));
+    assertTrue(link2.connectCurrentPosition(mqttUri(TOPIC_JOINT2)));
   }
 
   @Override
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/IncrementalDependencyTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/IncrementalDependencyTest.java
index 43fe8b349e2f10568238042276faf2d43c74271d..580c4fc81a2952f0042eb273ffcb9be35b0a3df5 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/IncrementalDependencyTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/IncrementalDependencyTest.java
@@ -3,6 +3,7 @@ package org.jastadd.ragconnect.tests;
 import incremental.ast.A;
 import incremental.ast.B;
 import incremental.ast.MqttHandler;
+import org.junit.jupiter.api.Tag;
 
 import java.io.IOException;
 import java.util.concurrent.TimeUnit;
@@ -16,6 +17,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
  *
  * @author rschoene - Initial contribution
  */
+@Tag("Incremental")
 public class IncrementalDependencyTest extends AbstractMqttTest {
 
   private static final String TOPIC_IN = "in/a";
@@ -71,10 +73,10 @@ public class IncrementalDependencyTest extends AbstractMqttTest {
       dataB2.lastStringValue = TestUtils.DefaultMappings.BytesToString(bytes);
     });
 
-    model.connectInput(mqttUri(TOPIC_IN));
-    model.connectOutputOnA(mqttUri(TOPIC_OUT_A), writeCurrentValue);
-    b1.connectOutputOnB(mqttUri(TOPIC_OUT_B1), writeCurrentValue);
-    b2.connectOutputOnB(mqttUri(TOPIC_OUT_B2), writeCurrentValue);
+    assertTrue(model.connectInput(mqttUri(TOPIC_IN)));
+    assertTrue(model.connectOutputOnA(mqttUri(TOPIC_OUT_A), writeCurrentValue));
+    assertTrue(b1.connectOutputOnB(mqttUri(TOPIC_OUT_B1), writeCurrentValue));
+    assertTrue(b2.connectOutputOnB(mqttUri(TOPIC_OUT_B2), writeCurrentValue));
   }
 
   @Override
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/MappingTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/MappingTest.java
index 7f83e3387688f578e47f6ac67c14e5f7fb26a688..52d750223efb102f73e5f63d83aaf8c85ae6847f 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/MappingTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/MappingTest.java
@@ -89,29 +89,29 @@ public class MappingTest extends AbstractMqttTest {
       data.lastNativeBooleanValue = TestUtils.DefaultMappings.BytesToBool(bytes);
     });
 
-    natives.connectWriteIntValue(mqttUri(TOPIC_WRITE_NATIVE_INT), writeCurrentValue);
-    natives.connectWriteShortValue(mqttUri(TOPIC_WRITE_NATIVE_SHORT), writeCurrentValue);
-    natives.connectWriteLongValue(mqttUri(TOPIC_WRITE_NATIVE_LONG), writeCurrentValue);
-    natives.connectWriteFloatValue(mqttUri(TOPIC_WRITE_NATIVE_FLOAT), writeCurrentValue);
-    natives.connectWriteDoubleValue(mqttUri(TOPIC_WRITE_NATIVE_DOUBLE), writeCurrentValue);
-    natives.connectWriteCharValue(mqttUri(TOPIC_WRITE_NATIVE_CHAR), writeCurrentValue);
-    natives.connectWriteBooleanValue(mqttUri(TOPIC_WRITE_NATIVE_BOOLEAN), writeCurrentValue);
-
-    natives.connectIntValue(mqttUri(TOPIC_INPUT));
-    natives.connectShortValue(mqttUri(TOPIC_INPUT));
-    natives.connectLongValue(mqttUri(TOPIC_INPUT));
-    natives.connectFloatValue(mqttUri(TOPIC_INPUT));
-    natives.connectDoubleValue(mqttUri(TOPIC_INPUT));
-    natives.connectCharValue(mqttUri(TOPIC_INPUT));
-    natives.connectBooleanValue(mqttUri(TOPIC_INPUT));
-
-    boxes.connectIntValue(mqttUri(TOPIC_INPUT));
-    boxes.connectShortValue(mqttUri(TOPIC_INPUT));
-    boxes.connectLongValue(mqttUri(TOPIC_INPUT));
-    boxes.connectFloatValue(mqttUri(TOPIC_INPUT));
-    boxes.connectDoubleValue(mqttUri(TOPIC_INPUT));
-    boxes.connectCharValue(mqttUri(TOPIC_INPUT));
-    boxes.connectBooleanValue(mqttUri(TOPIC_INPUT));
+    assertTrue(natives.connectWriteIntValue(mqttUri(TOPIC_WRITE_NATIVE_INT), writeCurrentValue));
+    assertTrue(natives.connectWriteShortValue(mqttUri(TOPIC_WRITE_NATIVE_SHORT), writeCurrentValue));
+    assertTrue(natives.connectWriteLongValue(mqttUri(TOPIC_WRITE_NATIVE_LONG), writeCurrentValue));
+    assertTrue(natives.connectWriteFloatValue(mqttUri(TOPIC_WRITE_NATIVE_FLOAT), writeCurrentValue));
+    assertTrue(natives.connectWriteDoubleValue(mqttUri(TOPIC_WRITE_NATIVE_DOUBLE), writeCurrentValue));
+    assertTrue(natives.connectWriteCharValue(mqttUri(TOPIC_WRITE_NATIVE_CHAR), writeCurrentValue));
+    assertTrue(natives.connectWriteBooleanValue(mqttUri(TOPIC_WRITE_NATIVE_BOOLEAN), writeCurrentValue));
+
+    assertTrue(natives.connectIntValue(mqttUri(TOPIC_INPUT)));
+    assertTrue(natives.connectShortValue(mqttUri(TOPIC_INPUT)));
+    assertTrue(natives.connectLongValue(mqttUri(TOPIC_INPUT)));
+    assertTrue(natives.connectFloatValue(mqttUri(TOPIC_INPUT)));
+    assertTrue(natives.connectDoubleValue(mqttUri(TOPIC_INPUT)));
+    assertTrue(natives.connectCharValue(mqttUri(TOPIC_INPUT)));
+    assertTrue(natives.connectBooleanValue(mqttUri(TOPIC_INPUT)));
+
+    assertTrue(boxes.connectIntValue(mqttUri(TOPIC_INPUT)));
+    assertTrue(boxes.connectShortValue(mqttUri(TOPIC_INPUT)));
+    assertTrue(boxes.connectLongValue(mqttUri(TOPIC_INPUT)));
+    assertTrue(boxes.connectFloatValue(mqttUri(TOPIC_INPUT)));
+    assertTrue(boxes.connectDoubleValue(mqttUri(TOPIC_INPUT)));
+    assertTrue(boxes.connectCharValue(mqttUri(TOPIC_INPUT)));
+    assertTrue(boxes.connectBooleanValue(mqttUri(TOPIC_INPUT)));
   }
 
   @Override
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/MqttHandlerTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/MqttHandlerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..091990a64a31c01a6f2c59b934cfe15b6fb76ece
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/MqttHandlerTest.java
@@ -0,0 +1,72 @@
+package org.jastadd.ragconnect.tests;
+
+import example.ast.MqttHandler;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.fail;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Testing the {@link MqttHandler} used in the "example" test case.
+ *
+ * @author rschoene - Initial contribution
+ */
+@Tag("mqtt")
+public class MqttHandlerTest {
+
+  @Test
+  public void defaultBehaviour() {
+    MqttHandler handler = new MqttHandler();
+    try {
+      handler.setHost(TestUtils.getMqttHost());
+    } catch (IOException e) {
+      fail("Fail during setHost", e);
+    }
+    boolean ready = handler.waitUntilReady(2, TimeUnit.SECONDS);
+    assertTrue(ready);
+    handler.close();
+  }
+
+  @Test
+  public void testWelcomeMessage() throws Exception {
+    MqttHandler welcomeMessageSubscriber = new MqttHandler();
+    List<String> receivedMessages = new ArrayList<>();
+    welcomeMessageSubscriber.setHost(TestUtils.getMqttHost());
+    assertTrue(welcomeMessageSubscriber.waitUntilReady(2, TimeUnit.SECONDS));
+    welcomeMessageSubscriber.newConnection("components", bytes -> receivedMessages.add(new String(bytes)));
+    assertThat(receivedMessages).isEmpty();
+
+    MqttHandler handler = new MqttHandler();
+    handler.setHost(TestUtils.getMqttHost());
+    assertTrue(handler.waitUntilReady(2, TimeUnit.SECONDS));
+    TestUtils.waitForMqtt();
+
+    assertEquals(1, receivedMessages.size());
+  }
+
+  @Test
+  public void testDontSendWelcomeMessage() throws Exception {
+    MqttHandler welcomeMessageSubscriber = new MqttHandler();
+    List<String> receivedMessages = new ArrayList<>();
+    welcomeMessageSubscriber.setHost(TestUtils.getMqttHost());
+    assertTrue(welcomeMessageSubscriber.waitUntilReady(2, TimeUnit.SECONDS));
+    welcomeMessageSubscriber.newConnection("components", bytes -> receivedMessages.add(new String(bytes)));
+    assertThat(receivedMessages).isEmpty();
+
+    MqttHandler handler = new MqttHandler().dontSendWelcomeMessage();
+    handler.setHost(TestUtils.getMqttHost());
+    assertTrue(handler.waitUntilReady(2, TimeUnit.SECONDS));
+    TestUtils.waitForMqtt();
+
+    assertThat(receivedMessages).isEmpty();
+  }
+
+}
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Read1Write2Test.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Read1Write2Test.java
index 889ce0531a6b61196856f40c5045920517b8dfd3..76eac958448c69e0ac1158074a4ddac12f4027f9 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Read1Write2Test.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Read1Write2Test.java
@@ -100,15 +100,15 @@ public class Read1Write2Test extends AbstractMqttTest {
       dataOther2.lastStringValue = TestUtils.DefaultMappings.BytesToString(bytes);
     });
 
-    onSameNonterminal.connectInput(mqttUri(TOPIC_SAME_READ));
-    onSameNonterminal.connectOutInteger(mqttUri(TOPIC_SAME_WRITE_INT), writeCurrentValue);
-    onSameNonterminal.connectOutString(mqttUri(TOPIC_SAME_WRITE_STRING), writeCurrentValue);
-
-    onDifferentNonterminal.connectInput(mqttUri(TOPIC_DIFFERENT_READ));
-    other1.connectOutInteger(mqttUri(TOPIC_DIFFERENT_WRITE1_INT), writeCurrentValue);
-    other1.connectOutString(mqttUri(TOPIC_DIFFERENT_WRITE1_STRING), writeCurrentValue);
-    other2.connectOutInteger(mqttUri(TOPIC_DIFFERENT_WRITE2_INT), writeCurrentValue);
-    other2.connectOutString(mqttUri(TOPIC_DIFFERENT_WRITE2_STRING), writeCurrentValue);
+    assertTrue(onSameNonterminal.connectInput(mqttUri(TOPIC_SAME_READ)));
+    assertTrue(onSameNonterminal.connectOutInteger(mqttUri(TOPIC_SAME_WRITE_INT), writeCurrentValue));
+    assertTrue(onSameNonterminal.connectOutString(mqttUri(TOPIC_SAME_WRITE_STRING), writeCurrentValue));
+
+    assertTrue(onDifferentNonterminal.connectInput(mqttUri(TOPIC_DIFFERENT_READ)));
+    assertTrue(other1.connectOutInteger(mqttUri(TOPIC_DIFFERENT_WRITE1_INT), writeCurrentValue));
+    assertTrue(other1.connectOutString(mqttUri(TOPIC_DIFFERENT_WRITE1_STRING), writeCurrentValue));
+    assertTrue(other2.connectOutInteger(mqttUri(TOPIC_DIFFERENT_WRITE2_INT), writeCurrentValue));
+    assertTrue(other2.connectOutString(mqttUri(TOPIC_DIFFERENT_WRITE2_STRING), writeCurrentValue));
   }
 
   @Override
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Read2Write1Test.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Read2Write1Test.java
index f0f634c8e031bcadac54b09dd644d34c1f2a8dff..f6a0d33a4b70689a1c883f866d33442defd2b786 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Read2Write1Test.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Read2Write1Test.java
@@ -89,14 +89,14 @@ public class Read2Write1Test extends AbstractMqttTest {
       dataOther2.lastIntValue = TestUtils.DefaultMappings.BytesToInt(bytes);
     });
 
-    onSameNonterminal.connectInput1(mqttUri(TOPIC_SAME_READ1));
-    onSameNonterminal.connectInput2(mqttUri(TOPIC_SAME_READ2));
-    onSameNonterminal.connectOutInteger(mqttUri(TOPIC_SAME_WRITE_INT), writeCurrentValue);
-
-    onDifferentNonterminal.connectInput1(mqttUri(TOPIC_DIFFERENT_READ1));
-    onDifferentNonterminal.connectInput2(mqttUri(TOPIC_DIFFERENT_READ2));
-    other1.connectOutInteger(mqttUri(TOPIC_DIFFERENT_WRITE1_INT), writeCurrentValue);
-    other2.connectOutInteger(mqttUri(TOPIC_DIFFERENT_WRITE2_INT), writeCurrentValue);
+    assertTrue(onSameNonterminal.connectInput1(mqttUri(TOPIC_SAME_READ1)));
+    assertTrue(onSameNonterminal.connectInput2(mqttUri(TOPIC_SAME_READ2)));
+    assertTrue(onSameNonterminal.connectOutInteger(mqttUri(TOPIC_SAME_WRITE_INT), writeCurrentValue));
+
+    assertTrue(onDifferentNonterminal.connectInput1(mqttUri(TOPIC_DIFFERENT_READ1)));
+    assertTrue(onDifferentNonterminal.connectInput2(mqttUri(TOPIC_DIFFERENT_READ2)));
+    assertTrue(other1.connectOutInteger(mqttUri(TOPIC_DIFFERENT_WRITE1_INT), writeCurrentValue));
+    assertTrue(other2.connectOutInteger(mqttUri(TOPIC_DIFFERENT_WRITE2_INT), writeCurrentValue));
   }
 
   @Override
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/RegressionTests.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/RegressionTests.java
new file mode 100644
index 0000000000000000000000000000000000000000..67c82177d96fe34c6379b9d62537f785292827df
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/RegressionTests.java
@@ -0,0 +1,65 @@
+package org.jastadd.ragconnect.tests;
+
+import org.junit.jupiter.api.Test;
+import via.ast.A;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.util.Collections;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Regression tests for fixed issues.
+ *
+ * @author rschoene - Initial contribution
+ */
+public class RegressionTests {
+
+  private static final String REGRESSION_TEST_OUTPUT_DIRECTORY = "regression-test/";
+
+  @Test
+  public void issue22() {
+    // use model of "via" test case as it uses both mqtt and rest as protocols
+    A a = new A();
+    try {
+      // should fail because of missing scheme
+      assertFalse(a.connectBoth2BothInput("missing/scheme"));
+
+      // should fail because of missing host
+      assertFalse(a.connectBoth2BothInput("mqtt://"));
+
+      // should fail because of missing part
+      assertFalse(a.connectBoth2BothInput("mqtt://localhost"));
+
+      // should fail because of unknown scheme
+      assertFalse(a.connectBoth2BothInput("badScheme://host/some/topic"));
+    } catch (IOException e) {
+      fail(e);
+    }
+  }
+
+  @Test
+  public void issue27() throws IOException {
+    String grammarFile = "regression-tests/issue27/Test.relast";
+    String connectFile = "regression-tests/issue27/Test.connect";
+    grammarFile = ensureNoTrailingNewLine(grammarFile);
+    connectFile = ensureNoTrailingNewLine(connectFile);
+    TestUtils.runCompiler(grammarFile, Collections.singletonList(connectFile), "A", REGRESSION_TEST_OUTPUT_DIRECTORY, 0);
+  }
+
+  private String ensureNoTrailingNewLine(String inputFileSuffix) throws IOException {
+    int dotIndex = inputFileSuffix.lastIndexOf('.');
+    String outFileSuffix = inputFileSuffix.substring(0, dotIndex) + ".noNewLine" + inputFileSuffix.substring(dotIndex);
+    Path inputPath = Paths.get(TestUtils.INPUT_DIRECTORY_PREFIX).resolve(inputFileSuffix);
+    Path outputPath = Paths.get(TestUtils.INPUT_DIRECTORY_PREFIX).resolve(outFileSuffix);
+
+    String content = Files.readString(inputPath);
+    Files.writeString(outputPath, content.stripTrailing(), StandardOpenOption.CREATE);
+
+    return outFileSuffix;
+  }
+}
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TestUtils.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TestUtils.java
index 776e7a01b5f2c683c09cb2dfd3b3e496f41110ad..c09059c8a66ca1cba262843ce30d4ce8ad0bc663 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TestUtils.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TestUtils.java
@@ -1,13 +1,22 @@
 package org.jastadd.ragconnect.tests;
 
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.jastadd.ragconnect.compiler.Compiler;
+import org.junit.jupiter.api.Assertions;
+
 import java.io.File;
 import java.io.IOException;
 import java.nio.charset.Charset;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.TimeUnit;
 
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
 
 /**
@@ -17,7 +26,10 @@ import static org.junit.jupiter.api.Assertions.fail;
  */
 public class TestUtils {
 
+  private static final Logger logger = LogManager.getLogger(TestUtils.class);
   public static final double DELTA = 0.001d;
+  public static final String INPUT_DIRECTORY_PREFIX = "./src/test/01-input/";
+  public static final String OUTPUT_DIRECTORY_PREFIX = "./src/test/02-after-ragconnect/";
 
   public static String getMqttHost() {
     if (System.getenv("GITLAB_CI") != null) {
@@ -41,6 +53,41 @@ public class TestUtils {
     return 1883;
   }
 
+  public static Path runCompiler(String grammarFile, Iterable<String> connectFiles, String rootNode, String outputDirectory, int expectedReturnValue) {
+
+    assertThat(connectFiles).isNotEmpty();
+
+    Path outPath = Paths.get(OUTPUT_DIRECTORY_PREFIX)
+        .resolve(outputDirectory)
+        .resolve("Compiler.out");
+    ensureCreated(outPath.getParent());
+
+    try {
+      logger.debug("user.dir: {}", System.getProperty("user.dir"));
+      List<String> args = new ArrayList<>() {{
+        add("--o=" + OUTPUT_DIRECTORY_PREFIX + outputDirectory);
+        add("--rootNode=" + rootNode);
+        add("--verbose");
+        add(INPUT_DIRECTORY_PREFIX + grammarFile);
+      }};
+      connectFiles.forEach(connectFile -> args.add(INPUT_DIRECTORY_PREFIX + connectFile));
+
+      int returnValue = exec(Compiler.class, args.toArray(new String[0]), outPath.toFile());
+      Assertions.assertEquals(expectedReturnValue, returnValue, "RagConnect did not return with value " + expectedReturnValue);
+    } catch (IOException | InterruptedException e) {
+      fail(e);
+    }
+    return outPath;
+  }
+
+  private static void ensureCreated(Path directory) {
+    File directoryFile = directory.toFile();
+    if (directoryFile.exists() && directoryFile.isDirectory()) {
+      return;
+    }
+    assertTrue(directoryFile.mkdirs());
+  }
+
   public static int exec(Class<?> klass, String[] args, File err) throws IOException,
       InterruptedException {
     String javaHome = System.getProperty("java.home");
@@ -79,14 +126,14 @@ public class TestUtils {
     }
   }
 
-  public static String readFile(String path, Charset encoding)
+  public static String readFile(Path path, Charset encoding)
       throws IOException {
-    byte[] encoded = Files.readAllBytes(Paths.get(path));
+    byte[] encoded = Files.readAllBytes(path);
     return new String(encoded, encoding);
   }
 
   static void waitForMqtt() throws InterruptedException {
-    TimeUnit.SECONDS.sleep(2);
+    TimeUnit.MILLISECONDS.sleep(1500);
   }
 
   @SuppressWarnings({"unused", "rawtypes"})
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TokenValueSendTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TokenValueSendTest.java
index b22a2fb6ac2d4fcfe5c78332f64a5ec6025613d9..6d99149688b33d8401037fb358f736c2509b0921 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TokenValueSendTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TokenValueSendTest.java
@@ -87,12 +87,12 @@ public class TokenValueSendTest extends AbstractMqttTest {
       dataThreeOther.lastStringValue = TestUtils.DefaultMappings.BytesToString(bytes);
     });
 
-    one.connectValue(mqttUri(TOPIC_SEND_ONE), writeCurrentValue);
-    two.connectValue(mqttUri(TOPIC_RECEIVE_TWO));
-    two.connectValue(mqttUri(TOPIC_SEND_TWO), writeCurrentValue);
-    three.connectValue(mqttUri(TOPIC_RECEIVE_THREE_VALUE));
-    three.connectValue(mqttUri(TOPIC_SEND_THREE_VALUE), writeCurrentValue);
-    three.connectOtherOutput(mqttUri(TOPIC_SEND_THREE_OTHER), writeCurrentValue);
+    assertTrue(one.connectValue(mqttUri(TOPIC_SEND_ONE), writeCurrentValue));
+    assertTrue(two.connectValue(mqttUri(TOPIC_RECEIVE_TWO)));
+    assertTrue(two.connectValue(mqttUri(TOPIC_SEND_TWO), writeCurrentValue));
+    assertTrue(three.connectValue(mqttUri(TOPIC_RECEIVE_THREE_VALUE)));
+    assertTrue(three.connectValue(mqttUri(TOPIC_SEND_THREE_VALUE), writeCurrentValue));
+    assertTrue(three.connectOtherOutput(mqttUri(TOPIC_SEND_THREE_OTHER), writeCurrentValue));
   }
 
   @Override
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TreeAllowedTokensIncrementalTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TreeAllowedTokensIncrementalTest.java
index 5dd1b1504701b0c5499e7277b8287e1b66a9e5a5..0edfbd6bee0b412158a799ef388901e62d398495 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TreeAllowedTokensIncrementalTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TreeAllowedTokensIncrementalTest.java
@@ -18,7 +18,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
  *
  * @author rschoene - Initial contribution
  */
-@Tag("SpecificTest")
+@Tag("Incremental")
+@Tag("Tree")
 public class TreeAllowedTokensIncrementalTest extends AbstractTreeAllowedTokensTest {
 
   private Root model;
@@ -42,6 +43,8 @@ public class TreeAllowedTokensIncrementalTest extends AbstractTreeAllowedTokensT
 
     receiverRoot = new ReceiverRoot();
     model.addReceiverRoot((ReceiverRoot) receiverRoot);
+
+    model.ragconnectCheckIncremental();
   }
 
   @Override
@@ -58,14 +61,14 @@ public class TreeAllowedTokensIncrementalTest extends AbstractTreeAllowedTokensT
     handler.newConnection(TOPIC_ALFA_PRIMITIVE, bytes -> data.numberOfPrimitiveTrees += 1);
 
     // connect. important: first receiver, then sender. to not miss initial value.
-    senderRoot.connectInput1WhenFlagIsFalse(mqttUri(TOPIC_INPUT1FALSE));
-    senderRoot.connectInput1WhenFlagIsTrue(mqttUri(TOPIC_INPUT1TRUE));
-    senderRoot.connectInput2(mqttUri(TOPIC_INPUT2));
-    senderRoot.connectInput3(mqttUri(TOPIC_INPUT3));
-    receiverRoot.connectAlfa(mqttUri(TOPIC_ALFA));
-    receiverRoot.connectAlfaPrimitive(mqttUri(TOPIC_ALFA_PRIMITIVE));
-    senderRoot.connectAlfa(mqttUri(TOPIC_ALFA), writeCurrentValue);
-    senderRoot.connectAlfaPrimitive(mqttUri(TOPIC_ALFA_PRIMITIVE), writeCurrentValue);
+    assertTrue(senderRoot.connectInput1WhenFlagIsFalse(mqttUri(TOPIC_INPUT1FALSE)));
+    assertTrue(senderRoot.connectInput1WhenFlagIsTrue(mqttUri(TOPIC_INPUT1TRUE)));
+    assertTrue(senderRoot.connectInput2(mqttUri(TOPIC_INPUT2)));
+    assertTrue(senderRoot.connectInput3(mqttUri(TOPIC_INPUT3)));
+    assertTrue(receiverRoot.connectAlfa(mqttUri(TOPIC_ALFA)));
+    assertTrue(receiverRoot.connectAlfaPrimitive(mqttUri(TOPIC_ALFA_PRIMITIVE)));
+    assertTrue(senderRoot.connectAlfa(mqttUri(TOPIC_ALFA), writeCurrentValue));
+    assertTrue(senderRoot.connectAlfaPrimitive(mqttUri(TOPIC_ALFA_PRIMITIVE), writeCurrentValue));
   }
 
   @Override
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TreeAllowedTokensManualTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TreeAllowedTokensManualTest.java
index c3b91a04f45a47250f2cdf557575c5c48ac09c05..9780d56daaa807a13082e9776fa45b1ec4eee5a3 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TreeAllowedTokensManualTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TreeAllowedTokensManualTest.java
@@ -1,5 +1,6 @@
 package org.jastadd.ragconnect.tests;
 
+import org.junit.jupiter.api.Tag;
 import org.junit.jupiter.api.Test;
 import treeAllowedTokens.ast.*;
 
@@ -18,6 +19,7 @@ import static org.junit.jupiter.api.Assertions.*;
  *
  * @author rschoene - Initial contribution
  */
+@Tag("Tree")
 public class TreeAllowedTokensManualTest extends AbstractTreeAllowedTokensTest {
 
   private Root model;
@@ -63,14 +65,14 @@ public class TreeAllowedTokensManualTest extends AbstractTreeAllowedTokensTest {
     handler.newConnection(TOPIC_ALFA_PRIMITIVE, bytes -> data.numberOfPrimitiveTrees += 1);
 
     // connect. important: first receiver, then sender. to not miss initial value.
-    senderRoot.connectInput1WhenFlagIsFalse(mqttUri(TOPIC_INPUT1FALSE));
-    senderRoot.connectInput1WhenFlagIsTrue(mqttUri(TOPIC_INPUT1TRUE));
-    senderRoot.connectInput2(mqttUri(TOPIC_INPUT2));
-    senderRoot.connectInput3(mqttUri(TOPIC_INPUT3));
-    receiverRoot.connectAlfa(mqttUri(TOPIC_ALFA));
-    receiverRoot.connectAlfaPrimitive(mqttUri(TOPIC_ALFA_PRIMITIVE));
-    senderRoot.connectAlfa(mqttUri(TOPIC_ALFA), writeCurrentValue);
-    senderRoot.connectAlfaPrimitive(mqttUri(TOPIC_ALFA_PRIMITIVE), writeCurrentValue);
+    assertTrue(senderRoot.connectInput1WhenFlagIsFalse(mqttUri(TOPIC_INPUT1FALSE)));
+    assertTrue(senderRoot.connectInput1WhenFlagIsTrue(mqttUri(TOPIC_INPUT1TRUE)));
+    assertTrue(senderRoot.connectInput2(mqttUri(TOPIC_INPUT2)));
+    assertTrue(senderRoot.connectInput3(mqttUri(TOPIC_INPUT3)));
+    assertTrue(receiverRoot.connectAlfa(mqttUri(TOPIC_ALFA)));
+    assertTrue(receiverRoot.connectAlfaPrimitive(mqttUri(TOPIC_ALFA_PRIMITIVE)));
+    assertTrue(senderRoot.connectAlfa(mqttUri(TOPIC_ALFA), writeCurrentValue));
+    assertTrue(senderRoot.connectAlfaPrimitive(mqttUri(TOPIC_ALFA_PRIMITIVE), writeCurrentValue));
   }
 
   @Override
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TreeIncrementalTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TreeIncrementalTest.java
index e0d7c817e372395adf98565b5a44347335a2eb45..9d455b6abe09809c0277c71dd3f8028f05d63da3 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TreeIncrementalTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TreeIncrementalTest.java
@@ -1,5 +1,6 @@
 package org.jastadd.ragconnect.tests;
 
+import org.junit.jupiter.api.Tag;
 import org.junit.jupiter.api.Test;
 import treeInc.ast.*;
 
@@ -17,6 +18,8 @@ import static org.junit.jupiter.api.Assertions.*;
  *
  * @author rschoene - Initial contribution
  */
+@Tag("Tree")
+@Tag("Incremental")
 public class TreeIncrementalTest extends AbstractTreeTest {
 
   private Root model;
@@ -53,8 +56,8 @@ public class TreeIncrementalTest extends AbstractTreeTest {
     handler.newConnection(TOPIC_ALFA, bytes -> data.numberOfTrees += 1);
 
     // connect. important: first receiver, then sender. to not miss initial value.
-    receiverRoot.connectAlfa(mqttUri(TOPIC_ALFA));
-    senderRoot.connectAlfa(mqttUri(TOPIC_ALFA), writeCurrentValue);
+    assertTrue(receiverRoot.connectAlfa(mqttUri(TOPIC_ALFA)));
+    assertTrue(senderRoot.connectAlfa(mqttUri(TOPIC_ALFA), writeCurrentValue));
   }
 
   @Override
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TreeManualTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TreeManualTest.java
index 060317dfd749f0815af9fe42d1cb9553fe9034ae..10c73aeff57d6752c8f6b903acbde2dd9d93aa8b 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TreeManualTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TreeManualTest.java
@@ -1,5 +1,6 @@
 package org.jastadd.ragconnect.tests;
 
+import org.junit.jupiter.api.Tag;
 import org.junit.jupiter.api.Test;
 import tree.ast.MqttHandler;
 import tree.ast.ReceiverRoot;
@@ -19,6 +20,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
  *
  * @author rschoene - Initial contribution
  */
+@Tag("Tree")
 public class TreeManualTest extends AbstractTreeTest {
 
   private Root model;
@@ -56,8 +58,8 @@ public class TreeManualTest extends AbstractTreeTest {
     handler.newConnection(TOPIC_ALFA, bytes -> data.numberOfTrees += 1);
 
     // connect. important: first receiver, then sender. to not miss initial value.
-    receiverRoot.connectAlfa(mqttUri(TOPIC_ALFA));
-    senderRoot.connectAlfa(mqttUri(TOPIC_ALFA), writeCurrentValue);
+    assertTrue(receiverRoot.connectAlfa(mqttUri(TOPIC_ALFA)));
+    assertTrue(senderRoot.connectAlfa(mqttUri(TOPIC_ALFA), writeCurrentValue));
   }
 
   @Override
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TutorialTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TutorialTest.java
index 6e66c761ffe4b5e6045d567cc1bdf9a14dea4de0..64ad5fbd147ba6c7e22d24ba7f92fe8d7114ced7 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TutorialTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TutorialTest.java
@@ -6,6 +6,7 @@ import tutorial.ast.B;
 import java.io.IOException;
 
 import static org.jastadd.ragconnect.tests.TestUtils.mqttUri;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 /**
  * Testcase "Tutorial".
@@ -38,10 +39,10 @@ public class TutorialTest extends AbstractMqttTest {
     // b2.OutputOnB -> a.Input
     b2.addDependencyB(a);
 
-    a.connectInput(mqttUri("topic/for/input"));
-    a.connectOutputOnA(mqttUri("a/out"), true);
-    b1.connectOutputOnB(mqttUri("b1/out"), true);
-    b2.connectOutputOnB(mqttUri("b2/out"), false);
+    assertTrue(a.connectInput(mqttUri("topic/for/input")));
+    assertTrue(a.connectOutputOnA(mqttUri("a/out"), true));
+    assertTrue(b1.connectOutputOnB(mqttUri("b1/out"), true));
+    assertTrue(b2.connectOutputOnB(mqttUri("b2/out"), false));
   }
 
   @Override
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ViaTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ViaTest.java
index 5433514398ea8ec3037f95fbc1c63bbb6a1cf2be..7ab46f7616a1cf90737c2e0b3ff752f553d38292 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ViaTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ViaTest.java
@@ -106,18 +106,18 @@ public class ViaTest extends AbstractMqttTest {
     senderRest2Mqtt = client.target(REST_SERVER_BASE_URL + PATH_REST_2_MQTT_RECEIVE);
     senderBoth2Rest = client.target(REST_SERVER_BASE_URL + PATH_BOTH_REST_RECEIVE);
 
-    model.connectMqtt2MqttInput(mqttUri(TOPIC_MQTT_2_MQTT_RECEIVE));
-    model.connectMqtt2MqttOutput(mqttUri(TOPIC_MQTT_2_MQTT_SEND), writeCurrentValue);
-    model.connectMqtt2RestInput(mqttUri(TOPIC_MQTT_2_REST_RECEIVE));
-    model.connectMqtt2RestOutput(restUri(PATH_MQTT_2_REST_SEND, REST_PORT), writeCurrentValue);
-    model.connectRest2MqttInput(restUri(PATH_REST_2_MQTT_RECEIVE, REST_PORT));
-    model.connectRest2MqttOutput(mqttUri(TOPIC_REST_2_MQTT_SEND), writeCurrentValue);
-    model.connectRest2RestInput(restUri(PATH_REST_2_REST_RECEIVE, REST_PORT));
-    model.connectRest2RestOutput(restUri(PATH_REST_2_REST_SEND, REST_PORT), writeCurrentValue);
-    model.connectBoth2BothInput(mqttUri(TOPIC_BOTH_MQTT_RECEIVE));
-    model.connectBoth2BothInput(restUri(PATH_BOTH_REST_RECEIVE, REST_PORT));
-    model.connectBoth2MqttOutput(mqttUri(TOPIC_BOTH_2_MQTT_SEND), writeCurrentValue);
-    model.connectBoth2RestOutput(restUri(PATH_BOTH_2_REST_SEND, REST_PORT), writeCurrentValue);
+    assertTrue(model.connectMqtt2MqttInput(mqttUri(TOPIC_MQTT_2_MQTT_RECEIVE)));
+    assertTrue(model.connectMqtt2MqttOutput(mqttUri(TOPIC_MQTT_2_MQTT_SEND), writeCurrentValue));
+    assertTrue(model.connectMqtt2RestInput(mqttUri(TOPIC_MQTT_2_REST_RECEIVE)));
+    assertTrue(model.connectMqtt2RestOutput(restUri(PATH_MQTT_2_REST_SEND, REST_PORT), writeCurrentValue));
+    assertTrue(model.connectRest2MqttInput(restUri(PATH_REST_2_MQTT_RECEIVE, REST_PORT)));
+    assertTrue(model.connectRest2MqttOutput(mqttUri(TOPIC_REST_2_MQTT_SEND), writeCurrentValue));
+    assertTrue(model.connectRest2RestInput(restUri(PATH_REST_2_REST_RECEIVE, REST_PORT)));
+    assertTrue(model.connectRest2RestOutput(restUri(PATH_REST_2_REST_SEND, REST_PORT), writeCurrentValue));
+    assertTrue(model.connectBoth2BothInput(mqttUri(TOPIC_BOTH_MQTT_RECEIVE)));
+    assertTrue(model.connectBoth2BothInput(restUri(PATH_BOTH_REST_RECEIVE, REST_PORT)));
+    assertTrue(model.connectBoth2MqttOutput(mqttUri(TOPIC_BOTH_2_MQTT_SEND), writeCurrentValue));
+    assertTrue(model.connectBoth2RestOutput(restUri(PATH_BOTH_2_REST_SEND, REST_PORT), writeCurrentValue));
   }
 
   @Override
diff --git a/relast-preprocessor b/relast-preprocessor
index b538a7f709167c5f56fe65e6d9e9f02179cacaef..02f8e35993dc3f62ab49e94f69a6dc27170660da 160000
--- a/relast-preprocessor
+++ b/relast-preprocessor
@@ -1 +1 @@
-Subproject commit b538a7f709167c5f56fe65e6d9e9f02179cacaef
+Subproject commit 02f8e35993dc3f62ab49e94f69a6dc27170660da
diff --git a/settings.gradle b/settings.gradle
index e7769874a5f3186274ceecbcd445feeb656cf9a4..8a597cfa491920744bb8b5e1d3f103fb4fc95bb1 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,3 +1,9 @@
+pluginManagement {
+    plugins {
+        id 'org.jastadd' version '1.13.3'
+    }
+}
+
 rootProject.name = 'ragconnect'
 
 include 'relast-preprocessor'