diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 332575587e4abd3f9be73902219966d25549b287..a33e8f0fcddb2760ec9a9e83e337ccd5035da873 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -38,14 +38,28 @@ test:
   needs:
     - build
   script:
+    - ./gradlew --console=plain --no-daemon test
     - ./gradlew --console=plain --no-daemon allTests
   artifacts:
     when: always
+    paths:
+      - "ragconnect.tests/test.log"
     reports:
       junit: "ragconnect.tests/build/test-results/**/TEST-*.xml"
     expire_in: 1 week
 
-publish:
+publish-dev:
+  image: openjdk:11
+  stage: publish
+  needs:
+    - test
+  script:
+    - "./gradlew setDevVersionForCI"
+    - "./gradlew publish"
+  except:
+    - master
+
+publish-master:
   image: openjdk:11
   stage: publish
   needs:
@@ -53,7 +67,6 @@ publish:
   script:
     - "./gradlew publish"
   only:
-    - dev
     - master
 
 ragdoc_build:
@@ -83,23 +96,42 @@ ragdoc_view:
     - OUTPUT_DIR=$(pwd -P)/pages/docs/ragdoc
     - cd /ragdoc-view/src/ && rm -rf data && ln -s $DATA_DIR
     - /ragdoc-view/build-view.sh --output-path=$OUTPUT_DIR
-  only:
-    - dev
-    - master
   artifacts:
     paths:
       - "pages/docs/ragdoc"
 
-pages:
+build_cloc:
+  image: "alpine:latest"
+  needs:
+    - build
+  script:
+    - apk add cloc
+    - cd pages/cloc
+    - sh run-cloc.sh
+  artifacts:
+    paths:
+      - pages/docs/cloc.md
+
+.pages-template:
   image: python:3.10.0-bullseye
   stage: publish
   needs:
     - ragdoc_view
     - test
+    - build_cloc
   before_script:
     - pip install -r pages/requirements.txt
   script:
     - cd pages && mkdocs build
+
+pages-dry-run:
+  extends: .pages-template
+  except:
+    - dev
+    - master
+
+pages:
+  extends: .pages-template
   artifacts:
     paths:
       - public/
@@ -107,13 +139,7 @@ pages:
     - master
 
 pages-dev:
-  image: python:3.10.0-bullseye
-  stage: publish
-  needs:
-    - ragdoc_view
-    - test
-  before_script:
-    - pip install -r pages/requirements.txt
+  extends: .pages-template
   script:
     - cd pages && mkdocs build
     - echo "UPSTREAM_JOB_ID=$CI_JOB_ID" >> build.env
diff --git a/.gitmodules b/.gitmodules
index 58efd108e858ae06dde5f626286bd3d73f9dde4e..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,4 +0,0 @@
-[submodule "relast-preprocessor"]
-	path = relast-preprocessor
-	url = ../relast-preprocessor.git
-	branch = develop
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..6c38c4c59743a075e698735d42ca14200772cb58
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,14 @@
+plugins {
+    id 'com.github.ben-manes.versions' version '0.42.0'
+    id 'java'
+}
+
+java.toolchain.languageVersion.set(JavaLanguageVersion.of(11))
+
+if (JavaVersion.current().isJava8Compatible()) {
+    allprojects {
+        tasks.withType(Javadoc) {
+            options.addStringOption('Xdoclint:none', '-quiet')
+        }
+    }
+}
diff --git a/gradle.properties b/gradle.properties
index 036899c6241c9aefbd7613611ceb35349d9d6624..0bce44fa414299045b03a6dadeb0263340d9c523 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,2 +1,5 @@
-log4j_version = 2.14.0
-mustache_java_version = 0.9.7
+log4j_version = 2.17.2
+mustache_java_version = 0.9.10
+preprocessor_version = 0.1.0-41
+relast_version = 0.3.0-137
+dumpAst_version = 1.2.0
diff --git a/libs/jastadd2.jar b/libs/jastadd2.jar
deleted file mode 100644
index d615b895453d660f0e7397fffad58a05029169fd..0000000000000000000000000000000000000000
Binary files a/libs/jastadd2.jar and /dev/null differ
diff --git a/libs/relast.jar b/libs/relast.jar
deleted file mode 100644
index b1a7542048dd1611db7f479307b0285efd8bb1f6..0000000000000000000000000000000000000000
Binary files a/libs/relast.jar and /dev/null differ
diff --git a/pages/.gitignore b/pages/.gitignore
index 14338f39f89e2a45e3473a6597e640aa9458f9a8..b731620fdf8be028d30088f0df75e32b5068744c 100644
--- a/pages/.gitignore
+++ b/pages/.gitignore
@@ -1,2 +1,3 @@
 /docs/ragdoc/
+/docs/cloc.md
 __pycache__
diff --git a/pages/cloc/.clocignore b/pages/cloc/.clocignore
new file mode 100644
index 0000000000000000000000000000000000000000..6be9eae9397330f2ed35619202932df433ce8bda
--- /dev/null
+++ b/pages/cloc/.clocignore
@@ -0,0 +1 @@
+run-cloc.sh
diff --git a/pages/cloc/.gitignore b/pages/cloc/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..2591dbfb5069d5010b8f0b40b18bf1a0a85cdb46
--- /dev/null
+++ b/pages/cloc/.gitignore
@@ -0,0 +1,10 @@
+files.csv
+my_definitions.txt
+ragconnect.base.lang
+ragconnect.file
+ragconnect.lang
+ragconnect.base-*.md
+ragconnect.base-*.txt
+ragconnect.tests-*.md
+ragconnect.tests-*.txt
+tmp.json
diff --git a/pages/cloc/cloc-def-connect.txt b/pages/cloc/cloc-def-connect.txt
new file mode 100644
index 0000000000000000000000000000000000000000..a20ca00ecfd5c2377ea06a87ebda2c4731ac30a2
--- /dev/null
+++ b/pages/cloc/cloc-def-connect.txt
@@ -0,0 +1,5 @@
+Connect
+    filter call_regexp_common Java
+    extension connect
+    extension ragconnect
+    3rd_gen_scale 10
diff --git a/pages/cloc/cloc-def-flex.txt b/pages/cloc/cloc-def-flex.txt
new file mode 100644
index 0000000000000000000000000000000000000000..4ac6dbb8da93e05d5b848dd5f8e8ed393c8b03c3
--- /dev/null
+++ b/pages/cloc/cloc-def-flex.txt
@@ -0,0 +1,4 @@
+Flex
+    filter remove_matches ^\s*//
+    extension flex
+    3rd_gen_scale 1.5
diff --git a/pages/cloc/cloc-def-grammar.txt b/pages/cloc/cloc-def-grammar.txt
new file mode 100644
index 0000000000000000000000000000000000000000..0afc6b8ad9bb80c5a34d155d2ea35cd856e9d924
--- /dev/null
+++ b/pages/cloc/cloc-def-grammar.txt
@@ -0,0 +1,5 @@
+Grammar
+    filter call_regexp_common Java
+    extension ast
+    extension relast
+    3rd_gen_scale 30
diff --git a/pages/cloc/cloc-def-jrag-jadd.txt b/pages/cloc/cloc-def-jrag-jadd.txt
new file mode 100644
index 0000000000000000000000000000000000000000..b5e3435ab068a9a83653808aa4e22aeb30c604fa
--- /dev/null
+++ b/pages/cloc/cloc-def-jrag-jadd.txt
@@ -0,0 +1,5 @@
+Attributes
+    filter call_regexp_common Java
+    extension jrag
+    extension jadd
+    3rd_gen_scale 1.5
diff --git a/pages/cloc/cloc-def-parser.txt b/pages/cloc/cloc-def-parser.txt
new file mode 100644
index 0000000000000000000000000000000000000000..c3df6758e0c6ca76f3af52e0ebb7a455c73b25a5
--- /dev/null
+++ b/pages/cloc/cloc-def-parser.txt
@@ -0,0 +1,4 @@
+Parser
+    filter remove_matches ^\s*//
+    extension parser
+    3rd_gen_scale 1.5
diff --git a/pages/cloc/run-cloc.sh b/pages/cloc/run-cloc.sh
new file mode 100755
index 0000000000000000000000000000000000000000..79308c9070e19ea9b6e77f26a9a343c9cca7117c
--- /dev/null
+++ b/pages/cloc/run-cloc.sh
@@ -0,0 +1,77 @@
+#!/bin/bash
+if [ "$1" == "-h" ] || [ "$1" == "--help" ]; then
+  echo "-s -> skip running cloc commands"
+  echo "-a -> produce files.csv with code count for single src files"
+  exit
+fi
+if [ "$1" != "-s" ]; then
+  #  --force-lang=Java,jrag --force-lang=Java,jadd
+  DEF_FILE=my_definitions.txt
+  echo "Export language definitions"
+  cloc --quiet --write-lang-def="$DEF_FILE"
+  for f in cloc-def-*.txt;
+  do
+  	cat $f >> "$DEF_FILE"
+  done
+  REPO_ROOT="../.."
+  CLOC_CMD="cloc --exclude-lang=JSON --read-lang-def=my_definitions.txt --exclude-list-file=.clocignore --quiet --hide-rate"
+  #  --ignored=bad-files.txt
+  cloc_double() {
+    f=$1
+    shift
+    echo "Creating $f.txt"
+    $CLOC_CMD --report-file="$f.txt" $@
+    echo "Creating $f.md"
+    $CLOC_CMD --md --report-file=tmp.md $@
+    tail -n+3 tmp.md > "$f.md"
+    rm tmp.md
+  }
+  make_page() {
+    echo "# Evaluation Metrics: Lines of Code"
+    echo
+    echo "## Manually written generator code"
+    echo
+    cat ragconnect.base-src-result.md
+    echo
+    echo "## Generated generator code"
+    cat ragconnect.base-gen-result.md
+    echo
+    echo "## Manually written test code"
+    cat ragconnect.tests-src-result.md
+    echo
+    echo "## Generated test code"
+    cat ragconnect.tests-gen-result.md
+  }
+fi
+if [ "$1" != "-s" ] && [ "$1" != "-a" ]; then
+  echo "Running cloc with new definitions"
+  cloc_double "ragconnect.base-src-result" --found=ragconnect.base-src-found.txt ${REPO_ROOT}/ragconnect.base/src/main/
+  cloc_double "ragconnect.base-gen-result" --found=ragconnect.base-gen-found.txt ${REPO_ROOT}/ragconnect.base/src/gen/jastadd ${REPO_ROOT}/ragconnect.base/src/gen/java
+  cloc_double "ragconnect.tests-src-result" --found=ragconnect.tests-src-found.txt ${REPO_ROOT}/ragconnect.tests/src/test/01-input/ ${REPO_ROOT}/ragconnect.tests/src/test/java/
+  cloc_double "ragconnect.tests-gen-result" --found=ragconnect.tests-gen-found.txt ${REPO_ROOT}/ragconnect.tests/src/test/02-after-ragconnect/ ${REPO_ROOT}/ragconnect.tests/src/test/java-gen
+  $CLOC_CMD --sum-reports --report_file=ragconnect ragconnect.base-src-result.txt ragconnect.base-gen-result.txt ragconnect.tests-src-result.txt ragconnect.tests-gen-result.txt
+  echo "Creating ../docs/cloc.md"
+  make_page > ../docs/cloc.md
+fi
+if [ "$1" == "-a" ]; then
+  echo "filename,code" > files.csv
+  for f in $(find ${REPO_ROOT}/ragconnect.base/src/main/ ${REPO_ROOT}/ragconnect.base/src/gen/jastadd-sources/ -type f); do
+    printf '.'
+    echo $f,$($CLOC_CMD --json $f | jq '.SUM.code') >> files.csv
+  done
+  echo
+  exit
+fi
+
+
+# cat ragconnect.base-src-result.txt
+# cat ragconnect.base.file
+# cat ragconnect.tests-result.txt
+
+echo "LOC stats:"
+echo "Language                     files          blank        comment           code"
+( for t in *-result.txt ; do echo -e "==> $t <=="; grep -v -e '---' -e 'SUM' -e 'Language' -e 'github' $t; done)
+
+echo 
+echo "Summary:"
+grep -v -e '---' -e 'SUM' -e 'Language' -e 'github' ragconnect.file
diff --git a/pages/docs/changelog.md b/pages/docs/changelog.md
index fe3b87a7249a5b554902ea7239b58f91ac55a500..58d7f207b980b417ecfbfc7c2fca380664e70ef3 100644
--- a/pages/docs/changelog.md
+++ b/pages/docs/changelog.md
@@ -1,25 +1,44 @@
 # Changelog
 
+## 1.0.0
+
+### Changes
+
+- Allow connection ports for
+  - relations ([#37](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/37))
+  - attributes ([#38](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/38)), especially collection and circular attributes ([#53](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/53))
+  - (sending) non-NTA nonterminals ([#36](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/36))
+  - context-free context ports ([#34](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/34))
+- Experimental support for Java handler ([#52](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/52))
+- Make specification language more concise ([#33](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/33))
+- Make dependency definitions deprecated ([#42](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/42)) and warn when used
+
+### Development Changes
+
+- Make grammar(s) more concise ([#40](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/40))
+- Enhance documentation, adding a DSL description
+- Refactor debug messages from System.out to SLF4J ([#46](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/46))
+
 ## 0.3.2
 
-- Allow connection endpoints for list nonterminals ([#21](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/21))
+- Allow connection ports for list nonterminals ([#21](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/21))
 - Ensure correct connect and disconnect functionality ([#31](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/31))
 - Enhance documentation ([#13](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/13), [#20](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/20), [#41](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/41))
 
 ## 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))
+- Full support for subtree port 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 [#22](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/22): Correct handling of malformed URIs passed when connecting an port
+- Bugfix [#23](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/23): Correct handling of OptComponents as ports
 - 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))
+- Add methods to `disconnect` a port
+- Internal: PoC for incremental dependency tracking and subtree port 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`
 
 ## 0.2.2
diff --git a/pages/docs/compiler.md b/pages/docs/compiler.md
index 86f15b7cdb2fcee74dc9d63f9bfa64cee2e8f62f..e89ad8fc8169e084c97736d6447c3466f1e2bc27 100644
--- a/pages/docs/compiler.md
+++ b/pages/docs/compiler.md
@@ -4,22 +4,23 @@ The compiler is JastAdd-compliant, i.e., it accepts all flags available for Jast
 Additional options are as follows.
 
 | Name | Required (Default) | Description |
-|-|-|-|
+|---|---|---|
 | `--rootNode` | Yes | Root node in the base grammar. |
-| `--protocols` | No (`mqtt`) | Protocols to enable, currently available: `mqtt, rest`. |
+| `--protocols` | No (`mqtt`) | Protocols to enable, currently available: `java` (experimental), `mqtt`, `rest`. |
 | `--printYaml` | No (false) | Print out YAML instead of generating files. |
 | `--verbose` | No (false) | Print more messages while compiling. |
 | `--logReads` | No (false) | Enable logging for every received message. |
 | `--logWrites` | No (false) | Enable logging for every sent message. |
 | `--logIncremental` | No (false) | Enable logging for observer in incremental dependency tracking. |
-| `--experimental-jastadd-329` | No (false) | Use trace events `INC_FLUSH_START` and `INC_FLUSH_END` ([JastAdd issue #329][jastadd-issue-329]), see [section about automatic dependency tracking](/using#dependency-tracking-automatically-derived). |
-| `--incremental` | No (false) | Enables incremental dependency tracking (if `trace` is also set appropriately). |
-| `--trace[=flush]` | No (false) | Enables incremental dependency tracking (if `incremental` is also set appropriately). |
+| `--logTarget` | No (`console`) | Logging target to use, currently available: `console, slf4j`. |
+| `--experimental-jastadd-329` | No (false) | Use tracing events `INC_FLUSH_START` and `INC_FLUSH_END` ([JastAdd issue #329][jastadd-issue-329]), see [section about automatic dependency tracking](using.md#dependency-tracking-automatically-derived). |
+| `--incremental` | No (false) | Enables incremental dependency tracking (if `tracing` is also set appropriately). |
+| `--tracing[=flush]` | No (false) | Enables incremental dependency tracking (if `incremental` is also set appropriately). |
 | `--version` | No (false) | Print version info and exit (reused JastAdd option) |
 | `--o` | No (`.`) | Output directory (reused JastAdd option) |
 
-All files to be process have to be passed as arguments.
-Their type is decided by the file extension (`ast` and `relast` for input grammars, `connect` and `ragconnect` for RagConnect definitions file).
+All files to be processed have to be passed as arguments.
+Their type is deduced by the file extension (`ast` and `relast` for input grammars, `connect` and `ragconnect` for RagConnect definitions file).
 
 # Additional software dependencies
 
@@ -28,6 +29,16 @@ However, depending on the selected protocols and/or used features, additional de
 
 ## Communication protocol characteristics
 
+### Java
+
+- Protocol identifier: `java`
+- URI scheme: `java://<ignored-host>[:ignored-port]/<topic>`
+    - the value for host and port are always ignored, but are necessary to form a legal URI
+- No required runtime dependencies
+- Additional remarks:
+    - First leading slash not included in topic.
+    - Currently, the default mappings are applied, which requires a consumer to expect `byte[]` (instead of a more intuitive token or node value). This might change in future versions.
+
 ### MQTT
 
 - Protocol identifier: `mqtt`
@@ -52,22 +63,22 @@ However, depending on the selected protocols and/or used features, additional de
 - Additional remarks:
     - Host is always `localhost`.
     - Might work with newer versions of `com.sparkjava.spark-core` as well.
-    - For debugging, it is beneficial to include an implementation for [SLF4J](http://www.slf4j.org/).
+    - For debugging, it is beneficial to include an implementation for [SLF4J][slf4j].
 
 ## Used features
 
 ### Automatic dependency tracking
 
-- Condition: When passing `--incremental` and `--trace=flush` to RagConnect
+- Condition: When passing `--incremental` and `--tracing=flush` to RagConnect
 - Required runtime dependencies: _none_
 - Required options for RelAST compiler: _none_
 - Required options for JastAdd:
     - `--incremental`
-    - `--trace=flush`
+    - `--tracing=flush`
 - Remarks:
     - Other (additional) values passed to those two options must be equal (e.g., `--incremental=param` passed to RagConnect must be also passed to JastAdd)
-    - Other values besides `flush` can be added to `--trace`
-    - [Feature description](/using#dependency-tracking-automatically-derived)
+    - Other values besides `flush` can be added to `--tracing`
+    - [Feature description](using.md#dependency-tracking-automatically-derived)
 
 ### (Safer) Automatic dependency tracking
 
@@ -77,11 +88,11 @@ However, depending on the selected protocols and/or used features, additional de
 - Required options for JastAdd: _none_
 - Remarks:
     - JastAdd version has to support `INC_FLUSH_START` and `INC_FLUSH_END` (i.e., has [issue #329][jastadd-issue-329] resolved)
-    - [Feature description](/using#dependency-tracking-automatically-derived)
+    - [Feature description](using.md#dependency-tracking-automatically-derived)
 
-### Tree/List Endpoints
+### Tree/List Ports
 
-- Condition: When using `tree` or `list` endpoints along with default mappings
+- Condition: When using ports along with default mappings for subtrees
 - Required runtime dependencies:
     - `group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.12.1'`
     - `group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.12.1'`
@@ -89,7 +100,17 @@ However, depending on the selected protocols and/or used features, additional de
     - `--serializer=jackson`
 - Required options for JastAdd: _none_
 - Remarks:
-    - [Feature description](/using#an-advanced-example)
+    - [Feature description](using.md#an-advanced-example)
+
+### Logging Target SLF4J
 
+- Condition: When passing `--logTarget=slf4j` to RagConnect
+- Required runtime dependencies:
+    - `group: 'org.slf4j', name: 'slf4j-api', version: '1.7.0'`
+- Required options for RelAST compiler: _none_
+- Required options for JastAdd: _none_
+- Remarks:
+    - Additionally, a slf4j binding is required, see [the slf4j user manual][slf4j]
 
 [jastadd-issue-329]: https://bitbucket.org/jastadd/jastadd2/issues/329/add-event-for-completion-of-flush
+[slf4j]: https://www.slf4j.org/manual.html
diff --git a/pages/docs/dsl.md b/pages/docs/dsl.md
new file mode 100644
index 0000000000000000000000000000000000000000..a73c292a9a4cd7e05775293a6d6c14d721b97755
--- /dev/null
+++ b/pages/docs/dsl.md
@@ -0,0 +1,127 @@
+# The RagConnect Specification Language
+
+To declare ports and mappings, a domain-specific language ([DSL](https://en.wikipedia.org/wiki/Domain-specific_language)) is used.
+
+## Ports
+
+A port marks an element of an AST as sending or receiving element.
+The kind of the element determines, whether an port for it can be receiving, sending, or both at the same time.
+
+To declare a new ports, use the following syntax:
+
+```
+("send"|"receive") ["indexed"] ["with add"] <Non-Terminal>[.<Target>["(<AttributeType>)"]] ["using" <Mapping-Name> (, <Mapping-Name>)*] ";"
+```
+
+A breakdown of the parts of that syntax:
+
+- The first word (`send` or `receive`) defines the kind of port - sending or receiving, respectively.
+- The optional `indexed` applies only for list children and lets the port act on elements of that list.
+  This only works for receiving ports, and is further changed by `with add`.
+    - A lonely `indexed` assigns each incoming "topic" to an index in a list.
+      This can be useful if multiple instances of this port are connected, or the communication protocol supports wildcard topics.
+      For the former case, the connect method with an explicit index can be used, whereas the "normal" connect method without the index acts as a method for "wildcard-connect".
+    - Combining `indexed with add`, incoming data is required to be an element of the list, and will be appended to the list.
+- The second optional keyword `with add` can also be used only for receiving ports targeting a list children.
+  As described above, it can be combined with `indexed`.
+  If used on its own, the incoming data is interpreted as a complete list and its elements will be appended to the current list.
+- The `<Non-Terminal>[.<Target>["(<AttributeType>)"]]` notation describes the actual affected node.
+    - If the target is omitted, all nodes of that non-terminal type can be connected, irrespective of their context. This is a context-free port definition.
+    - The target can be any child on the right-hand side of a production rule, a role of a relation, or an attribute.
+      The brackets `(<AttributeType>)` after the target must be used in case of an attribute, and only then.
+      Here, the return type of the attribute has to be specified, as aspect files are not parsed by RagConnect.
+      Hence, RagConnect can not and will not verify the existence of the attribute, and the possible non-existence of an attribute will be found by the Java compiler.
+- Optionally, a port can use one or more [mappings](#mappings).
+  They will be applied before sending, or after receiving a message.
+  Mappings will always be applied in the order they are listed after `using`.
+
+### Context-Free Ports
+
+A port with only a non-terminal and without a target is called context-free port.
+Specifying such a port has several consequences:
+
+- The given non-terminal can be connected to in all contexts it occurs as if there were ports for all those contexts.
+- There is a special method available on the given non-terminal to connect itself, which selects the correct connect-method depending on its context.
+- Context-sensitive ports for this non-terminal can still be specified to modify mappings in this context. If the context is a list, the port must use `indexed` and cannot use `with add`.
+
+**Example**:
+
+```
+// grammar
+Root ::= A SingleA:A [OptA:A] ListA:A* ;
+A ::= <Value> ;
+
+// connect
+receive A;
+receive Root.SingleA using MyMapping; // specialized port
+```
+
+Implied, additional connect specifications:
+
+```
+receive Root.A;
+receive Root.OptA;
+receive indexed Root.ListA;
+```
+
+Application code:
+
+```java
+A a = root.getOptA();
+// new method on A:
+a.connect("<some-uri-to-connect>");
+// equivalent to (implicitly generated):
+root.connectOptA("<some-uri-to-connect>");
+```
+
+## Mappings
+
+A mapping is a side effect-free function with one argument (the value that will be transformed) and one result (the transformed value), that will be applied on a value to be sent for a sending port, a received value for a receiving port, or the result of another mapping.
+Mappings can be shared between ports.
+
+To declare a mapping, use the following syntax:
+
+```
+<Mapping-Name> "maps" <From-Type> <Input-Variable-Name> "to" "To-Type" "{:"
+  <Java-Block>
+":}"
+```
+
+A breakdown of the parts of that syntax:
+
+- The `<Mapping-Name>` identifies the mapping.
+- The `<From-Type` is the type of the input. The type of the first mapping of a receiving port must be `byte[]`.
+- To refer to the input, `<Input-Variable-Name>` defines the name of it.
+- The `<To-Type>` is the type of the result. The type of the last mapping of a sending port must be `byte[]`.
+- Finally, the `<Java-Block>` is the actual definition of the mapping using normal Java syntax.
+  The previously defined input variable can be used via its name here.
+  This block can contain multiple statements, but must end with a `return` statement.
+  The validity of this block is not verified by RagConnect itself, but later in the compilation process by the Java compiler.
+
+Note: There are default mappings provided for all primitive Java types (using their "normal" byte representation), and for all non-terminal types (using their JSON representation converted from/to bytes).
+Those default mappings apply to both sending and receiving ports, and match their counterparts, e.g., the mapping from `int` to `byte[]` uses the same byte representation as the mapping back from `byte[]` to `int`.
+Default mappings are always inserted if either no mapping is present, or if the type of the first/last mapping is not `byte[]` as stated above.
+Their main intent is to allow quick prototyping without constraining a more complex use case.
+
+## Dependency definitions
+
+!!! note
+    Deprecated since `1.0.0`
+
+A dependency definition describes a possible dependency on type-level from a token to an attribute.
+Whenever the token changes, the attribute is eagerly re-computed and ports attached to it are triggered.
+
+Such a dependency must be added on instance-level for every token that could have an influence to the attribute.
+An alternative for those explicit dependency definitions is [incremental dependency tracking](using.md#dependency-tracking-automatically-derived).
+
+To declare a dependency definition, use the following syntax:
+
+```
+<Non-Terminal-1>.<Target> "canDependOn" <Non-Terminal-2>.<Token-Name> "as" <Dependency-Name> ";"
+```
+
+A breakdown of the parts of that syntax:
+
+- `<Non-Terminal-1>.<Target>` denotes the attribute (and the non-terminal it is defined on) which depends on the token
+- `<Non-Terminal-2>.<Token-Name>` denotes the token (and the non-terminal it is defined on) that (potentially) influences the attribute value
+- `<Dependency-Name>` identifies the dependency definition and is used for the generated method, which will be defined on `Non-Terminal-1` as `<Non-Terminal-1>.add<Dependency-Name>(<Non-Terminal-2> influencingNode)`
diff --git a/pages/docs/extending.md b/pages/docs/extending.md
index 61cabca224b7834f08cca7d96f3913c7cb9feb37..14fa8c8dbb525900f26c0ccbf9f7b45ac17544e5 100644
--- a/pages/docs/extending.md
+++ b/pages/docs/extending.md
@@ -1,32 +1,24 @@
 # Extending `RagConnect`
 
-To add a new communication protocol, the following locations have to be changed (replace `ABC` and `abc` with the name of the protocol):
+To add a new communication protocol, the following locations have to be changed (replace `ABC` and `abc` with the name of the protocol).
 
-Within `ragconnect.base/src/main/resources`:
+### Within `ragconnect.base/src/main/resources`
 
 {% raw %}
-- Add a new handler `ABCHandler`, if appropriate, similar to the existing handlers
-    - If further methods are needed for handler initialization, add a new template `abc.mustache` containing those procedures. Add `{{#usesABC}}{{> abc}}{{/usesABC}}` at the top of `ragconnect.mustache` to use this template
-- In `receiveDefinition.mustache` and `sendDefinition.mustache`: add a new case in the switch statement defining the logic to happen for both definitions. If the new protocol is close to a PUSH semantic, follow `mqtt`. If it is closer to PULL semantic, follow `rest`.
+- Add a new handler `ABCHandler.jadd`, similar to the existing handlers. 
+- In `handler.mustache`, add further methods if needed for handler usage in the application code (similar to `{{rootNodeName}}.{{SetupWaitUntilReadyMethodName}}` for `mqtt`)
+- In `receiveDefinition.mustache` and `sendDefinition.mustache`: add a new case in the switch statements defining the logic to happen upon connect and disconnect for both definitions. If the new protocol is close to a PUSH semantic, follow `mqtt`. If it is closer to PULL semantic, follow `rest`.
 {% endraw %}
 
-Within `ragconnect.base/src/main/jastadd`:
+### Within `ragconnect.base/src/main/jastadd`
 
-- In `backend/Configuration`:
-    - Add a new static boolean flag `usesABC` to indicate whether the protocol is used
-- In `backend/Generation`:
-    - Add new attributes for type `MRagConnect` for handler attribute and handler field, if needed
-    - Add attributes for newly introduced references in changed mustache templates, if any
-    - Add a newly constructed handler within the definition of `RagConnect.toMustache` with the needed fields (class name, construction snippet, handler attribute, handler field, the boolean flag you just added to Configuration)
-- In `backend/MustacheNodesToYAML`:
-    - Add key-value-pair for `usesABC` (and handler, if any)
-    - Add key-value-pairs for newly introduced referemces in changed mustache templates, if any
+In `Handlers.jrag`: Add a new attribute `RagConnect.abcHandler()` returning the resolved handler
 
-In `ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/Compiler.java`:
+### Within `ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler`
 
+In `Compiler.java`:
 - Add a new choice for `--protocols` similar to the existing ones
-- Set the flag `usesABC` if the choice is given.
-- Add code to add the handler to the list `handlers` if the choice is given, i.e., if `ASTNode.usesABC`
+- Add a newly constructed handler in `setConfiguration`  with the needed fields (definition file name within `resources` directory, commonly `ABCHandler.jadd`; class name of the handler; unique name for the protocol; whether the handler is used, i.e., if it was given in `--protocols`)
 
 Furthermore, new test cases are appreciated, see [below](#writing-tests).
 
@@ -58,7 +50,7 @@ All tests are required to run both locally, and within the CI.
 
 ### build.gradle
 
-Use the [PreprocessorPlugin][preprocessor-plugin], the build process can be written concisely in three parts per task:
+Using the [PreprocessorPlugin][preprocessor-plugin], the build process can be written concisely in three parts per task:
 
 ```groovy
 task compileTreeAllowedTokens(type: RagConnectTest) {
diff --git a/pages/docs/img/poster.pdf b/pages/docs/img/2020_mpm4cps/poster.pdf
similarity index 100%
rename from pages/docs/img/poster.pdf
rename to pages/docs/img/2020_mpm4cps/poster.pdf
diff --git a/pages/docs/img/robo3d.png b/pages/docs/img/2020_mpm4cps/robo3d.png
similarity index 100%
rename from pages/docs/img/robo3d.png
rename to pages/docs/img/2020_mpm4cps/robo3d.png
diff --git a/pages/docs/img/mpm4cps-slides.pdf b/pages/docs/img/2020_mpm4cps/slides.pdf
similarity index 100%
rename from pages/docs/img/mpm4cps-slides.pdf
rename to pages/docs/img/2020_mpm4cps/slides.pdf
diff --git a/pages/docs/img/2022_models/photo.jpg b/pages/docs/img/2022_models/photo.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..55a8d11e0e1fe10a16d5f4849c8af727b25f89cd
Binary files /dev/null and b/pages/docs/img/2022_models/photo.jpg differ
diff --git a/pages/docs/img/ragconnect-process.png b/pages/docs/img/ragconnect-process.png
new file mode 100644
index 0000000000000000000000000000000000000000..7f7ee7ec8d6d2e6b001ca9bc16c9cac8a928d975
Binary files /dev/null and b/pages/docs/img/ragconnect-process.png differ
diff --git a/pages/docs/img/ros2rag-process.png b/pages/docs/img/ros2rag-process.png
deleted file mode 100644
index f44fad87ba0d5205dc6638be24fb97fe8ce86bbd..0000000000000000000000000000000000000000
Binary files a/pages/docs/img/ros2rag-process.png and /dev/null differ
diff --git a/pages/docs/inner-workings.md b/pages/docs/inner-workings.md
index 6d8b41e13847f2ae337f08c65068cbef6085e065..c3f88279d126ec6f4f878621c6eb0f0650061225 100644
--- a/pages/docs/inner-workings.md
+++ b/pages/docs/inner-workings.md
@@ -1,15 +1,72 @@
-# Inner workings of `RagConnect`
+# Inner Workings of `RagConnect`
 
 Please see [API documentation](ragdoc/index.html) for more details.
 
-![ros2rag-process](img/ros2rag-process.png)
+![ragconnect-process](img/ragconnect-process.png)
 
-`RagConnect` uses the [relast-preprocessor](https://git-st.inf.tu-dresden.de/jastadd/relast-preprocessor) to parse `.relast` grammar files. This results in an ASTNode of type `Program`.
-It further uses a dedicated parser for `.connect` files containing endpoint-, mapping-, and dependency-definitions. This results in an ASTNode of type `RagConnect`.
-The goal is to generate an aspect file containing setters and getters of tokens referred to by endpoint-definitions
-We use [mustache](https://mustache.github.io/) (currently its [Java version](https://github.com/spullara/mustache.java)) making use of partials resulting in a set of `.mustache` files located in `ragconnect.base/src/main/resources`.
-The generation process uses an intermediate NTA of type `MRagConnect` defined in `MustacheNodes.relast` to separate this generation concern from the content of the DSL
+`RagConnect` uses the [relast-preprocessor](https://git-st.inf.tu-dresden.de/jastadd/relast-preprocessor) to parse `.relast` grammar files.
+This results in an ASTNode of type `Program`.
+It further uses a dedicated parser for `.connect` files containing port-, mapping-, and dependency-definitions.
+This results in an ASTNode of type `RagConnect`.
+The goal is to generate an aspect file containing setters and getters of tokens referred to by port-definitions
+We use [mustache](https://mustache.github.io/) (currently its [Java version](https://github.com/spullara/mustache.java)) making use of partials to separate concerns.
+The `.mustache` files are located in `ragconnect.base/src/main/resources`.
+The generation process uses intermediates NTAs (whose types are defined in `Intermediate.relast`) to ease the definition of two main generation "problems".
+Those problems are differentiation on both kinds of a port (send/receive and type/token/list), and attributes depending on position in n-ary relations.
 
-There are aspect files for `Navigation` (mainly isX/asX attributes), `Analysis` (lookup attributes), `Printing`, `backend/Mappings` (default mappings)
-One of the main aspects is `backend/Generation` containing attributes to construct the `MRagConnect` NTA and all necessary attributes used within the mustache templates
-The other main aspect (which is currently not really used) is `backend/MustacheNodesToYAML.jrag` containing the transformation from a `MRagConnect` subtree to a `Document` subtree defined by `YAML.relast`. This is used to generate a YAML file containing the data used by mustache. It can be used by the default mustache implementation together with the templates.
+There are aspect files for `Navigation` (mainly isX/asX attributes), `Analysis` (static analysis attributes), `Printing`, `Mappings` (default mappings).
+One of the main aspects is `Intermediate` containing all attributes consumed by `mustache` and other attributes the former depend on.
+The other main aspect (which is currently not really used) is `IntermediateToYAML` containing the transformation from a `RagConnect` subtree to a `Document` subtree defined by `Mustache.relast` (located in `relast-preprocessor` submodule).
+This is used to generate a YAML file containing the data used by mustache.
+It can be used by the default mustache implementation together with the templates.
+
+# Implementation Details
+
+In the following, details for special implementation topics are discussed.
+
+## Forwarding
+
+When a nonterminal is used in a send ports, it needs an implicit forwarding attribute to work, because only _computed elements_ can be sent.
+Since the nonterminal itself should be sent, the generated attribute simply returns this nonterminal.
+
+However, changing any token within the whole subtree or changing the structure of the subtree must trigger a new message, upon computation of the forwarding attribute, all tokens are "touched" (their getter is called).
+This way, the dependency tracking registers a dependency between structure and tokens to the attribute.
+
+The attribute (as well as any other generated element) is prefixed with `_ragconnect_` to avoid potential name conflicts with user-specified elements.
+
+# Implementation Hints
+
+## Debugging Tests and Finding Bugs
+
+To help with finding errors/bugs when tests fail, there are several things to find the correct spot.
+
+- **Look closely**. Analyze the error message closely, and possible any previous error message(s) that could have caused the test to fail.
+- **Focus on single error**
+    - To only inspect one test, mark them with `@Tag("New")` and use the gradle task "newTests".
+    - Use `Assumptions.assumeTrue(false);` to abort unneeded test cases early.
+    - When editing RagConnect itself and force recreating source for the affected test, e.g., `compileForwardingIncremental.outputs.upToDateWhen { false }`
+    - _Remember to undo all changes, once the bug is fixed._
+- **Activate logs**. Activate logging in the `ragconnect` specification of the compile-task of the affected test:
+    - _Remember to remove those lines, once the bug is fixed._
+
+```groovy
+task compile(type: RagConnectTest) {
+    ragconnect {
+        // ... other parameters ...
+        logReads = true
+        logWrites = true
+        logIncremental = true
+    }
+    // ... other tools ...
+}
+```
+
+- **Trace incremental events**. Add a receiver right after creation of the root node (named `model` here)
+    - This will output every event fired by the incremental evaluation engine.
+    - _Remember to remove this line, once the bug is fixed._
+
+```java
+model.trace().setReceiver(TestUtils::logEvent);
+```
+
+- **Add log statements**. As there will be quite some log output, add some identifying log statement (i.e., using `logger.fatal("---")`) right before the suspicious statement to inspect only the relevant log message after that.
diff --git a/pages/docs/use_cases.md b/pages/docs/use_cases.md
index 6e4524c7801f92d41c41371db585046ec6a83136..56c5af23a33a6aded02584dd4d1b37828dfdac4d 100644
--- a/pages/docs/use_cases.md
+++ b/pages/docs/use_cases.md
@@ -1,13 +1,13 @@
 # Use cases with `RagConnect`
 
-## MPM4CPS Paper - Codename 'Ros2Rag'
+## MPM4CPS Paper (2020) - Codename 'Ros2Rag'
 
 In the publication [*"Connecting conceptual models using Relational Reference Attribute Grammars"*](https://doi.org/10.1145/3417990.3421437), a use case involving a simulated robot arm and two different models connected to it was shown.
 One model was used to ensure a low speed of the robot when within a safety zone (purple boxes in the picture below), and the other model executes a workflow to control the robot.
 
-![Screenshot of Gazebo](img/robo3d.png)
+![Screenshot of Gazebo](img/2020_mpm4cps/robo3d.png)
 
-This paper was presented on October, 16h during the [MPM4CPS workshop](https://msdl.uantwerpen.be/conferences/MPM4CPS/2020/) within the [MODELS 2020 conference](https://conf.researchr.org/home/models-2020). For more information, see the [presented slides](img/mpm4cps-slides.pdf), [a recording of the session](https://youtu.be/Hgc1qFfmr44?t=1220) or the accompanied [poster](img/poster.pdf).
+This paper was presented on October, 16th 2020 during the [MPM4CPS workshop](https://msdl.uantwerpen.be/conferences/MPM4CPS/2020/) within the [MODELS 2020 conference](https://conf.researchr.org/home/models-2020). For more information, see the [presented slides](img/2020_mpm4cps/slides.pdf), [a recording of the session](https://youtu.be/Hgc1qFfmr44?t=1220) or the accompanied [poster](img/2020_mpm4cps/poster.pdf).
 
 The repository with the used source code can be found at: <https://git-st.inf.tu-dresden.de/ceti/ros/mpm4cps2020>
 The usage is dockerized, so starting the application only involves the commands listed below.
@@ -28,3 +28,22 @@ docker-compose up rag_app
 # Terminal 3: Goal-Model
 docker-compose up rag_goal
 ```
+
+## ACSOS Paper (2022) - Codename 'Motion Grammar Demo'
+
+In the paper [*"Specifying Reactive Robotic Applications
+With Reference Attribute Motion Grammars"*](http://mg.relational-rags.eu), motion grammars (an older approach by [Dantham and Stilman](https://doi.org/10.1109/TRO.2013.2239553)) were implemented using Relational RAGs.
+This paper will be presented on September, 21st 2022 during the [Posters and Demos session](https://2022.acsos.org/track/acsos-2022-posters-and-demos) of the [ACSOS 2022 conference](https://2022.acsos.org/).
+For more information, see <http://mg.relational-rags.eu/>.
+
+## MODELS Paper (2022) - Codename 'Ros3Rag'
+
+In the paper [*"Incremental Causal Connection for Self-Adaptive Systems Based on Relational Reference Attribute Grammars"*](https://doi.org/10.1145/3550355.3552460), a [previous use case](#mpm4cps-paper-codename-ros2rag) was extended to Collaborative, Teaching-Based Robotic Cells.
+
+![Screenshot](img/2022_models/photo.jpg)
+
+This paper will be presented in the technical track of the [MODELS 2022 conference](https://conf.researchr.org/home/models-2022) (Oct 23 - Oct 29).
+There is an [artifact hosted at Zenodo](https://doi.org/10.5281/zenodo.7009758) containing all source code and the executable case study.
+Furthermore, the presentation slides and a poster will be available soon.
+
+The repository with the used source code can be found at: <https://git-st.inf.tu-dresden.de/ceti/ros/models2022>
diff --git a/pages/docs/using.md b/pages/docs/using.md
index 0118524d95148c180bb6242d0823b9b64099853b..e9705465ab851344f0abda19acd75e1cad334cb4 100644
--- a/pages/docs/using.md
+++ b/pages/docs/using.md
@@ -4,20 +4,20 @@ The full example is available at <https://git-st.inf.tu-dresden.de/jastadd/ragco
 
 ## Preparation and Specification
 
-The following examples are inspired by the real test case [read1write2](https://git-st.inf.tu-dresden.de/jastadd/ragconnect-tests/-/tree/master/ragconnect.tests/src/test/01-input/read1write2)
-The idea is to have two nonterminals, where input information is received on one of them, and - after transformation - is sent out by both.
+The following examples are inspired by real [test cases](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/tree/master/ragconnect.tests/src/test/01-input) read1write2 and tokenValueSend.
+The idea is to have two non-terminals, where input information is received on one of them, and - after transformation - is sent out by both.
 
-Let the following grammar be used:
+Let's use the following grammar:
 
 ```
 A ::= <Input:String> /<OutputOnA:String>/ B* ;
-B ::= /<OutputOnB:String>/ ;
+B ::= <OutputOnB:String> ;
 ```
 
 To declare receiving and sending tokens, a dedicated DSL is used:
 
 ```java
-// endpoint definitions
+// port definitions
 receive A.Input ;
 send A.OutputOnA ;
 send B.OutputOnB using Transformation ;
@@ -35,32 +35,19 @@ Such mapping definitions can be defined for receiving tokens as well.
 In this case, they are applied before the value is set.
 If no mapping definition is given, or if the required type (depending on the communication protocol, see later) does not match, a "default mapping definition" is used to avoid boilerplate code converting from or to primitive types.
 
-Furthermore, let the following attribute definitions be given:
+Furthermore, let the following attribute definition be given:
 
 ```java
 syn String A.getOutputOnA() = "a" + getInput();
-
-syn String B.getOutputOnB() = "b" + input();
-inh String B.input();
-eq A.getB().input() = getInput();
 ```
 
-In other words, `OutputOnA` depends on `Input` of the same node, and `OutputOnB` depends on `Input` of its parent node.
-Currently, those dependencies can be explicitly written down, or incremental evaluation can be used.
-
-### Dependency tracking: Manually specified
-
-This specification happens also in the DSL (dependencies have to be named to uniquely identify them):
-
-```java
-// dependency definitions
-A.OutputOnA canDependOn A.Input as dependencyA ;
-B.OutputOnB canDependOn A.Input as dependencyB ;
-```
+In other words, `OutputOnA` depends on `Input` of the same node.
+This dependency is automatically inferred, if incremental evaluation is used.
+Otherwise, the deprecated manual dependencies must be used.
 
 ### Dependency tracking: Automatically derived
 
-To automatically track dependencies, the two additional parameters `--incremental` and `--trace=flush` have to be provided to both RagConnect and (in the later stage) JastAdd.
+To automatically track dependencies, the two additional parameters `--incremental` and `--tracing=flush` have to be provided to both RagConnect and (in the later stage) JastAdd.
 This will generate a different implementation of RagConnect relying on enabled incremental evaluation of JastAdd.
 The value for `incremental` has only been tested for `incremental=param`.
 The value for `trace` can include other values besides `flush`.
@@ -68,9 +55,18 @@ The value for `trace` can include other values besides `flush`.
 An experimental, optimized version can be selected using `--experimental-jastadd-329` reducing the risk of conflicts between concurrent attribute evaluations.
 However, this requires a version of JastAdd that resolved the [issue 329](https://bitbucket.org/jastadd/jastadd2/issues/329/add-event-for-completion-of-flush).
 
+### Deprecated Manual Dependency Specification
+
+Specification happens also in the DSL (dependencies have to be named to uniquely identify them):
+
+```java
+// dependency definition
+A.OutputOnA canDependOn A.Input as dependencyA ;
+```
+
 ## Using generated code
 
-After specifying everything, code will be generated if [setup properly](/adding).
+After specifying everything, code will be generated if [setup properly](adding.md).
 Let's create an AST in some driver code:
 
 ```java
@@ -83,19 +79,15 @@ a.addB(b1);
 a.addB(b2);
 ```
 
-If necessary, we have to set the dependencies as [described earlier](#dependency-tracking-manually-specified).
+If necessary, we have to set the dependencies as [described earlier](#deprecated-manual-dependency-specification).
 
 ```java
 // a.OutputOnA -> a.Input
 a.addDependencyA(a);
-// b1.OutputOnB -> a.Input
-b1.addDependencyB(a);
-// b2.OutputOnB -> a.Input
-b2.addDependencyB(a);
 ```
 
 Finally, we can actually _connect_ the tokens.
-Depending on the enabled protocols, [different URI schemes are allowed](/compiler#communication-protocol-characteristics).
+Depending on the enabled protocols, [different URI schemes are allowed](compiler.md#communication-protocol-characteristics).
 In this example, we use the default protocol: MQTT.
 
 ```java
@@ -105,15 +97,15 @@ b1.connectOutputOnB("mqtt://localhost/b1/out", true);
 b2.connectOutputOnB("mqtt://localhost/b2/out", false);
 ```
 
-The first parameter of those connect-methods is always an URI-like String, to identify the protocol to use, the server operating the protocol, and a path to identify the concrete token.
+The first parameter of those connect-methods is always a URI-like String, to identify the protocol to use, the server operating the protocol, and a path to identify the concrete token.
 In case of MQTT, the server is the host running an MQTT broker, and the path is equal to the topic to publish or subscribe to.
 Please note, that the first leading slash (`/`) is removed for MQTT topics, e.g., for `A.Input` the topic is actually `topic/for/input`.
 
-For sending endpoints, there is a second boolean parameter to specify whether the current value shall be sent immediately after connecting.
+For sending ports, there is a second boolean parameter to specify whether the current value shall be sent immediately after connecting.
 
 ## Remarks for using manual dependency tracking
 
-When constructing the AST and connecting it, one should always set dependencies before connecting, especially if updates already arriving for receiving endpoints.
+When constructing the AST and connecting it, one should always set dependencies before connecting, especially if updates already arriving for receiving ports.
 Otherwise, updates might not be propagated after setting dependencies, if values are equal after applying transformations of mapping definitions.
 
 As an example, when using the following grammar and definitions for RagConnect ...
@@ -137,34 +129,34 @@ Round maps float f to int {:
 
 # An advanced example
 
-Non-terminal children can also be selected as endpoints (not only tokens).
+Non-terminal children can also be selected as ports (not only tokens).
 
 ## Normal Non-Terminal Children
 
 Receiving normal non-terminal children and optionals means to replace them with a new node deserialized from the received message.
 Sending them involves serializing a node, and sending this representation in a message.
 
-Suppose, the following (shortened) grammar is used (inspired from the testcase [tree](https://git-st.inf.tu-dresden.de/jastadd/ragconnect-tests/-/tree/master/ragconnect.tests/src/test/01-input/tree))
+Suppose, the following (shortened) grammar is used (inspired from the testcase tree and forwarding)
 
 ```
 Root ::= SenderRoot ReceiverRoot ;
-SenderRoot ::= <Input:int> /Alfa/ ;
-ReceiverRoot ::= Alfa ;
-Alfa ::= // some content ...
+SenderRoot ::= <Input:int> /A/ B ;
+ReceiverRoot ::= A ;
+A ::= // some content ...
+B ::= <Value> ;
 ```
 
-Now, the complete node of type `Alfa` can be sent, and received again using the following connect specification:
+Now, the complete node of types `A` and `B` can be sent, and received again using the following connect specification:
 
 ```
-send tree SenderRoot.Alfa ;
-receive tree ReceiverRoot.Alfa ;
+send SenderRoot.A ;
+send SenderRoot.B ;
+receive ReceiverRoot.A ;
 ```
 
-Currently, receiving and sending trees requires the explicit demarcation from tokens using the keyword `tree`.
-
 To process non-terminals, default mappings are provided for every non-terminal type of the used grammar.
 They use the JSON serialization offered by the RelAST compiler, i.e., interpret the message as a `String`, deserialize the content reading the message as JSON, or vice versa.
-Additional dependencies are required to use this feature, as detailed in [the compiler section](/compiler#treelist-endpoints).
+Additional dependencies are required to use this feature, as detailed in [the compiler section](compiler.md#treelist-ports).
 
 ## Receiving List Children
 
@@ -173,82 +165,93 @@ When receiving list children, there are a few more options to match the connecti
 Suppose we use a similar grammar as above, i.e.:
 
 ```
-SenderRoot ::= /AlfaList:Alfa*/ /SingleAlfa:Alfa/;
-ReceiverRoot ::= Alfa* ;
+SenderRoot ::= /AList:A*/ /SingleA:A/;
+ReceiverRoot ::= A* ;
 ```
 
-Several options are possible:
+Several options are possible (please also refer to the specification of the [connect DSL](dsl.md):
 
-### list
+### (empty)
 
-A message for a list endpoint can be interpreted as a complete list (a sequence of nodes of type `Alfa`) by using the `list` keyword instead of `tree`:
+A message for a list port can be interpreted as a complete list (a sequence of nodes of type `A`) by not specifying any special keyword:
 
 ```
-receive list ReceiverRoot.Alfa ;
+receive ReceiverRoot.A ;
 ```
 
-### list + with add
+### with add
 
 Upon receiving the message, the deserialized list can also be appended to the existing list instead of replace the latter.
-This can be achieved using the keyword `with add` in addition to the keyword `list`:
+This can be achieved using the keyword `with add` :
 
 ```
-receive list with add ReceiverRoot.Alfa ;
+receive with add ReceiverRoot.Alfa ;
 ```
 
-### tree (indexed)
+### indexed
 
-A message for a list endpoint can also be interpreted as an element of this list.
+A message for a list port can also be interpreted as an element of this list.
 
 ```
-receive tree ReceiverRoot.Alfa ;
+receive tree ReceiverRoot.A ;
 ```
 
 Upon connection, the index of the deserialized element to set, has to be passed (`1` in the example below).
 The list must have enough elements once a message is received.
 
 ```java
-receiverRoot.connectAlfa("<some-url>", 1);
+receiverRoot.connectA("<some-url>", 1);
 ```
 
-### tree (wildcard)
+### indexed (wildcard)
 
-Similar to the `tree (indexed)` case above, messages are interpreted as an element of the list, but the connection can also be made using a "wildcard topic" and without an index.
+Similar to the `indexed` case above, messages are interpreted as an element of the list, but the connection can also be made using a "wildcard topic" and without an index.
 Then, once a message is received from a new concrete topic, the deserialized element will be appended to the list and this topic is associated with the index of the newly added element.
 Any further message from that topic will replace the element at the associated index.
 In the short example below, MQTT is used to with a wildcard topic, as `#` matches every sub-topic.
 
 ```java
-receiverRoot.connectAlfa("mqtt://<broker>/some/topic/#");
+receiverRoot.connectA("mqtt://<broker>/some/topic/#");
 
 // list is initially empty
-assertEquals(receiverRoot.getAlfaList(), list());
+assertEquals(receiverRoot.getAList(), list());
 // after receiving "1" on new topic "some/topic/one" (index 0)
-assertEquals(receiverRoot.getAlfaList(), list("1"));
+assertEquals(receiverRoot.getAList(), list("1"));
 // after receiving "other" on new topic "some/topic/two" (index 1)
-assertEquals(receiverRoot.getAlfaList(), list("1", "other"));
+assertEquals(receiverRoot.getAList(), list("1", "other"));
 // after receiving "new" on existing topic "some/topic/one" (index 0)
-assertEquals(receiverRoot.getAlfaList(), list("new", "other"));
+assertEquals(receiverRoot.getAList(), list("new", "other"));
 ```
 
-### tree (indexed/wildcard) + with add
+### indexed + with add
 
-Combining `tree` and `with add` results in a connection, where messages are interpreted as elements of the list, and new elements are appended to the existing list.
+Combining `indexed` and `with add` results in a connection, where messages are interpreted as elements of the list, and new elements are appended to the existing list.
 In that case, wildcard and non-wildcard connections behave in the same way, as no index has to be passed, and the element is always append at the end.
 Reusing the example from above, the following observations can be made.
 
 ```java
-receiverRoot.connectAlfa("mqtt://<broker>/some/topic/#");
+receiverRoot.connectA("mqtt://<broker>/some/topic/#");
 // or
-receiverRoot.connectAlfa("mqtt://<broker>/some/topic/one");
-receiverRoot.connectAlfa("mqtt://<broker>/some/topic/two");
+receiverRoot.connectA("mqtt://<broker>/some/topic/one");
+receiverRoot.connectA("mqtt://<broker>/some/topic/two");
 
 // list is initially empty
-assertEquals(receiverRoot.getAlfaList(), list());
+assertEquals(receiverRoot.getAList(), list());
 // after receiving "1" on topic "some/topic/one"
-assertEquals(receiverRoot.getAlfaList(), list("1"));
+assertEquals(receiverRoot.getAList(), list("1"));
 // after receiving "other" on topic "some/topic/two"
-assertEquals(receiverRoot.getAlfaList(), list("1", "other"));
+assertEquals(receiverRoot.getAList(), list("1", "other"));
 // after receiving "new" on topic "some/topic/one"
-assertEquals(receiverRoot.getAlfaList(), list("1", "other", "new"));
+assertEquals(receiverRoot.getAList(), list("1", "other", "new"));
 ```
+
+## Using attributes as port targets
+
+As described in the [DSL specification](dsl.md), attributes can be used as port targets.
+They can only be used in send ports, and the return type of the attribute must be specified in the connect specification (because aspect files are not handled completely yet).
+
+Currently, synthesized, inherited, collection, and circular attributes are supported.
+Nonterminal attributes are best used with the "legacy" notation `/Context:Type/` within the grammar.
+
+Please note, that serialization of Java collections of nonterminals is not supported, e.g., a `java.util.Set<ASTNode>`.
+Only list nodes as defined in the grammar `/Context:Type*/` are properly recognized.
diff --git a/pages/main.py b/pages/main.py
index 34fc17a618280621b5d737559c8a1839a9e019e7..b5160b33a32ca57e663f59e15277e99ba0464521 100644
--- a/pages/main.py
+++ b/pages/main.py
@@ -1,6 +1,6 @@
 import os
 
-ragconnectVersionFileName = '../ragconnect.base/src/main/resources/ragConnectVersion.properties'
+ragconnectVersionFileName = '../ragconnect.base/src/main/resources/ragconnectVersion.properties'
 
 
 def get_version():
diff --git a/pages/mkdocs.yml b/pages/mkdocs.yml
index c00e8c5eaf3bc9593a30800d532baedb56be2620..8ff01eda0839ea0231b98a152a2820a50bef92c1 100644
--- a/pages/mkdocs.yml
+++ b/pages/mkdocs.yml
@@ -3,11 +3,13 @@ repo_url: https://git-st.inf.tu-dresden.de/jastadd/ragconnect
 site_dir: ../public
 
 nav:
-  - "RagConnect by Example": using.md
-  - "Use Cases": use_cases.md
   - "Adding RagConnect to your project": adding.md
+  - "Using RagConnect (by Example)": using.md
+  - "RagConnect Specification Language": dsl.md
   - "Compiler options": compiler.md
+  - "Use Cases": use_cases.md
   - "Inner workings": inner-workings.md
+  - "Evaluation Metrics: Lines of Code": cloc.md
   - "Extending RagConnect": extending.md
   - "Changelog": changelog.md
   - "API documentation": ragdoc/index.html
@@ -19,6 +21,7 @@ theme:
 markdown_extensions:
   - toc:
       permalink: 
+  - admonition
 
 plugins:
   - search
diff --git a/pages/requirements.txt b/pages/requirements.txt
index 43e5e8286a08c67e0f31725821347f38ae4641ef..15f2ba17edb0e5afa8886d4a3cc191baf4f89248 100644
--- a/pages/requirements.txt
+++ b/pages/requirements.txt
@@ -2,3 +2,5 @@ mkdocs==1.2.2
 mkdocs-git-revision-date-localized-plugin==0.10.3
 mkdocs-macros-plugin==0.6.3
 mike==1.1.2
+Jinja2==2.11.2
+MarkupSafe==1.1.1
diff --git a/ragconnect.base/.gitignore b/ragconnect.base/.gitignore
index 87b4cdd3d7c6a41502ca98703abeeb69a1d536fb..4c0fcfac7f2f2c14ac6e81d173d3a2f7d9d99a55 100644
--- a/ragconnect.base/.gitignore
+++ b/ragconnect.base/.gitignore
@@ -3,3 +3,4 @@ src/gen-res/
 src/gen/
 out/
 *.class
+/parameters.txt
diff --git a/ragconnect.base/build.gradle b/ragconnect.base/build.gradle
index c1ee98d52184b358a411a17a359f75a4fa601e8e..b00a4301c412901a14dd13199829a2ad7904eab1 100644
--- a/ragconnect.base/build.gradle
+++ b/ragconnect.base/build.gradle
@@ -1,3 +1,4 @@
+// --- Buildscripts (must be at the top) ---
 buildscript {
     repositories.mavenCentral()
     dependencies {
@@ -5,86 +6,80 @@ buildscript {
     }
 }
 
+// --- Plugin definitions ---
 plugins {
+    id 'com.github.ben-manes.versions'
     id 'java'
-    id 'java-library'
     id 'idea'
-    id 'com.github.ben-manes.versions' version '0.36.0'
-    id 'maven-publish'
+    id 'org.jastadd'
     id 'application'
+    id 'java-library'
+    id 'maven-publish'
 }
 
-apply plugin: 'jastadd'
-
-group = 'de.tudresden.inf.st'
-
-mainClassName = 'org.jastadd.ragconnect.compiler.Compiler'
-
+// --- Dependencies ---
 repositories {
     mavenCentral()
-    jcenter()
+    maven {
+        name "gitlab-maven"
+        url "https://git-st.inf.tu-dresden.de/api/v4/groups/jastadd/-/packages/maven"
+    }
+}
+
+configurations {
+    relast
 }
 
 dependencies {
-    implementation project(':relast-preprocessor')
+    relast group: 'org.jastadd', name: 'relast', version: "${relast_version}"
+    implementation group: 'org.jastadd', name: 'relast-preprocessor', version: "${preprocessor_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 fileTree(include: ['jastadd2.jar'], dir: '../libs')
+    jastadd2 group: 'org.jastadd', name: 'jastadd2', version: '2.3.5-dresden'
     api group: 'net.sf.beaver', name: 'beaver-rt', version: '0.9.11'
 }
 
-def versionFile = 'src/main/resources/ragConnectVersion.properties'
-def props = new Properties()
-
-try {
-    file(versionFile).withInputStream { stream -> props.load(stream) }
-    version = props['version']
-} catch (e) {
-    // this happens, if either the properties file is not present, or cannot be read from
-    throw new GradleException("File ${versionFile} not found or unreadable. Aborting.", e)
-}
-
-task printVersion() {
-    doLast {
-        println(version)
-    }
+// --- Preprocessors ---
+ext {
+    extractLocation = "src/gen/jastadd-sources/relast.preprocessor"
 }
-
-task newVersion() {
-    doFirst {
-        def newProps = new Properties()
-        newProps['version'] = value
-        newProps.store(file(versionFile).newWriter(), null)
-    }
-}
-
 File genSrc = file("src/gen/java")
 sourceSets.main.java.srcDir genSrc
 idea.module.generatedSourceDirs += genSrc
 
-jar {
-    manifest {
-        attributes "Main-Class": 'org.jastadd.ragconnect.compiler.Compiler'
-    }
-
-    from {
-        configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
+task extractJastAddSources(type: Sync) {
+    dependsOn configurations.runtimeClasspath
+    configurations.runtimeClasspath.asFileTree.filter { it.toString().endsWith("relast-preprocessor-${preprocessor_version}.jar") }.collect {
+        from(zipTree(it)) {
+            include "**/*.jrag"
+            include "**/*.jadd"
+            include "**/*.ast"
+            include "**/*.relast"
+            include "**/*.flex"
+            include "**/*.parser"
+        }
     }
-    duplicatesStrategy = DuplicatesStrategy.EXCLUDE
-
-    archiveBaseName = 'ragconnect'
+    includeEmptyDirs false
+    into file("${extractLocation}")
 }
 
-File preprocessorGrammar = file('../relast-preprocessor/src/main/jastadd/RelAst.relast')
+File preprocessorGrammar = file("${extractLocation}/RelAst.relast")
 File ragConnectGrammar = file('./src/main/jastadd/RagConnect.relast')
-File intermediateGrammar = file('./src/main/jastadd/intermediate/MustacheNodes.relast')
-File mustacheGrammar = file('../relast-preprocessor/src/main/jastadd/mustache/Mustache.relast')
+File intermediateGrammar = file('./src/main/jastadd/Intermediate.relast')
+File mustacheGrammar = file("${extractLocation}/mustache/Mustache.relast")
+
 task relast(type: JavaExec) {
     group = 'Build'
-    main = "-jar"
+    classpath = configurations.relast
+    //noinspection GroovyAssignabilityCheck, GroovyAccessibility
+    mainClass = 'org.jastadd.relast.compiler.Compiler'
+
+    dependsOn extractJastAddSources
+
+    doFirst {
+        mkdir "src/gen/jastadd/"
+    }
 
     args = [
-            "../libs/relast.jar",
             preprocessorGrammar,
             ragConnectGrammar,
             intermediateGrammar,
@@ -116,52 +111,45 @@ clean {
     delete "src/gen/jastadd/RagConnectResolverStubs.jrag"
 }
 
+// --- JastAdd ---
 jastadd {
     configureModuleBuild()
     modules {
         //noinspection GroovyAssignabilityCheck
         module("RagConnect") {
 
-            java {
-                basedir ".."
-                include "relast-preprocessor/main/**/*.java"
-                include "relast-preprocessor/gen/**/*.java"
-                include "ragconnect.base/src/main/**/*.java"
-                include "ragconnect.base/src/gen/**/*.java"
-            }
-
             jastadd {
-                basedir ".."
-                include "relast-preprocessor/src/main/jastadd/**/*.ast"
-                include "relast-preprocessor/src/main/jastadd/**/*.jadd"
-                include "relast-preprocessor/src/main/jastadd/**/*.jrag"
-                include "ragconnect.base/src/main/jastadd/**/*.ast"
-                include "ragconnect.base/src/main/jastadd/**/*.jadd"
-                include "ragconnect.base/src/main/jastadd/**/*.jrag"
-                include "ragconnect.base/src/gen/jastadd/**/*.ast"
-                include "ragconnect.base/src/gen/jastadd/**/*.jadd"
-                include "ragconnect.base/src/gen/jastadd/**/*.jrag"
+                basedir "."
+                include "src/gen/jastadd-sources/relast.preprocessor/**/*.ast"
+                include "src/gen/jastadd-sources/relast.preprocessor/**/*.jadd"
+                include "src/gen/jastadd-sources/relast.preprocessor/**/*.jrag"
+                include "src/main/jastadd/**/*.ast"
+                include "src/main/jastadd/**/*.jadd"
+                include "src/main/jastadd/**/*.jrag"
+                include "src/gen/jastadd/**/*.ast"
+                include "src/gen/jastadd/**/*.jadd"
+                include "src/gen/jastadd/**/*.jrag"
             }
 
             scanner {
-                basedir ".."
-                include "ragconnect.base/src/main/jastadd/scanner/Header.flex",             [-5]
-                include "relast-preprocessor/src/main/jastadd/scanner/Preamble.flex",       [-4]
-                include "relast-preprocessor/src/main/jastadd/scanner/Macros.flex",         [-3]
-                include "ragconnect.base/src/main/jastadd/scanner/Macros.flex",             [-3]
-                include "relast-preprocessor/src/main/jastadd/scanner/RulesPreamble.flex",  [-2]
-                include "ragconnect.base/src/main/jastadd/scanner/MappingContent.flex",     [-1]
-                include "ragconnect.base/src/main/jastadd/scanner/Keywords.flex"
-                include "relast-preprocessor/src/main/jastadd/scanner/Keywords.flex"
-                include "relast-preprocessor/src/main/jastadd/scanner/Symbols.flex",        [1]
-                include "relast-preprocessor/src/main/jastadd/scanner/RulesPostamble.flex", [2]
+                basedir "."
+                include "src/main/jastadd/scanner/Header.flex",                                    [-5]
+                include "src/gen/jastadd-sources/relast.preprocessor/scanner/Preamble.flex",       [-4]
+                include "src/gen/jastadd-sources/relast.preprocessor/scanner/Macros.flex",         [-3]
+                include "src/main/jastadd/scanner/Macros.flex",                                    [-3]
+                include "src/gen/jastadd-sources/relast.preprocessor/scanner/RulesPreamble.flex",  [-2]
+                include "src/main/jastadd/scanner/MappingContent.flex",                            [-1]
+                include "src/main/jastadd/scanner/Keywords.flex"
+                include "src/gen/jastadd-sources/relast.preprocessor/scanner/Keywords.flex"
+                include "src/gen/jastadd-sources/relast.preprocessor/scanner/Symbols.flex",        [ 1]
+                include "src/gen/jastadd-sources/relast.preprocessor/scanner/RulesPostamble.flex", [ 2]
             }
 
             parser {
-                basedir ".."
-                include "ragconnect.base/src/main/jastadd/parser/Preamble.parser"
-                include "ragconnect.base/src/main/jastadd/parser/RagConnect.parser"
-                include "relast-preprocessor/src/main/jastadd/parser/RelAst.parser"
+                basedir "."
+                include "src/main/jastadd/parser/Preamble.parser"
+                include "src/main/jastadd/parser/RagConnect.parser"
+                include "src/gen/jastadd-sources/relast.preprocessor/parser/RelAst.parser"
             }
         }
     }
@@ -172,9 +160,7 @@ jastadd {
     }
 
     preprocessParser.doFirst {
-
         args += ["--no-beaver-symbol"]
-
     }
 
     module = "RagConnect"
@@ -193,33 +179,100 @@ jastadd {
     jastaddOptions = ["--lineColumnNumbers", "--List=JastAddList", "--safeLazy", "--visitCheck=true", "--rewrite=cnta", "--cache=all"]
 }
 
-generateAst.dependsOn relast
+// --- Versioning and Publishing ---
+group = 'de.tudresden.inf.st'
+
+ext {
+    mainClassName = 'org.jastadd.ragconnect.compiler.Compiler'
+}
+application.mainClassName = "${mainClassName}"
+
+def parametersTxtFileName = 'ragconnect.base/parameters.txt'
+task runParameters(type: JavaExec) {
+    doFirst {
+        if (!new File(parametersTxtFileName).exists()) {
+            throw new GradleException("Please create '${parametersTxtFileName}' to use this task.")
+        }
+    }
+    group 'application'
+    description 'Run using parameters.txt (line-separated with comments)'
+    classpath sourceSets.main.runtimeClasspath
+    main = "org.jastadd.ragconnect.compiler.Compiler"
+    try {
+        args new File(parametersTxtFileName).text.strip().split("\n").dropWhile { it.startsWith("#") }
+    } catch (FileNotFoundException ignored) { /* empty */ }
+    standardInput = System.in
+}
+
+jar {
+    manifest.attributes "Main-Class": "${mainClassName}"
+}
+
+task fatJar(type: Jar) {
+    dependsOn jar
+    group = "build"
+    //noinspection GroovyAssignabilityCheck, GroovyAccessibility
+    archiveAppendix = "fatjar"
+    from sourceSets.main.output
+    from {
+        configurations.runtimeClasspath.collect {it.isDirectory() ? it : zipTree(it) }
+    }
+
+    manifest.attributes "Main-Class": "${mainClassName}"
+}
+
+def versionFile = "src/main/resources/ragconnectVersion.properties"
+
+try {
+    def props = new Properties()
+    file(versionFile).withInputStream { stream -> props.load(stream) }
+    version = props['version']
+} catch (e) {
+    // this happens, if either the properties file is not present, or cannot be read from
+    throw new GradleException("File ${versionFile} not found or unreadable. Aborting.", e)
+}
+
+task printVersion() {
+    doLast {
+        println(version)
+    }
+}
+
+task newVersion() {
+    doFirst {
+        def newProps = new Properties()
+        newProps['version'] = value
+        newProps.store(file(versionFile).newWriter(), null)
+    }
+}
+
+task setDevVersionForCI() {
+    doFirst {
+        def props = new Properties()
+        props['version'] = version + "-$System.env.CI_PIPELINE_IID"
+        props.store(file(versionFile).newWriter(), null)
+    }
+}
 
-//708
 publishing {
     publications {
+        //noinspection GroovyAssignabilityCheck
         maven(MavenPublication) {
+            //noinspection GroovyAssignabilityCheck
             artifactId = 'ragconnect'
-            // Comment rs: components.java does not include relast.preprocessor
-            // from components.java
-            artifact("build/libs/ragconnect-${version}.jar") {
-                extension 'jar'
-            }
+            //noinspection GroovyAssignabilityCheck
+            from components.java
         }
     }
     repositories {
         maven {
-            url "https://git-st.inf.tu-dresden.de/api/v4/projects/708/packages/maven"
-            // Uncomment the following lines to publish manually (and comment out the other credentials section)
-//            credentials(HttpHeaderCredentials) {
-//                name = "Private-Token"
-//                value = gitLabPrivateToken // the variable resides in ~/.gradle/gradle.properties
-//            }
+            url "https://git-st.inf.tu-dresden.de/api/v4/projects/$System.env.CI_PROJECT_ID/packages/maven"
             credentials(HttpHeaderCredentials) {
                 name = 'Job-Token'
                 value = System.getenv("CI_JOB_TOKEN")
             }
             authentication {
+                //noinspection GroovyAssignabilityCheck
                 header(HttpHeaderAuthentication)
             }
         }
@@ -227,5 +280,12 @@ publishing {
     }
 }
 
+// --- Task order ---
+generateAst.dependsOn relast
 publish.dependsOn jar
-jar.dependsOn ":relast-preprocessor:jar"
+
+// --- Misc ---
+dependencyUpdates {
+    gradleReleaseChannel = 'current'
+    revision = 'release'
+}
diff --git a/ragconnect.base/src/main/jastadd/Analysis.jrag b/ragconnect.base/src/main/jastadd/Analysis.jrag
index ac891b0e819b1e57e135afced64495a76396db5e..79f37b38b3839982f784f1dd79b094a663aaa921 100644
--- a/ragconnect.base/src/main/jastadd/Analysis.jrag
+++ b/ragconnect.base/src/main/jastadd/Analysis.jrag
@@ -1,59 +1,60 @@
 aspect Analysis {
-  // --- lookupTokenEndpointDefinition ---
-  inh java.util.List<TokenEndpointDefinition> TokenEndpointDefinition.lookupTokenEndpointDefinitions(TokenComponent 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 : allEndpointDefinitionList()) {
-      if (def.isTokenEndpointDefinition() && def.asTokenEndpointDefinition().getToken().equals(token)) {
-        result.add(def.asTokenEndpointDefinition());
+  // --- isAlreadyDefined ---
+  syn boolean PortDefinition.isAlreadyDefined() = getPortTarget().isAlreadyDefined();
+  syn boolean PortTarget.isAlreadyDefined();
+  eq AttributePortTarget.isAlreadyDefined() {
+    // define lookup here, as not used elsewhere
+    int numberOfSameDefs = 0;
+    for (PortTarget target : ragconnect().givenPortTargetList()) {
+      if (target.isAttributePortTarget()) {
+        AttributePortTarget other = target.asAttributePortTarget();
+        if (other.getParentTypeDecl().equals(this.getParentTypeDecl()) && other.getName().equals(this.getName())) {
+          numberOfSameDefs += 1;
+        }
       }
     }
-    return result;
+    return numberOfSameDefs > 1;
   }
-
-  // --- lookupTypeEndpointDefinition ---
-  inh java.util.List<TypeEndpointDefinition> TypeEndpointDefinition.lookupTypeEndpointDefinitions(TypeComponent 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 : allEndpointDefinitionList()) {
-      if (def.isTypeEndpointDefinition() && def.asTypeEndpointDefinition().getType().equals(type)) {
-        result.add(def.asTypeEndpointDefinition());
+  eq RelationPortTarget.isAlreadyDefined() {
+    // define lookup here, as not used elsewhere
+    int numberOfSameDefs = 0;
+    for (PortTarget target : ragconnect().givenPortTargetList()) {
+      if (target.isRelationPortTarget()) {
+        RelationPortTarget other = target.asRelationPortTarget();
+        if (other.getRole().equals(this.getRole())) {
+          numberOfSameDefs += 1;
+        }
       }
     }
-    return result;
+    return numberOfSameDefs > 1;
   }
-
-  // --- lookupDependencyDefinition ---
-  inh DependencyDefinition DependencyDefinition.lookupDependencyDefinition(TypeDecl source, String id);
-  eq RagConnect.getConnectSpecificationFile().lookupDependencyDefinition(TypeDecl source, String id) {
-    for (DependencyDefinition def : allDependencyDefinitionList()) {
-      if (def.getID().equals(id) && def.getSource().containingTypeDecl().equals(source)) {
-        return def;
-      }
-    }
-    return null;
+  eq TokenPortTarget.isAlreadyDefined() {
+    return lookupTokenPortDefinitions(getToken()).stream()
+        .filter(containingPortDefinition()::matchesType)
+        .count() > 1;
   }
-
-  // --- isAlreadyDefined ---
-  syn boolean TokenEndpointDefinition.isAlreadyDefined() {
-    return lookupTokenEndpointDefinitions(getToken()).stream()
-      .filter(this::matchesType)
-      .count() > 1;
+  eq TypePortTarget.isAlreadyDefined() {
+    return lookupGivenTypePortDefinitions(getType()).stream()
+        .filter(containingPortDefinition()::matchesType)
+        .count() > 1;
+  }
+  eq ContextFreeTypePortTarget.isAlreadyDefined() {
+    return lookupContextFreeTypePortDefinitions(getTypeDecl()).stream()
+        .filter(containingPortDefinition()::matchesType)
+        .count() > 1;
   }
   syn boolean DependencyDefinition.isAlreadyDefined() = lookupDependencyDefinition(getSource().containingTypeDecl(), getID()) != this;
 
   // --- matchesType ---
-  syn boolean TokenEndpointDefinition.matchesType(TokenEndpointDefinition other);
-  eq ReceiveTokenEndpointDefinition.matchesType(TokenEndpointDefinition other) = other.isReceiveTokenEndpointDefinition();
-  eq SendTokenEndpointDefinition.matchesType(TokenEndpointDefinition other) = other.isSendTokenEndpointDefinition();
+  syn boolean PortDefinition.matchesType(PortDefinition other) = this.getSend() == other.getSend();
 
   // --- assignableTo ---
   syn boolean MappingDefinitionType.assignableTo(JavaTypeUse target);
   eq JavaMappingDefinitionType.assignableTo(JavaTypeUse target) = getType().assignableTo(target);
   eq JavaArrayMappingDefinitionType.assignableTo(JavaTypeUse target) {
-    if (!target.getName().endsWith("[]")) { return false; }
+    if (!target.getName().endsWith("[]")) {
+      return false;
+    }
     return getType().assignableTo(new SimpleJavaTypeUse(target.getName().replace("[]", "")));
   }
   syn boolean JavaTypeUse.assignableTo(JavaTypeUse target) {
@@ -61,44 +62,53 @@ aspect Analysis {
     return target.primitivePrettyPrint().equals(this.primitivePrettyPrint());
   }
   syn String JavaTypeUse.primitivePrettyPrint() {
-    switch(getName()) {
+    String name = getName();
+    switch (name) {
       case "boolean":
-      case "Boolean": return "boolean";
+      case "Boolean":
+        return "boolean";
       case "int":
-      case "Integer": return "int";
+      case "Integer":
+        return "int";
       case "short":
-      case "Short": return "short";
+      case "Short":
+        return "short";
       case "long":
-      case "Long": return "long";
+      case "Long":
+        return "long";
       case "float":
-      case "Float": return "float";
+      case "Float":
+        return "float";
       case "double":
-      case "Double": return "double";
+      case "Double":
+        return "double";
       case "char":
-      case "Character": return "char";
-      default: return getName();
+      case "Character":
+        return "char";
+      default:
+        return getName();
     }
   }
 
-  // --- shouldSendValue ---
-  syn boolean TokenEndpointDefinition.shouldSendValue() = isSendTokenEndpointDefinition() && !getToken().getNTA();
-  syn boolean TypeEndpointDefinition.shouldSendValue() = isSendTypeEndpointDefinition() && !getType().getNTA();
+  syn boolean PortTarget.hasAttributeResetMethod();
+  eq AttributePortTarget.hasAttributeResetMethod() = false;
+  eq RelationPortTarget.hasAttributeResetMethod() = false;
+  eq TokenPortTarget.hasAttributeResetMethod() = getToken().getNTA();
+  eq TypePortTarget.hasAttributeResetMethod() = getType().getNTA();
+  eq ContextFreeTypePortTarget.hasAttributeResetMethod() = false;
 
   // --- needProxyToken ---
-  syn boolean TokenComponent.needProxyToken() = !getDependencySourceDefinitionList().isEmpty() || getTokenEndpointDefinitionList().stream().anyMatch(TokenEndpointDefinition::shouldSendValue);
+  syn boolean TokenComponent.needProxyToken() = !getDependencySourceDefinitionList().isEmpty() ||
+          getTokenPortTargetList().stream()
+                  .map(PortTarget::containingPortDefinition)
+                  .anyMatch(PortDefinition::shouldNotResetValue);
 
   // --- effectiveUsedAt ---
-  coll Set<EndpointDefinition> MappingDefinition.effectiveUsedAt()
-    [new java.util.HashSet<EndpointDefinition>()]
+  coll Set<PortDefinition> MappingDefinition.effectiveUsedAt()
+    [new java.util.HashSet<PortDefinition>()]
     root RagConnect;
-  EndpointDefinition contributes this
+  PortDefinition contributes this
     to MappingDefinition.effectiveUsedAt()
     for each effectiveMappings();
 
-  // --- typeIsList ---
-  syn boolean EndpointDefinition.typeIsList() = false;
-  eq TypeEndpointDefinition.typeIsList() {
-    return getType().isListComponent();
-  }
-
 }
diff --git a/ragconnect.base/src/main/jastadd/Configuration.jadd b/ragconnect.base/src/main/jastadd/Configuration.jadd
index 8fd0e64b14bbd9a05c7cbf9b7de59ed0cd216d78..9a4645b515e3d690ae4c4b2cdecaac26da516a88 100644
--- a/ragconnect.base/src/main/jastadd/Configuration.jadd
+++ b/ragconnect.base/src/main/jastadd/Configuration.jadd
@@ -1,11 +1,3 @@
-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 String ASTNode.JastAddList = "List";
-  public static boolean ASTNode.usesMqtt;
-  public static boolean ASTNode.usesRest;
-  public static boolean ASTNode.incrementalOptionActive;
-  public static boolean ASTNode.experimentalJastAdd329;
+aspect ConfigurationShortcuts {
+  syn TypeDecl RagConnect.rootNode() = getConfiguration().getRootNode();
 }
diff --git a/ragconnect.base/src/main/jastadd/Errors.jrag b/ragconnect.base/src/main/jastadd/Errors.jrag
index 770e506cc5c0145a0ba9979d57a1086a399ed39e..5e15584f2188ce2d52ad74c60444a3be0e27eede 100644
--- a/ragconnect.base/src/main/jastadd/Errors.jrag
+++ b/ragconnect.base/src/main/jastadd/Errors.jrag
@@ -1,40 +1,56 @@
 aspect Errors {
-  coll Set<ErrorMessage> RagConnect.errors()
-    [new TreeSet<ErrorMessage>()]
+  coll Set<CompilerMessage> RagConnect.errors()
+    [new TreeSet<CompilerMessage>()]
     root RagConnect;
 
-    ReceiveTokenEndpointDefinition contributes error("Receive definition already defined for " + getToken().getName())
-      when isAlreadyDefined()
-      to RagConnect.errors();
+  PortDefinition contributes error("Port definition already defined for " + parentTypeName() + "." + entityName())
+    when isAlreadyDefined()
+    to RagConnect.errors();
 
-    ReceiveTokenEndpointDefinition contributes error("Receiving target token must not be an NTA token!")
-      when getToken().getNTA()
-      to RagConnect.errors();
+  PortDefinition contributes error("Receiving target token must not be an NTA token!")
+    when !getSend() && getPortTarget().isTokenPortTarget() && token().getNTA()
+    to RagConnect.errors();
 
-    // if first mapping is null, then suitableDefaultMapping() == null
-    ReceiveTokenEndpointDefinition contributes error("No suitable default mapping found for type " +
-      ((getMappingList().isEmpty())
-        ? getToken().effectiveJavaTypeUse().prettyPrint()
-        : getMappingList().get(0).getFromType().prettyPrint()))
-      when effectiveMappings().get(0) == null
-      to RagConnect.errors();
-
-    ReceiveTokenEndpointDefinition contributes error("to-type of last mapping (" + effectiveMappings().get(effectiveMappings().size() - 1).getToType().prettyPrint() + ") not assignable to type of the Token (" + getToken().effectiveJavaTypeUse().prettyPrint() + ")!")
-      when !effectiveMappings().get(effectiveMappings().size() - 1).getToType().assignableTo(
-          getToken().effectiveJavaTypeUse())
-      to RagConnect.errors();
-
-    SendTokenEndpointDefinition contributes error("Send definition already defined for " + getToken().getName())
-      when isAlreadyDefined()
-      to RagConnect.errors();
+  PortDefinition contributes error("Indexed based list access may only be used for type port targets!")
+    when getIndexBasedListAccess() && !getPortTarget().isTypePortTarget()
+    to RagConnect.errors();
 
-    DependencyDefinition contributes error("Dependency definition already defined for " + getSource().containingTypeDecl().getName() + " with name " + getID())
-      when isAlreadyDefined()
-      to RagConnect.errors();
-
-    DependencyDefinition contributes error("The name of a dependency definition must not be equal to a list-node on the source")
-      when isAlreadyDefinedAsList()
-      to RagConnect.errors();
+  // if first mapping is null, then suitableDefaultMapping() == null
+  PortDefinition contributes error("No suitable default mapping found for type " +
+      ((getMappingList().isEmpty())
+          ? token().effectiveJavaTypeUse().prettyPrint()
+          : getMappingList().get(0).getFromType().prettyPrint()))
+    when !getSend() && getPortTarget().isTokenPortTarget() && effectiveMappings().get(0) == null
+    to RagConnect.errors();
+
+  PortDefinition contributes error("to-type of last mapping (" +
+      effectiveMappings().get(effectiveMappings().size() - 1).getToType().prettyPrint() +
+      ") not assignable to type of the token (" + token().effectiveJavaTypeUse().prettyPrint() + ")!")
+    when !getSend() && getPortTarget().isTokenPortTarget() &&
+      !effectiveMappings().get(effectiveMappings().size() - 1).getToType().assignableTo(
+          token().effectiveJavaTypeUse())
+    to RagConnect.errors();
+
+  UntypedPortTarget contributes error("Could not resolve port target " + getTypeName() + "." + getChildName())
+    to RagConnect.errors();
+
+  ContextFreeTypePortTarget contributes error("Context-Free port not allowed for root node " +
+      getTypeDecl().getName() + "!")
+    when getTypeDecl().occurencesInProductionRules().isEmpty()
+    to RagConnect.errors();
+
+  PortDefinition contributes error("Clash with implied, indexed port definition of context-free port in line " +
+          clashingContextFreePortDefinition().getStartLine() + "!")
+    when clashingContextFreePortDefinition() != null && clashingContextFreePortDefinition().matchesType(this)
+    to RagConnect.errors();
+
+  DependencyDefinition contributes error("Dependency definition already defined for " + getSource().containingTypeDecl().getName() + " with name " + getID())
+    when isAlreadyDefined()
+    to RagConnect.errors();
+
+  DependencyDefinition contributes error("The name of a dependency definition must not be equal to a list-node on the source")
+    when isAlreadyDefinedAsList()
+    to RagConnect.errors();
 }
 
 aspect ErrorHelpers {
@@ -46,64 +62,20 @@ aspect ErrorHelpers {
     }
     return false;
   }
-  syn String TokenComponent.parentTypeDeclAndName() = containingTypeDecl().getName() + "." + getName();
-}
 
-aspect ErrorMessage {
-  public class ErrorMessage implements Comparable<ErrorMessage> {
-    private final ASTNode node;
-    private final String filename;
-    private final int line;
-    private final int col;
-    private final String message;
-
-    public ErrorMessage(ASTNode node, String message) {
-      this.node = node;
-      this.filename = node.containedFileName();
-      this.line = node.getStartLine();
-      this.col = node.getStartColumn();
-      this.message = message;
-    }
-
-    public ASTNode getNode() {
-      return node;
-    }
-    public int getLine() {
-      return line;
-    }
-    public int getCol() {
-      return col;
+  syn PortDefinition PortDefinition.clashingContextFreePortDefinition() {
+    if (getSend() || !typeIsList() || getIndexBasedListAccess()) {
+      return null;
     }
-    public String getMessage() {
-      return message;
-    }
-
-    public String toString() {
-      return filename + " Line " + line + ", column " + col + ": " + message;
-    }
-
-    @Override
-    public int compareTo(ErrorMessage err) {
-      int n = filename.compareTo(err.filename);
-      if (n != 0) {
-        return n;
-      }
-
-      n = line - err.line;
-      if (n != 0) {
-        return n;
-      }
-
-      n = col-err.col;
-      if (n != 0) {
-        return n;
-      }
-
-      return message.compareTo(err.message);
+    List<PortDefinition> contextFreePortsWithSameType = lookupContextFreeTypePortDefinitions(
+            getPortTarget().asTypePortTarget().getType().getTypeDecl());
+    if (!contextFreePortsWithSameType.isEmpty()) {
+      return contextFreePortsWithSameType.get(0);
     }
+    return null;
   }
 
-  protected ErrorMessage ASTNode.error(String message) {
-    return new ErrorMessage(this, message);
+  protected CompilerMessage ASTNode.error(String message) {
+    return new CompilerMessage(this, message);
   }
 }
diff --git a/ragconnect.base/src/main/jastadd/Handlers.jrag b/ragconnect.base/src/main/jastadd/Handlers.jrag
new file mode 100644
index 0000000000000000000000000000000000000000..e64a8c5d0db5467f9a4b0d7451e8c38ab22a8bbf
--- /dev/null
+++ b/ragconnect.base/src/main/jastadd/Handlers.jrag
@@ -0,0 +1,15 @@
+aspect RagConnectHandlers {
+  syn Handler RagConnect.javaHandler() = resolveHandlerByName("java");
+  syn Handler RagConnect.mqttHandler() = resolveHandlerByName("mqtt");
+  syn Handler RagConnect.restHandler() = resolveHandlerByName("rest");
+
+  private Handler RagConnect.resolveHandlerByName(String uniqueName) {
+    for (Handler handler : getHandlerList()) {
+      if (uniqueName.equals(handler.getUniqueName())) {
+        return handler;
+      }
+    }
+    System.err.println("Could not find handler with name '" + uniqueName + "'");
+    return null;
+  }
+}
diff --git a/ragconnect.base/src/main/jastadd/Intermediate.jadd b/ragconnect.base/src/main/jastadd/Intermediate.jadd
new file mode 100644
index 0000000000000000000000000000000000000000..97cc5148e9e6901d8a1b23cc965adbd57a7c0d44
--- /dev/null
+++ b/ragconnect.base/src/main/jastadd/Intermediate.jadd
@@ -0,0 +1,803 @@
+/*
+Design considerations
+- InnerMappingDefinition needed for iteration attribute (first, last) - not possible with list-relation
+- no complete intermediate structure, but instead single nodes where applicable/needed
+*/
+aspect SharedMustache {
+  // === RagConnect ===
+  syn boolean RagConnect.configIncrementalOptionActive() = getConfiguration().getIncrementalOptionActive();
+
+  syn String RagConnect.configJastAddList() = getConfiguration().getJastAddList();
+
+  syn String RagConnect.configJastAddOpt() = getConfiguration().getJastAddOpt();
+
+  syn boolean RagConnect.configLoggingEnabledForIncremental() = getConfiguration().getLoggingEnabledForIncremental();
+
+  syn boolean RagConnect.configExperimentalJastAdd329() = getConfiguration().getExperimentalJastAdd329();
+
+  syn boolean RagConnect.configEvaluationCounter() = getConfiguration().getEvaluationCounter();
+
+  syn String RagConnect.evaluationCounterVariable() = internalRagConnectPrefix() + "evaluationCounter";
+
+  syn String RagConnect.internalRagConnectPrefix() = "_ragconnect_";
+
+  syn String RagConnect.logDebug() = logStatement("debug");
+  syn String RagConnect.logInfo() = logStatement("info");
+  syn String RagConnect.logWarn() = logStatement("warn");
+  syn String RagConnect.logError() = logStatement("error");
+  syn String RagConnect.logException() = logStatement("exception");
+  syn String RagConnect.log_() {
+    switch (getConfiguration().getLoggingTarget()) {
+      //noinspection ConstantConditions
+      case "console":
+        return "%s";
+      //noinspection ConstantConditions
+      case "slf4j":
+        return "{}";
+      default:
+        return "?";
+    }
+  }
+
+  syn String RagConnect.observerInstanceSingletonMethodName() = internalRagConnectPrefix() + "Observer";
+  syn String RagConnect.observerInstanceResetMethodName() = internalRagConnectPrefix() + "resetObserver";
+
+  syn String RagConnect.rootNodeName() = getConfiguration().getRootNode().getName();
+
+  // === PortDefinition ===
+  syn String PortDefinition.lastResult() = lastDefinition().outputVarName();
+
+  syn boolean PortDefinition.typeIsList() = getPortTarget().typeIsList();
+
+  syn boolean PortDefinition.typeIsOpt() = getPortTarget().typeIsOpt();
+
+  // === attributes needed for computing above ones ===
+  syn String RagConnect.logStatement(String level) {
+    switch (getConfiguration().getLoggingTarget()) {
+      //noinspection ConstantConditions
+      case "console":
+        switch (level) {
+          case "debug":
+          case "info":
+            return "ASTNode." + logConsoleOut();
+          case "warn":
+          case "error":
+          case "exception":
+            //noinspection DuplicateBranchesInSwitch
+            return "ASTNode." + logConsoleErr();
+          default:
+            return "unknownLoggingLevelForConsole_" + level + "_";
+        }
+      //noinspection ConstantConditions
+      case "slf4j":
+        if (level.equals("exception")) {
+          level = "error";
+        }
+        return "org.slf4j.LoggerFactory.getLogger(getClass())." + level;
+      default:
+        return "unknownLoggingOptionGiven_" + getConfiguration().getLoggingTarget() + "_";
+    }
+  }
+  syn boolean PortTarget.typeIsList() = false;
+  eq TypePortTarget.typeIsList() = getType().isListComponent();
+  eq RelationPortTarget.typeIsList() = getRole().isListRole();
+
+  syn boolean PortTarget.typeIsOpt() = false;
+  eq TypePortTarget.typeIsOpt() {
+    return getType().isOptComponent();
+  }
+}
+
+aspect MustacheDependencyDefinition {
+  // === DependencyDefinition ===
+  syn String DependencyDefinition.dependencyMethodName() = "add" + capitalize(getID());
+
+  syn String DependencyDefinition.internalRelationPrefix() = ragconnect().internalRagConnectPrefix() + getID();
+
+  syn String DependencyDefinition.sourceParentTypeName() = getSource().containingTypeDecl().getName();
+
+  syn String DependencyDefinition.targetParentTypeName() = getTarget().containingTypeDecl().getName();
+}
+
+aspect MustacheHandler {
+  // === RagConnect ===
+  syn String RagConnect.closeMethodName() = "ragconnectCloseConnections";
+
+  syn String RagConnect.evaluationCounterInnerClass() = internalRagConnectPrefix() + "Counter";
+
+  syn String RagConnect.evaluationCounterSummaryMethodName() = "ragconnectEvaluationCounterSummary";
+
+  syn boolean RagConnect.hasRootTypeComponents() = !rootTypeComponents().isEmpty();
+
+  syn List<TypeComponent> RagConnect.rootTypeComponents() {
+    List<TypeComponent> result = new ArrayList<>();
+    for (Component child : rootNode().getComponentList()) {
+      if (child.isTypeComponent()) {
+        result.add(child.asTypeComponent());
+      }
+    }
+    return result;
+  }
+
+  // === Handler ===
+  syn String Handler.attributeName() = ragconnect().internalRagConnectPrefix() + getUniqueName() + "Handler";
+
+  syn String Handler.constructionSnippet() = "new " + getClassName() + "(\"Handler for " + ragconnect().rootNodeName() + ".\" + this.hashCode())";
+
+  syn String Handler.fieldName() = ragconnect().internalRagConnectPrefix() + getUniqueName() + "Handler";
+
+  syn String Handler.pushMethodName() = "ragconnect" + capitalize(getUniqueName()) + "Push";
+
+  syn String Handler.registerConsumerMethodName() = "ragconnect" + capitalize(getUniqueName()) + "RegisterConsumer";
+
+  syn String Handler.setupWaitUntilReadyMethodName() = "ragconnectSetup" + capitalize(getUniqueName()) + "WaitUntilReady";
+}
+
+aspect MustacheHandleUri { /* empty */ }
+
+aspect MustacheListAspect {
+  // === RagConnect ===
+  syn boolean RagConnect.hasTreeListPorts() {
+    for (PortDefinition portDef : allPortDefinitionList()) {
+      if (portDef.typeIsList()) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  syn Set<TypeDecl> RagConnect.typesForReceivingListPorts() {
+    Set<TypeDecl> result = new HashSet<>();
+    for (PortDefinition portDef : allPortDefinitionList()) {
+      if (portDef.typeIsList() && !portDef.getSend()) {
+        result.add(portDef.type().getTypeDecl());
+      }
+    }
+    return result;
+  }
+}
+
+aspect MustacheMappingApplicationAndDefinition {
+  // === PortDefinition ===
+  syn String PortDefinition.condition() {
+    // TODO [OLD] probably, this has to be structured in a better way
+    if (lastDefinition().getMappingDefinition().getToType().isArray()) {
+      return "java.util.Arrays.equals(" + preemptiveExpectedValue() + ", " + lastResult() + ")";
+    }
+    if (getPortTarget().isTokenPortTarget() && token().isPrimitiveType() && lastDefinition().getMappingDefinition().getToType().isPrimitiveType()) {
+      return preemptiveExpectedValue() + " == " + lastResult();
+    }
+    if (!getSend() && getPortTarget().isTypePortTarget() && getWithAdd()) {
+      // only check if received list is not null
+      return lastResult() + " == null";
+    }
+    String toPrepend = "";
+    if (!getSend() && getIndexBasedListAccess() && typeIsList() && !getWithAdd()) {
+      // for wildcard (list) receive connect
+      toPrepend = "index >= 0 && ";
+    }
+    if (getPortTarget().isTypePortTarget() && type().isOptComponent()) {
+      // use "hasX()" instead of "getX() != null" for optionals
+      return toPrepend + "has" + typeName() + "()" + " && " + preemptiveExpectedValue() + ".equals(" + lastResult() + ")";
+    }
+    if (lastDefinition().getMappingDefinition().getToType().isPrimitiveType() || lastDefinition().getMappingDefinition().isDefaultMappingDefinition()) {
+      return toPrepend + preemptiveExpectedValue() + " != null && " + preemptiveExpectedValue() + ".equals(" + lastResult() + ")";
+    }
+    return toPrepend + preemptiveExpectedValue() + " != null ? " + preemptiveExpectedValue() + ".equals(" + lastResult() + ") : " + lastResult() + " == null";
+  }
+
+  syn JastAddList<MInnerMappingDefinition> PortDefinition.innerMappingDefinitions() = toMustache().getInnerMappingDefinitionList();
+
+  syn String PortDefinition.lastDefinitionToType() = lastDefinition().toType();
+
+  syn String PortDefinition.preemptiveReturn() = toMustache().preemptiveReturn();
+
+  // === (MInner)MappingDefinition ===
+  syn String MappingDefinition.fromType() = getFromType().prettyPrint();
+
+  inh boolean MInnerMappingDefinition.isLast();
+  eq MPortDefinition.getInnerMappingDefinition(int i).isLast() {
+    return i == getNumInnerMappingDefinition() - 1;
+  }
+
+  inh String MInnerMappingDefinition.inputVarName();
+  eq MPortDefinition.getInnerMappingDefinition(int i).inputVarName() {
+    return i == 0
+        ? firstInputVarName()
+        : getInnerMappingDefinition(i - 1).outputVarName();
+  }
+
+  syn String MInnerMappingDefinition.methodName() = getMappingDefinition().methodName();
+  syn String MappingDefinition.methodName() = ragconnect().internalRagConnectPrefix() + "_apply_" + getID();
+
+  syn String MInnerMappingDefinition.outputVarName() = "result" + methodName();  // we do not need "_" in between here, because methodName begins with one
+
+  syn String MInnerMappingDefinition.toType() = getMappingDefinition().toType();
+  syn String MappingDefinition.toType() = getToType().prettyPrint();
+
+  // === attributes needed for computing above ones ===
+  syn String PortDefinition.preemptiveExpectedValue() = toMustache().preemptiveExpectedValue();
+  syn String PortDefinition.firstInputVarName() = toMustache().firstInputVarName();
+
+  syn String MPortDefinition.preemptiveExpectedValue();
+  syn String MPortDefinition.preemptiveReturn();
+  syn String MPortDefinition.firstInputVarName();
+
+  eq MAttributeSendDefinition.firstInputVarName() = getterMethodCall();
+  eq MAttributeSendDefinition.preemptiveExpectedValue() = lastValueGetterCall();
+  eq MAttributeSendDefinition.preemptiveReturn() = "return false;";
+
+  eq MRelationSendDefinition.firstInputVarName() = getterMethodCall();
+  eq MRelationSendDefinition.preemptiveExpectedValue() = lastValueGetterCall();
+  eq MRelationSendDefinition.preemptiveReturn() = "return false;";
+
+  eq MTokenReceiveDefinition.firstInputVarName() = "message";
+  eq MTokenReceiveDefinition.preemptiveExpectedValue() = getterMethodCall();
+  eq MTokenReceiveDefinition.preemptiveReturn() = "return;";
+
+  eq MTokenSendDefinition.firstInputVarName() = getterMethodCall();
+  eq MTokenSendDefinition.preemptiveExpectedValue() = lastValueGetterCall();
+  eq MTokenSendDefinition.preemptiveReturn() = "return false;";
+
+  eq MTypeReceiveDefinition.firstInputVarName() = "message";
+  eq MTypeReceiveDefinition.preemptiveExpectedValue() = getterMethodCall();
+  eq MTypeReceiveDefinition.preemptiveReturn() = "return;";
+
+  eq MTypeSendDefinition.firstInputVarName() = getterMethodCall();
+  eq MTypeSendDefinition.preemptiveExpectedValue() = lastValueGetterCall();
+  eq MTypeSendDefinition.preemptiveReturn() = "return false;";
+
+  eq MContextFreeTypeReceiveDefinition.firstInputVarName() = "message";
+  eq MContextFreeTypeReceiveDefinition.preemptiveExpectedValue() = "this";
+  eq MContextFreeTypeReceiveDefinition.preemptiveReturn() = "return;";
+
+  eq MContextFreeTypeSendDefinition.firstInputVarName() = null;
+  eq MContextFreeTypeSendDefinition.preemptiveExpectedValue() = null;
+  eq MContextFreeTypeSendDefinition.preemptiveReturn() = null;
+
+  syn String MPortDefinition.parentTypeName() = getPortDefinition().parentTypeName();
+
+  syn String MPortDefinition.getterMethodCall() = getPortDefinition().getterMethodCall();
+}
+
+aspect MustacheRagConnect {
+  // === RagConnect ===
+  syn List<DependencyDefinition> RagConnect.allDependencyDefinitionList() {
+    List<DependencyDefinition> result = new ArrayList<>();
+    for (ConnectSpecification spec : getConnectSpecificationFileList()) {
+      spec.getDependencyDefinitionList().forEach(result::add);
+    }
+    return result;
+  }
+
+  syn List<PortDefinition> RagConnect.allPortDefinitionList() {
+    List<PortDefinition> result = new ArrayList<>();
+    // first gather all user-defined port definitions, that are not context-free
+    for (PortDefinition def : givenPortDefinitionList()) {
+      if (!def.hasContextFreeTypePortTarget()) {
+        result.add(def);
+      }
+    }
+    // then check for additional ports, and add if no conflict with existing definitions exists
+    for (PortDefinition def : givenPortDefinitionList()) {
+      def.getPortTarget().impliedPortDefinitions().iterator().forEachRemaining(result::add);
+    }
+
+    return result;
+  }
+
+  syn List<TypeDecl> RagConnect.allTypeDecls() {
+    return getProgram().typeDecls().stream()
+            .sorted(java.util.Comparator.comparing(TypeDecl::getName))
+            .collect(java.util.stream.Collectors.toList());
+  }
+
+  // > allMappingDefinitions in Mappings.jrag
+
+  syn String RagConnect.observerInstanceFieldName() = internalRagConnectPrefix() + "ObserverInstance";
+
+  syn String RagConnect.logConsoleOut() = internalRagConnectPrefix() + "logStdOut";
+  syn String RagConnect.logConsoleErr() = internalRagConnectPrefix() + "logStdErr";
+
+  syn java.util.List<Component> TypeDecl.tokenComponents() {
+    return filteredComponents(Component::isTokenComponent);
+  }
+  syn java.util.List<Component> TypeDecl.normalComponents() {
+    return filteredComponents(comp -> comp.isTypeComponent() && !comp.isListComponent());
+  }
+  syn java.util.List<Component> TypeDecl.listComponents() {
+    return filteredComponents(Component::isListComponent);
+  }
+
+  syn List<TokenComponent> RagConnect.tokenComponentsThatNeedProxy() {
+    List<TokenComponent> result = new ArrayList<>();
+    for (TokenComponent token : getProgram().allTokenComponents()) {
+      if (token.needProxyToken()) {
+        result.add(token);
+      }
+    }
+    return result;
+  }
+
+  syn String RagConnect.touchedTerminalsMethodName() = internalRagConnectPrefix() + "touchedTerminals";
+
+  syn List<TypeDecl> RagConnect.typeDeclsOfContextFreePortTargets() {
+    List<TypeDecl> result = new ArrayList<>();
+    for (PortTarget target : givenPortTargetList()) {
+      if (target.isContextFreeTypePortTarget()) {
+        result.add(target.asContextFreeTypePortTarget().getTypeDecl());
+      }
+    }
+    return result;
+  }
+
+  // === MappingDefinition ===
+  syn boolean MappingDefinition.isUsed() = !effectiveUsedAt().isEmpty();
+  eq SerializeListMapping.isUsed() = ragconnect().defaultListToBytesMapping().isUsed();
+  eq SerializeJavaUtilListMapping.isUsed() = ragconnect().defaultJavaUtilListToBytesMapping().isUsed();
+  eq DeserializeListMapping.isUsed() = program().typeDecls().stream().anyMatch(
+          typeDecl -> ragconnect().defaultBytesToListMapping(typeDecl.getName()).isUsed());
+
+  // === attributes needed for computing above ones ===
+  syn List<PortDefinition> RagConnect.givenPortDefinitionList() {
+    List<PortDefinition> result = new ArrayList<>();
+    for (ConnectSpecification spec : getConnectSpecificationFileList()) {
+      spec.getPortDefinitionList().forEach(result::add);
+    }
+    return result;
+  }
+
+  syn nta JastAddList<PortDefinition> PortTarget.impliedPortDefinitions() = new JastAddList<>();
+//  eq TypePortTarget.impliedPortDefinitions() {
+//    JastAddList<PortDefinition> result = super.impliedPortDefinitions();
+//    if (!getSend() || !typeIsList() || !getIndexBasedListAccess()) {
+//      return result;
+//    }
+//    // create a new port
+//  }
+  eq ContextFreeTypePortTarget.impliedPortDefinitions() {
+    JastAddList<PortDefinition> result = super.impliedPortDefinitions();
+    PortDefinition containingDef = containingPortDefinition();
+    for (TypeComponent typeComponent : getTypeDecl().occurencesInProductionRules()) {
+      List<PortDefinition> defsForTypeComponent = lookupGivenTypePortDefinitions(typeComponent);
+      if (!defsForTypeComponent.stream().anyMatch(containingDef::matchesType)) {
+        // there is no user-defined port definition for this typeComponent yet
+        // -> create a new port definition with the same options and mappings as the context-free def
+        //    (except indexed-based for list-types)
+        PortDefinition newDef = new PortDefinition();
+        newDef.setAlwaysApply(containingDef.getAlwaysApply());
+        newDef.setIndexBasedListAccess(typeComponent.isListComponent());
+        newDef.setSend(containingDef.getSend());
+        containingDef.getMappings().forEach(newDef::addMapping);
+
+        TypePortTarget target = new TypePortTarget();
+        target.setType(typeComponent);
+        newDef.setPortTarget(target);
+
+        result.add(newDef);
+      }
+    }
+    return result;
+  }
+
+  private java.util.List<Component> TypeDecl.filteredComponents(java.util.function.Predicate<Component> filter) {
+    java.util.List<Component> result = new java.util.ArrayList<>();
+    for (Component comp : getComponentList()) {
+      if (filter.test(comp)) {
+        result.add(comp);
+      }
+    }
+    return result;
+  }
+}
+
+aspect MustacheReceiveAndSendAndHandleUri {
+  // === PortDefinition ===
+  syn String PortDefinition.connectMethodName() = "connect" + capitalize(entityName());
+
+  syn String PortDefinition.connectParameterName() = "uriString";
+
+  syn String PortDefinition.disconnectMethodName() {
+    // if both (send and receive) are defined for an port, ensure methods with different names
+    String extra;
+    if (getPortTarget().isTokenPortTarget()) {
+      extra = lookupTokenPortDefinitions(token()).size() > 1 ? uniqueSuffix() : "";
+    } else if (getPortTarget().isTypePortTarget()) {
+      // here it has to be checked if there are ANY typePortDefinitions within all ports (including implied)
+      extra = lookupAllTypePortDefinitions(type()).size() > 1 ? uniqueSuffix() : "";
+    } else if (getPortTarget().isContextFreeTypePortTarget()) {
+      // here it has to be checked if there are ANY typePortDefinitions within all ports (including implied)
+      boolean needExtra = false;
+      for (TypeComponent typeComponent : getPortTarget().asContextFreeTypePortTarget().getTypeDecl().occurencesInProductionRules()) {
+        if (lookupAllTypePortDefinitions(typeComponent).size() > 1) {
+          needExtra = true;
+          break;
+        }
+      }
+      extra = needExtra ? uniqueSuffix() : "";
+    } else {
+      extra = "";
+    }
+    return "disconnect" + extra + capitalize(entityName());
+  }
+
+  syn String PortDefinition.entityName() = getPortTarget().entityName();
+
+  syn boolean PortDefinition.indexedSend() = getSend() && indexedList();
+  syn boolean PortDefinition.indexedList() = typeIsList() && getIndexBasedListAccess();
+
+  syn String PortDefinition.getterMethodName() = getPortTarget().getterMethodName();
+  syn String PortDefinition.getterMethodCall() = getPortTarget().getterMethodCall();
+  syn String PortDefinition.realGetterMethodName() = getPortTarget().realGetterMethodName();
+  syn String PortDefinition.realGetterMethodCall() = getPortTarget().realGetterMethodCall();
+
+  syn String PortDefinition.parentTypeName() = getPortTarget().parentTypeName();
+
+  // === attributes needed for computing above ones ===
+  syn String PortTarget.getterMethodName();
+  syn String PortTarget.getterMethodCall() = getterMethodName() + "()";
+  syn String PortTarget.realGetterMethodName() = getterMethodName() + "NoTransform";
+  syn String PortTarget.realGetterMethodCall() = realGetterMethodName() + "()";
+  syn String PortTarget.parentTypeName();
+  syn String PortTarget.entityName();
+
+  eq AttributePortTarget.getterMethodName() = getName();
+  eq AttributePortTarget.parentTypeName() = getParentTypeDecl().getName();
+  eq AttributePortTarget.entityName() = getName();
+
+  eq RelationPortTarget.getterMethodName() = forwardingName();
+  eq RelationPortTarget.parentTypeName() = getRole().getType().getName();
+  eq RelationPortTarget.entityName() = getRole().getName();
+  eq RelationPortTarget.realGetterMethodName() = "get" + getRole().getterMethodName();
+  eq RelationPortTarget.realGetterMethodCall() = realGetterMethodName() + (containingPortDefinition().indexedSend() ? "(index)" : "()");
+
+  syn String NavigableRole.getterMethodName() = getName();
+  eq ListRole.getterMethodName() = getName() + "List";
+
+  eq TokenPortTarget.getterMethodName() = "get" + getToken().getName();
+  eq TokenPortTarget.parentTypeName() = getToken().containingTypeDecl().getName();
+  eq TokenPortTarget.entityName() = getToken().getName();
+
+  eq TypePortTarget.getterMethodName() = getterMethodeNameHelper(true);
+  eq TypePortTarget.getterMethodCall() = getterMethodName() + (containingPortDefinition().indexedList() && !containingPortDefinition().getWithAdd() ? "(index)" : "()") + (typeIsOpt() ? ".getChild(0)" : "");
+  eq TypePortTarget.realGetterMethodName() = getterMethodeNameHelper(false);
+  eq TypePortTarget.realGetterMethodCall() = realGetterMethodName() + (containingPortDefinition().indexedSend() ? "(index)" : "()");
+  eq TypePortTarget.parentTypeName() = getType().containingTypeDecl().getName();
+  eq TypePortTarget.entityName() = getType().getName() + (typeIsList() && !containingPortDefinition().getIndexBasedListAccess() ? "List" : "");
+
+  private String TypePortTarget.getterMethodeNameHelper(boolean useForwarding) {
+    return (useForwarding && needForwarding() ? forwardingName() :
+            "get" + getType().getName() + (typeIsList() && (!containingPortDefinition().getIndexBasedListAccess() ||
+                    containingPortDefinition().getWithAdd()) ? "List" : "") + (typeIsOpt() ? "Opt" : "")
+                    + (needForwarding() ? "NoTransform" : ""));
+  }
+
+  eq ContextFreeTypePortTarget.getterMethodName() = null;
+  eq ContextFreeTypePortTarget.parentTypeName() = getTypeDecl().getName();
+  eq ContextFreeTypePortTarget.entityName() = "";
+}
+
+aspect MustacheReceiveDefinition {
+  // === RagConnect ===
+  syn boolean RagConnect.configLoggingEnabledForReads() = getConfiguration().getLoggingEnabledForReads();
+
+  // === PortDefinition ===
+  syn boolean PortDefinition.hasContextFreeTypePortTarget() = getPortTarget().isContextFreeTypePortTarget();
+
+  syn boolean PortDefinition.hasTypePortTarget() = getPortTarget().isTypePortTarget();
+
+  syn String PortDefinition.idTokenName() = ragconnect().internalRagConnectPrefix() + "TopicInList";
+
+  syn String PortDefinition.internalConnectMethodName() = ragconnect().internalRagConnectPrefix() + "_internal_" + connectMethodName();
+
+  syn String PortDefinition.resolveInListMethodName() = ragconnect().internalRagConnectPrefix() + "_resolve" + entityName() + "InList";
+
+  // === attributes needed for computing above ones ===
+  syn String PortDefinition.uniqueSuffix() = getSend() ? "Send" : "Receive";
+}
+
+aspect MustacheSendDefinition {
+  // === RagConnect ===
+  syn boolean RagConnect.configLoggingEnabledForWrites() = getConfiguration().getLoggingEnabledForWrites();
+
+  // === PortDefinition ===
+  syn String PortDefinition.lastValueGetterCall() = senderName() + ".getLastValue(" +
+    (getIndexBasedListAccess() ? "index" : "") + ")";
+
+  syn String PortDefinition.lastValueSetter() = senderName() + ".setLastValue";
+
+  syn boolean PortDefinition.needForwarding() = getPortTarget().needForwarding();
+  syn String PortDefinition.forwardingName() = getPortTarget().forwardingName();
+  syn String PortDefinition.forwardingType() = getPortTarget().forwardingType();
+
+  syn boolean PortDefinition.targetIsAttribute() = getPortTarget().isAttributePortTarget();
+
+  syn boolean PortDefinition.indexBasedAccessAndTargetIsNTA() {
+    return typeIsList() && getIndexBasedListAccess() && !needForwarding();
+  }
+
+  syn boolean PortDefinition.relationPortWithListRole() = getPortTarget().relationPortWithListRole();
+
+  syn String PortDefinition.senderName() = getPortTarget().senderName();
+
+  syn java.util.List<SendIncrementalObserverEntry> PortDefinition.sendIncrementalObserverEntries() {
+    // todo maybe getterMethodName needs to be change for indexed send
+    java.util.List<SendIncrementalObserverEntry> result = new java.util.ArrayList<>();
+    // "{{getterMethodName}}{{#IndexBasedListAccess}}_int{{/IndexBasedListAccess}}"
+    result.add(SendIncrementalObserverEntry.of(getterMethodName() + (getIndexBasedListAccess() ? "_int" : ""),
+            getIndexBasedListAccess(), getIndexBasedListAccess() ? "index" : "null"));
+    if (indexBasedAccessAndTargetIsNTA()) {
+      // "{{getterMethodName}}List"
+      result.add(SendIncrementalObserverEntry.of(getterMethodName() + "List", false, "index"));
+    }
+    if (targetIsAttribute()) {
+      // "{{parentTypeName}}_{{getterMethodName}}{{#IndexBasedListAccess}}_int{{/IndexBasedListAccess}}"
+      result.add(SendIncrementalObserverEntry.of(parentTypeName() + "_" + getterMethodName() + (getIndexBasedListAccess() ? "_int" : ""), getIndexBasedListAccess(), getIndexBasedListAccess() ? "index" : "null"));
+    }
+    return result;
+  }
+
+  syn boolean PortDefinition.shouldNotResetValue() = getSend() && !getPortTarget().hasAttributeResetMethod();
+
+  syn String PortDefinition.tokenResetMethodName() = getPortTarget().tokenResetMethodName();
+
+  syn String PortDefinition.updateMethodName() = toMustache().updateMethodName();
+
+  syn String PortDefinition.writeMethodName() = toMustache().writeMethodName();
+
+  // === attributes needed for computing above ones ===
+  syn boolean PortTarget.needForwarding() = false;
+  eq TypePortTarget.needForwarding() = containingPortDefinition().getSend() && !getType().getNTA();
+  eq RelationPortTarget.needForwarding() = containingPortDefinition().getSend();
+
+  syn String PortTarget.forwardingName() = null;  // only needed, if needForwarding evaluates to true
+  eq TypePortTarget.forwardingName() = ragconnect().internalRagConnectPrefix() + getType().getName();
+  eq RelationPortTarget.forwardingName() = ragconnect().internalRagConnectPrefix() + getRole().getName();
+
+  syn String PortTarget.forwardingType() = null;  // only needed, if needForwarding evaluates to true
+  eq TypePortTarget.forwardingType() = getType().forwardingType(
+          containingPortDefinition().getIndexBasedListAccess());
+  eq RelationPortTarget.forwardingType() = getRole().forwardingType(
+containingPortDefinition().getIndexBasedListAccess());
+
+  syn String TypeComponent.forwardingType(boolean indexBasedListAccess);
+  eq NormalComponent.forwardingType(boolean indexBasedListAccess) = getTypeDecl().getName();
+  eq OptComponent.forwardingType(boolean indexBasedListAccess) =
+          ragconnect().configJastAddOpt() + "<" + getTypeDecl().getName() + ">";
+  eq ListComponent.forwardingType(boolean indexBasedListAccess) = indexBasedListAccess ?
+          getTypeDecl().getName() :
+          ragconnect().configJastAddList() + "<" + getTypeDecl().getName() + ">";
+
+  syn String Role.forwardingType(boolean indexBasedListAccess) = oppositeRole().getType().getName();
+  eq ListRole.forwardingType(boolean indexBasedListAccess) = indexBasedListAccess ?
+          oppositeRole().getType().getName() :
+          "java.util.List<" + oppositeRole().getType().getName() + ">";
+
+  syn boolean PortTarget.relationPortWithListRole() = false;
+  eq RelationPortTarget.relationPortWithListRole() = getRole().isListRole();
+
+  syn String PortTarget.senderName() = ragconnect().internalRagConnectPrefix() + "_sender_" + entityName();
+  eq ContextFreeTypePortTarget.senderName() = null;
+
+  syn String PortTarget.tokenResetMethodName() = getterMethodName() + (
+          typeIsList() && containingPortDefinition().getIndexBasedListAccess() ? "List" : "") + "_reset";
+
+  syn String MPortDefinition.updateMethodName();
+  syn String MPortDefinition.writeMethodName();
+
+  eq MAttributeSendDefinition.updateMethodName() = ragconnect().internalRagConnectPrefix() + "_update_attr_" + getPortDefinition().entityName();
+  eq MAttributeSendDefinition.writeMethodName() = ragconnect().internalRagConnectPrefix() + "_writeLastValue_attr_" + getPortDefinition().entityName();
+
+  eq MRelationSendDefinition.updateMethodName() = ragconnect().internalRagConnectPrefix() + "_update_" + getPortDefinition().entityName();
+  eq MRelationSendDefinition.writeMethodName() = ragconnect().internalRagConnectPrefix() + "_writeLastValue_" + getPortDefinition().entityName();
+
+  eq MTokenReceiveDefinition.updateMethodName() = null;
+  eq MTokenReceiveDefinition.writeMethodName() = null;
+
+  eq MTokenSendDefinition.updateMethodName() = ragconnect().internalRagConnectPrefix() + "_update_" + tokenName();
+  eq MTokenSendDefinition.writeMethodName() = ragconnect().internalRagConnectPrefix() + "_writeLastValue_" + tokenName();
+
+  eq MTypeReceiveDefinition.updateMethodName() = null;
+  eq MTypeReceiveDefinition.writeMethodName() = null;
+
+  eq MTypeSendDefinition.updateMethodName() = ragconnect().internalRagConnectPrefix() + "_update_" + typeName();
+  eq MTypeSendDefinition.writeMethodName() = ragconnect().internalRagConnectPrefix() + "_writeLastValue_" + typeName();
+
+  eq MContextFreeTypeReceiveDefinition.updateMethodName() = null;
+  eq MContextFreeTypeReceiveDefinition.writeMethodName() = null;
+
+  eq MContextFreeTypeSendDefinition.updateMethodName() = null;
+  eq MContextFreeTypeSendDefinition.writeMethodName() = null;
+
+  syn String PortDefinition.tokenName() = token().getName();
+  syn String MPortDefinition.tokenName() = getPortDefinition().tokenName();
+
+  syn String PortDefinition.typeName() = type().getName();
+  syn String MPortDefinition.typeName() = getPortDefinition().typeName();
+
+  static SendIncrementalObserverEntry SendIncrementalObserverEntry.of(String attributeString, boolean compareParams, Object params) {
+    return new SendIncrementalObserverEntry()
+            .setParams(params)
+            .setCompareParams(compareParams)
+            .setAttributeString(attributeString);
+  }
+}
+
+aspect MustacheTokenComponent {
+  // === TokenComponent ===
+  syn String TokenComponent.internalName() = needProxyToken() ? ragconnect().internalRagConnectPrefix() + getName() : getName();
+
+  syn String TokenComponent.javaType() = effectiveJavaTypeUse().prettyPrint();
+
+  syn PortDefinition TokenComponent.normalTokenSendDef() {
+    for (PortTarget target : getTokenPortTargetList()) {
+      if (target.isTokenPortTarget() && target.containingPortDefinition().shouldNotResetValue()) {
+        return target.containingPortDefinition();
+      }
+    }
+    return null;
+  }
+
+  syn String TokenComponent.parentTypeName() = containingTypeDecl().getName();
+
+  // > see MustacheDependencyDefinition for internalRelationPrefix, targetParentTypeName
+
+  // === DependencyDefinition ===
+  syn PortDefinition DependencyDefinition.targetPortDefinition() {
+    // resolve definition in here, as we do not need resolveMethod in any other place (yet)
+    for (PortDefinition portDefinition : ragconnect().allPortDefinitionList()) {
+      if (!portDefinition.getSend()) {
+        continue;
+      }
+      PortTarget portTarget = portDefinition.getPortTarget();
+      if (portTarget.isTokenPortTarget() &&
+          portTarget.asTokenPortTarget().getToken().equals(this.getTarget())) {
+        return portDefinition;
+      }
+      if (portTarget.isTypePortTarget() &&
+          portTarget.asTypePortTarget().getType().equals(this.getTarget())) {
+        return portDefinition;
+      }
+    }
+    return null;
+  }
+
+  // > see MustacheSend for updateMethodName, writeMethodName
+}
+
+aspect MustacheTypeDecl {
+  // === TypeComponent ===
+  syn String TypeComponent.parentTypeName() = containingTypeDecl().getName();
+  syn String TypeComponent.disconnectMethodName() {
+    List<TypePortTarget> typePortTargets = getTypePortTargets();
+    if (typePortTargets.isEmpty()) {
+      return "MISSING_PORT";
+    } else {
+      return typePortTargets.get(0).containingPortDefinition().disconnectMethodName();
+    }
+  }
+
+  // === TypeDecl ===
+  syn List<TypeComponent> TypeDecl.occurencesInProductionRules() {
+    List<TypeComponent> result = new ArrayList<>();
+    for (TypeDecl typeDecl : program().typeDecls()) {
+      for (Component comp : typeDecl.getComponentList()) {
+        if (comp.isTypeComponent() && comp.asTypeComponent().getTypeDecl().equals(this)) {
+          result.add(comp.asTypeComponent());
+        }
+      }
+    }
+    return result;
+  }
+
+}
+
+aspect AttributesForMustache {
+  syn String MPortDefinition.lastValueGetterCall() = getPortDefinition().lastValueGetterCall();
+  syn String MPortDefinition.lastValueSetter() = getPortDefinition().lastValueSetter();
+
+  // token and type are potentially dangerous because asXPortTarget can return null
+  syn TokenComponent PortDefinition.token() = getPortTarget().asTokenPortTarget().getToken();
+  syn TypeComponent PortDefinition.type() = getPortTarget().asTypePortTarget().getType();
+
+  syn MInnerMappingDefinition PortDefinition.lastDefinition() = toMustache().lastDefinition();
+  syn MInnerMappingDefinition MPortDefinition.lastDefinition() = getInnerMappingDefinition(getNumInnerMappingDefinition() - 1);
+
+  syn nta MPortDefinition PortDefinition.toMustache() {
+    final MPortDefinition result = getPortTarget().createMPortDefinition(getSend());
+    result.setPortDefinition(this);
+    for (MappingDefinition def : effectiveMappings()) {
+      MInnerMappingDefinition inner = new MInnerMappingDefinition();
+      inner.setMappingDefinition(def);
+      result.addInnerMappingDefinition(inner);
+    }
+    return result;
+  }
+  abstract MPortDefinition PortTarget.createMPortDefinition(boolean isSend);
+  MPortDefinition AttributePortTarget.createMPortDefinition(boolean isSend) {
+    if (!isSend) {
+      throw new IllegalArgumentException("AttributePortTarget can only be sent!");
+    }
+    return new MAttributeSendDefinition();
+  }
+  MPortDefinition RelationPortTarget.createMPortDefinition(boolean isSend) {
+    if (!isSend) {
+      throw new IllegalArgumentException("RelationPortTarget can only be sent!");
+    }
+    return new MRelationSendDefinition();
+  }
+  MPortDefinition TokenPortTarget.createMPortDefinition(boolean isSend) {
+    return isSend ? new MTokenSendDefinition() : new MTokenReceiveDefinition();
+  }
+  MPortDefinition TypePortTarget.createMPortDefinition(boolean isSend) {
+    return isSend ? new MTypeSendDefinition() : new MTypeReceiveDefinition();
+  }
+  MPortDefinition ContextFreeTypePortTarget.createMPortDefinition(boolean isSend) {
+    return isSend ? new MContextFreeTypeSendDefinition() : new MContextFreeTypeReceiveDefinition();
+  }
+  MPortDefinition UntypedPortTarget.createMPortDefinition(boolean isSend) {
+    throw new RuntimeException("Could not resolve port target '" +
+        getTypeName() + "." + getChildName() + "'");
+  }
+}
+
+aspect GrammarGeneration {
+  syn java.util.List<Relation> RagConnect.additionalRelations() {
+    java.util.List<Relation> result = new java.util.ArrayList<>();
+    for (DependencyDefinition dd : allDependencyDefinitionList()) {
+      result.add(dd.getRelationToCreate());
+    }
+    return result;
+  }
+
+  syn nta Relation DependencyDefinition.getRelationToCreate() {
+    BidirectionalRelation result = new BidirectionalRelation();
+    NavigableRole left = new ListRole(internalRelationPrefix() + "Source");
+    left.setType(getTarget().containingTypeDecl());
+    NavigableRole right = new ListRole(internalRelationPrefix() + "Target");
+    right.setType(getSource().containingTypeDecl());
+    result.setLeft(left);
+    result.setRight(right);
+    result.addComment(new WhitespaceComment("\n"));
+    return result;
+  }
+
+  syn java.util.Map<TypeDecl, TokenComponent> RagConnect.additionalTokens() {
+    java.util.Map<TypeDecl, TokenComponent> result = new java.util.HashMap<>();
+    for (PortDefinition def : allPortDefinitionList()) {
+      if (def.getTokenToCreate() != null) {
+        result.put(def.type().getTypeDecl(), def.getTokenToCreate());
+      }
+    }
+    return result;
+  }
+
+  syn TokenComponent PortDefinition.getTokenToCreate() {
+    if (typeIsList() && getIndexBasedListAccess()) {
+      TokenComponent result = new TokenComponent();
+      result.setName(idTokenName());
+      result.setNTA(false);
+      result.setJavaTypeUse(new SimpleJavaTypeUse("String"));
+      return result;
+    } else {
+      return null;
+    }
+  }
+}
+
+aspect GrammarExtension {
+  refine BackendAbstractGrammar public void TokenComponent.generateAbstractGrammar(StringBuilder b) {
+    if (getNTA()) {
+      b.append("/");
+    }
+    b.append("<");
+    if (!getName().equals("")) {
+      b.append(internalName()).append(":");
+    }
+    effectiveJavaTypeUse().generateAbstractGrammar(b);
+    b.append(">");
+    if (getNTA()) {
+      b.append("/");
+    }
+  }
+}
diff --git a/ragconnect.base/src/main/jastadd/Intermediate.relast b/ragconnect.base/src/main/jastadd/Intermediate.relast
new file mode 100644
index 0000000000000000000000000000000000000000..3e29f479565db55e894dfbcc60a23cd7a972355b
--- /dev/null
+++ b/ragconnect.base/src/main/jastadd/Intermediate.relast
@@ -0,0 +1,19 @@
+abstract MPortDefinition ::= InnerMappingDefinition:MInnerMappingDefinition*;
+rel MPortDefinition.PortDefinition -> PortDefinition;
+
+MAttributeSendDefinition : MPortDefinition;
+MRelationSendDefinition : MPortDefinition;
+abstract MTokenPortDefinition : MPortDefinition;
+MTokenReceiveDefinition : MTokenPortDefinition;
+MTokenSendDefinition : MTokenPortDefinition;
+abstract MTypePortDefinition : MPortDefinition;
+MTypeReceiveDefinition : MTypePortDefinition;
+MTypeSendDefinition : MTypePortDefinition;
+abstract MContextFreeTypePortDefinition : MPortDefinition;
+MContextFreeTypeReceiveDefinition : MContextFreeTypePortDefinition;
+MContextFreeTypeSendDefinition : MContextFreeTypePortDefinition;
+
+MInnerMappingDefinition;
+rel MInnerMappingDefinition.MappingDefinition -> MappingDefinition;
+
+SendIncrementalObserverEntry ::= <Params:Object> <CompareParams:boolean> <AttributeString>;
diff --git a/ragconnect.base/src/main/jastadd/IntermediateToYAML.jrag b/ragconnect.base/src/main/jastadd/IntermediateToYAML.jrag
new file mode 100644
index 0000000000000000000000000000000000000000..c247d9d0a69b2f548e04fe69adcf35620fd69255
--- /dev/null
+++ b/ragconnect.base/src/main/jastadd/IntermediateToYAML.jrag
@@ -0,0 +1,219 @@
+aspect IntermediateToYAML {
+  syn Document RagConnect.toYAML() {
+    Document doc = new Document();
+    MappingElement root = new MappingElement();
+    // grammar children
+    ListElement handlersElement = new ListElement();
+    for (Handler handler : getHandlerList()) {
+      handlersElement.add(handler.toYAML());
+    }
+    root.put("Handlers" , handlersElement);
+
+    // shared
+    root.put("configIncrementalOptionActive" , configIncrementalOptionActive());
+    root.put("configLoggingEnabledForIncremental" , configLoggingEnabledForIncremental());
+    root.put("configExperimentalJastAdd329" , configExperimentalJastAdd329());
+    root.put("internalRagConnectPrefix" , internalRagConnectPrefix());
+    root.put("rootNodeName" , rootNodeName());
+
+    // handler
+    root.put("closeMethodName" , closeMethodName());
+    root.put("hasRootTypeComponents" , hasRootTypeComponents());
+    ListElement rootTypeComponentsElement = new ListElement();
+    for (TypeComponent comp : rootTypeComponents()) {
+      rootTypeComponentsElement.addElement(comp.toYAML());
+    }
+    root.put("rootTypeComponents" , rootTypeComponentsElement);
+
+    // listAspect
+    root.put("configJastAddList" , configJastAddList());
+    root.put("hasTreeListPorts" , hasTreeListPorts());
+    ListElement typesForReceivingListPortsElement = new ListElement();
+    for (TypeDecl typeDecl : typesForReceivingListPorts()) {
+      typesForReceivingListPortsElement.addElement(typeDecl.toYAML());
+    }
+    root.put("typesForReceivingListPorts" , typesForReceivingListPortsElement);
+
+    // ragconnect
+    ListElement dependencyDefinitionsElement = new ListElement();
+    for (DependencyDefinition def : allDependencyDefinitionList()) {
+      dependencyDefinitionsElement.addElement(def.toYAML());
+    }
+    root.put("allDependencyDefinitionList" , dependencyDefinitionsElement);
+    ListElement portDefinitionsElement = new ListElement();
+    for (PortDefinition def : allPortDefinitionList()) {
+      portDefinitionsElement.addElement(def.toYAML());
+    }
+    root.put("allPortDefinitionList" , portDefinitionsElement);
+    ListElement mappingDefinitionsElement = new ListElement();
+    for (MappingDefinition def : allMappingDefinitions()) {
+      mappingDefinitionsElement.addElement(def.toYAML());
+    }
+    root.put("allMappingDefinitions" , mappingDefinitionsElement);
+    ListElement tokenComponentsElement = new ListElement();
+    for (TokenComponent comp : tokenComponentsThatNeedProxy()) {
+      tokenComponentsElement.addElement(comp.toYAML());
+    }
+    root.put("tokenComponentsThatNeedProxy" , tokenComponentsElement);
+
+    // receive
+    root.put("configLoggingEnabledForReads" , configLoggingEnabledForReads());
+
+    // send
+    root.put("configLoggingEnabledForWrites" , configLoggingEnabledForWrites());
+
+    // from Handlers.jrag
+    root.put("mqttHandler" , mqttHandler().toYAML());
+    root.put("restHandler" , restHandler().toYAML());
+
+    doc.setRootElement(root);
+    return doc;
+  }
+
+  syn MappingElement PortDefinition.toYAML() {
+    MappingElement result = new MappingElement();
+    // grammar children
+    result.put("AlwaysApply" , getAlwaysApply());
+    result.put("IndexBasedListAccess" , getIndexBasedListAccess());
+    result.put("WithAdd" , getWithAdd());
+    result.put("Send" , getSend());
+
+    // shared
+    result.put("lastResult" , lastResult());
+
+    // mapping
+    result.put("condition" , sanitizeValueForYAML(condition()));
+    ListElement innerMappingDefinitions = new ListElement();
+    for (MInnerMappingDefinition def : innerMappingDefinitions()) {
+      innerMappingDefinitions.addElement(def.toYAML());
+    }
+    result.put("innerMappingDefinitions" , innerMappingDefinitions);
+    result.put("lastDefinitionToType" , lastDefinitionToType());
+    result.put("preemptiveReturn" , preemptiveReturn());
+
+    // receive + send
+    result.put("connectMethodName" , connectMethodName());
+    result.put("connectParameterName" , connectParameterName());
+    result.put("disconnectMethodName" , disconnectMethodName());
+    result.put("entityName" , entityName());
+    result.put("getterMethodName" , getterMethodName());
+    result.put("parentTypeName" , parentTypeName());
+
+    if (getSend()) {
+      result.put("lastValueGetterCall" , lastValueGetterCall());
+      result.put("lastValueSetter" , lastValueSetter());
+      result.put("senderName" , senderName());
+      result.put("shouldNotResetValue" , shouldNotResetValue());
+      result.put("tokenResetMethodName" , tokenResetMethodName());
+      result.put("updateMethodName" , updateMethodName());
+      result.put("writeMethodName" , writeMethodName());
+    } else {
+      result.put("hasTypePortTarget" , hasTypePortTarget());
+      result.put("idTokenName" , idTokenName());
+      result.put("internalConnectMethodName" , internalConnectMethodName());
+      result.put("resolveInListMethodName" , resolveInListMethodName());
+      result.put("typeIsList" , getPortTarget().typeIsList());
+    }
+    return result;
+  }
+
+  void UntypedPortTarget.addToYAML(MappingElement result) {
+    // empty
+  }
+
+  syn Element MInnerMappingDefinition.toYAML() {
+    MappingElement result = new MappingElement();
+    result.put("last" , isLast());
+    result.put("inputVarName" , inputVarName());
+    result.put("methodName" , methodName());
+    result.put("outputVarName" , outputVarName());
+    result.put("toType" , toType());
+    return result;
+  }
+
+  syn Element MappingDefinition.toYAML() {
+    MappingElement result = new MappingElement();
+    // grammar children
+    result.put("FromVariableName" , getFromVariableName());
+    result.put("Content" , sanitizeValueForYAML(getContent()));
+
+    // mapping
+    result.put("fromType" , fromType());
+    result.put("methodName" , methodName());
+    result.put("toType" , toType());
+
+    // ragconnect
+    result.put("isUsed" , isUsed());
+    return result;
+  }
+
+  syn Element TemplateDefaultMappingDefinition.toYAML() {
+    MappingElement result = new MappingElement();
+
+    // ragconnect / mapping
+    result.put("isUsed" , isUsed());
+    result.put("isSerializeListMapping" , isSerializeListMapping());
+    result.put("isSerializeJavaUtilListMapping" , isSerializeJavaUtilListMapping());
+    result.put("isDeserializeListMapping" , isDeserializeListMapping());
+    return result;
+  }
+
+  syn Element DependencyDefinition.toYAML() {
+    MappingElement result = new MappingElement();
+    // dependencyDefinition
+    result.put("dependencyMethodName" , dependencyMethodName());
+    result.put("internalRelationPrefix" , internalRelationPrefix());
+    result.put("sourceParentTypeName" , sourceParentTypeName());
+    result.put("targetParentTypeName" , targetParentTypeName());
+
+    // tokenComponent
+    result.put("targetPortDefinition" , targetPortDefinition().toYAML());
+    return result;
+  }
+
+  syn Element TokenComponent.toYAML() {
+    MappingElement result = new MappingElement();
+    result.put("Name" , getName());
+    ListElement dependencySourceDefinitions = new ListElement();
+    for (DependencyDefinition def : getDependencySourceDefinitionList()) {
+      dependencySourceDefinitions.add(def.toYAML());
+    }
+    result.put("DependencySourceDefinitions" , dependencySourceDefinitions);
+
+    result.put("javaType" , javaType());
+    result.put("normalTokenSendDef" , normalTokenSendDef().toYAML());
+    result.put("parentTypeName" , parentTypeName());
+    return result;
+  }
+
+  syn MappingElement Handler.toYAML() {
+    MappingElement result = new MappingElement();
+    result.put("ClassName" , getClassName());
+    result.put("InUse" , getInUse());
+
+    result.put("attributeName" , attributeName());
+    result.put("constructionSnippet" , constructionSnippet());
+    result.put("fieldName" , fieldName());
+    result.put("setupWaitUntilReadyMethodName" , setupWaitUntilReadyMethodName());
+    return result;
+  }
+
+  syn MappingElement TypeComponent.toYAML() {
+    return new MappingElement().put("Name", getName());
+  }
+
+  syn MappingElement TypeDecl.toYAML() {
+    return new MappingElement().put("Name", getName());
+  }
+
+  protected StringElement ASTNode.sanitizeValueForYAML(String value) {
+    return StringElement.of(value.replace("\"" , "\\\"").replace("\n" , "\\n"));
+  }
+}
+
+aspect Navigation {
+  eq Document.getChild().program() = null;
+  eq Document.getChild().ragconnect() = null;
+  eq Document.getChild().containedFile() = null;
+  eq Document.containedFileName() = getFileName();
+}
diff --git a/ragconnect.base/src/main/jastadd/intermediate/Mappings.jrag b/ragconnect.base/src/main/jastadd/Mappings.jrag
similarity index 50%
rename from ragconnect.base/src/main/jastadd/intermediate/Mappings.jrag
rename to ragconnect.base/src/main/jastadd/Mappings.jrag
index 3a7fce51404e43be679d7b8829127099fd78b683..75ea75eb90f122bc743a36ac1b2a62b36a065a89 100644
--- a/ragconnect.base/src/main/jastadd/intermediate/Mappings.jrag
+++ b/ragconnect.base/src/main/jastadd/Mappings.jrag
@@ -1,10 +1,18 @@
 aspect DefaultMappings {
 
   private String RagConnect.baseDefaultMappingTypeNamePart(String typeName) {
-    return capitalize(typeName).replace("[]", "s").replace("<", "").replace(">", "List");
+    if (typeName.contains(".")) {
+      StringBuilder sb = new StringBuilder();
+      for (String part : typeName.split("\\.")) {
+        sb.append(baseDefaultMappingTypeNamePart(part));
+      }
+      return sb.toString();
+    } else {
+      return capitalize(typeName).replace("[]", "s").replace("<", "").replace(">", "List");
+    }
   }
 
-  private MappingDefinitionType RagConnect.baseDefaultMappingTypeFromName(String typeName) {
+  private MappingDefinitionType RagConnect.baseDefaultMappingTypeName(String typeName) {
     return typeName.endsWith("[]") ?
         new JavaArrayMappingDefinitionType(new SimpleJavaTypeUse(typeName.replace("[]", ""))) :
         new JavaMappingDefinitionType(new SimpleJavaTypeUse(typeName));
@@ -13,12 +21,22 @@ aspect DefaultMappings {
   private DefaultMappingDefinition RagConnect.createDefaultMappingDefinition(String prefix, String fromTypeName, String toTypeName, String content) {
     DefaultMappingDefinition result = new DefaultMappingDefinition();
     result.setID(prefix + baseDefaultMappingTypeNamePart(fromTypeName) + "To" + baseDefaultMappingTypeNamePart(toTypeName) + "Mapping");
-    result.setFromType(baseDefaultMappingTypeFromName(fromTypeName));
+    result.setFromType(baseDefaultMappingTypeName(fromTypeName));
     result.setFromVariableName("input");
-    result.setToType(baseDefaultMappingTypeFromName(toTypeName));
+    result.setToType(baseDefaultMappingTypeName(toTypeName));
     result.setContent(content);
     return result;
   }
+  private TemplateDefaultMappingDefinition RagConnect.initTemplateMappingDefinition(
+      TemplateDefaultMappingDefinition prototype, String id, String type, boolean fromBytes) {
+    MappingDefinitionType givenType = new JavaMappingDefinitionType().setType(new SimpleJavaTypeUse(type));
+    MappingDefinitionType bytesType = new JavaArrayMappingDefinitionType().setType(new SimpleJavaTypeUse("byte"));
+
+    prototype.setID(id);
+    prototype.setFromType(fromBytes ? bytesType : givenType);
+    prototype.setToType(fromBytes ? givenType : bytesType);
+    return prototype;
+  }
 
   private DefaultMappingDefinition RagConnect.baseDefaultMappingDefinition(String fromTypeName, String toTypeName, String content) {
     return createDefaultMappingDefinition("_Default", fromTypeName, toTypeName, content);
@@ -47,50 +65,60 @@ aspect DefaultMappings {
 
   syn nta DefaultMappingDefinition RagConnect.defaultBytesToTreeMapping(String typeName) {
     return treeDefaultMappingDefinition("byte[]", typeName,
-      "String content = new String(input);\n" +
-      "com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();\n" +
-      "com.fasterxml.jackson.core.JsonFactory factory = new com.fasterxml.jackson.core.JsonFactory();\n" +
-      "com.fasterxml.jackson.core.JsonParser parser = factory.createParser(content);\n" +
-      typeName + " result = " + typeName + ".deserialize((com.fasterxml.jackson.databind.JsonNode)mapper.readTree(parser));\n" +
-      "parser.close();\n" +
-      "return result;"
+        "String content = new String(input);\n" +
+            "com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();\n" +
+            "com.fasterxml.jackson.core.JsonFactory factory = new com.fasterxml.jackson.core.JsonFactory();\n" +
+            "com.fasterxml.jackson.core.JsonParser parser = factory.createParser(content);\n" +
+            typeName + " result = " + typeName + ".deserialize((com.fasterxml.jackson.databind.JsonNode)mapper.readTree(parser));\n" +
+            "parser.close();\n" +
+            "return result;"
     );
   }
   syn nta DefaultMappingDefinition RagConnect.defaultTreeToBytesMapping(String typeName) {
     return treeDefaultMappingDefinition(typeName, "byte[]",
-      "java.io.ByteArrayOutputStream outputStream = new java.io.ByteArrayOutputStream();\n" +
-      "com.fasterxml.jackson.core.JsonFactory factory = new com.fasterxml.jackson.core.JsonFactory();\n" +
-      "com.fasterxml.jackson.core.JsonGenerator generator = factory.createGenerator(outputStream, com.fasterxml.jackson.core.JsonEncoding.UTF8);\n"+
-      "input.serialize(generator);\n" +
-      "generator.flush();\n" +
-      "return outputStream.toString().getBytes();"
+        "java.io.ByteArrayOutputStream outputStream = new java.io.ByteArrayOutputStream();\n" +
+            "com.fasterxml.jackson.core.JsonFactory factory = new com.fasterxml.jackson.core.JsonFactory();\n" +
+            "com.fasterxml.jackson.core.JsonGenerator generator = factory.createGenerator(outputStream, com.fasterxml.jackson.core.JsonEncoding.UTF8);\n" +
+            "input.serialize(generator);\n" +
+            "generator.flush();\n" +
+            "return outputStream.toString().getBytes();"
     );
   }
 
-  syn nta DefaultMappingDefinition RagConnect.defaultBytesToListTreeMapping(String typeName) {
-    return treeDefaultMappingDefinition("byte[]", JastAddList + "<" + typeName + ">",
-      "String content = new String(input);\n" +
-      "com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();\n" +
-      "com.fasterxml.jackson.core.JsonFactory factory = new com.fasterxml.jackson.core.JsonFactory();\n" +
-      "com.fasterxml.jackson.core.JsonParser parser = factory.createParser(content);\n" +
-      JastAddList + "<" + typeName + ">" + " result = " + typeName + ".deserializeList((com.fasterxml.jackson.databind.node.ArrayNode)mapper.readTree(parser));\n" +
-      "parser.close();\n" +
-      "return result;"
+  syn nta DefaultMappingDefinition RagConnect.defaultBytesToListMapping(String typeName) {
+    return treeDefaultMappingDefinition("byte[]", configJastAddList() + "<" + typeName + ">",
+        "String content = new String(input);\n" +
+            "com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();\n" +
+            "com.fasterxml.jackson.core.JsonFactory factory = new com.fasterxml.jackson.core.JsonFactory();\n" +
+            "com.fasterxml.jackson.core.JsonParser parser = factory.createParser(content);\n" +
+            configJastAddList() + "<" + typeName + ">" + " result = " + typeName + ".deserializeListOf" + typeName + "((com.fasterxml.jackson.databind.node.ArrayNode)mapper.readTree(parser));\n" +
+            "parser.close();\n" +
+            "return result;"
     );
   }
-  syn nta DefaultMappingDefinition RagConnect.defaultListTreeToBytesMapping() {
-    return treeDefaultMappingDefinition(JastAddList, "byte[]",
-      "java.io.ByteArrayOutputStream outputStream = new java.io.ByteArrayOutputStream();\n" +
-      "com.fasterxml.jackson.core.JsonFactory factory = new com.fasterxml.jackson.core.JsonFactory();\n" +
-      "com.fasterxml.jackson.core.JsonGenerator generator = factory.createGenerator(outputStream, com.fasterxml.jackson.core.JsonEncoding.UTF8);\n"+
-      "input.serialize(generator);\n" +
-      "generator.flush();\n" +
-      "return outputStream.toString().getBytes();"
+  syn nta DefaultMappingDefinition RagConnect.defaultListToBytesMapping() {
+    return treeDefaultMappingDefinition(configJastAddList(), "byte[]",
+        "java.io.ByteArrayOutputStream outputStream = new java.io.ByteArrayOutputStream();\n" +
+            "com.fasterxml.jackson.core.JsonFactory factory = new com.fasterxml.jackson.core.JsonFactory();\n" +
+            "com.fasterxml.jackson.core.JsonGenerator generator = factory.createGenerator(outputStream, com.fasterxml.jackson.core.JsonEncoding.UTF8);\n" +
+            "input.serialize(generator);\n" +
+            "generator.flush();\n" +
+            "return outputStream.toString().getBytes();"
+    );
+  }
+  syn nta DefaultMappingDefinition RagConnect.defaultJavaUtilListToBytesMapping() {
+    return treeDefaultMappingDefinition("java.util.List", "byte[]",
+        "java.io.ByteArrayOutputStream outputStream = new java.io.ByteArrayOutputStream();\n" +
+            "com.fasterxml.jackson.core.JsonFactory factory = new com.fasterxml.jackson.core.JsonFactory();\n" +
+            "com.fasterxml.jackson.core.JsonGenerator generator = factory.createGenerator(outputStream, com.fasterxml.jackson.core.JsonEncoding.UTF8);\n" +
+            "serializeJavaUtilList(input, generator);\n" +
+            "generator.flush();\n" +
+            "return outputStream.toString().getBytes();"
     );
   }
 
   syn nta DefaultMappingDefinition RagConnect.defaultBooleanToBytesMapping() = baseDefaultMappingDefinition(
-        "boolean", "byte[]", "return java.nio.ByteBuffer.allocate(1).put((byte) (input ? 1 : 0)).array();");
+      "boolean", "byte[]", "return java.nio.ByteBuffer.allocate(1).put((byte) (input ? 1 : 0)).array();");
   syn nta DefaultMappingDefinition RagConnect.defaultIntToBytesMapping() = baseDefaultMappingDefinition(
       "int", "byte[]", "return java.nio.ByteBuffer.allocate(Integer.BYTES).putInt(input).array();");
   syn nta DefaultMappingDefinition RagConnect.defaultShortToBytesMapping() = baseDefaultMappingDefinition(
@@ -137,40 +165,51 @@ aspect DefaultMappings {
       "char", "String", "return String.valueOf(input);");
 }
 
+aspect TemplateDefaultMappingDefinitions {
+  syn nta TemplateDefaultMappingDefinition RagConnect.serializeListMapping() = initTemplateMappingDefinition(
+          new SerializeListMapping(), "SerializeListMapping", "JastAddList", false);
+  syn nta TemplateDefaultMappingDefinition RagConnect.serializeJavaUtilListMapping() = initTemplateMappingDefinition(
+          new SerializeJavaUtilListMapping(), "SerializeJavaUtilListMapping", "java.util.List", false);
+  syn nta TemplateDefaultMappingDefinition RagConnect.deserializeListMapping(String name) = initTemplateMappingDefinition(
+          new DeserializeListMapping().setName(name), "DeserializeListMapping", "JastAddList", true);
+}
+
 aspect Mappings {
   // --- effectiveMappings ---
-  syn java.util.List<MappingDefinition> EndpointDefinition.effectiveMappings() {
+  syn java.util.List<MappingDefinition> PortDefinition.effectiveMappings() {
     java.util.List<MappingDefinition> result;
-    if (isReceiveTokenEndpointDefinition() || isReceiveTypeEndpointDefinition()) {
+    if (!getSend()) {
       // if no mappings are specified, or if first mapping is not suitable.
       // then prepend the suitable default mapping
       if (getMappingList().isEmpty() || !getMappingList().get(0).getFromType().isByteArray()) {
-        result = new java.util.ArrayList();
+        result = new java.util.ArrayList<>();
         result.add(suitableReceiveDefaultMapping());
         result.addAll(getMappingList());
       } else {
         result = getMappingList();
       }
-    } else if (isSendTokenEndpointDefinition() || isSendTypeEndpointDefinition()) {
+    } else {
       // if no mappings are specified, or if last mapping is not suitable
       // then append the suitable default mapping
       if (getMappingList().isEmpty() || !getMappingList().get(getMappingList().size() - 1).getToType().isByteArray()) {
-        result = new java.util.ArrayList(getMappingList());
+        result = new java.util.ArrayList<>(getMappingList());
         result.add(suitableSendDefaultMapping());
       } else {
         result = getMappingList();
       }
-    } else {
-      throw new RuntimeException("Unknown endpoint definition: " + this);
     }
     return result;
   }
 
   // --- isPrimitiveType ---
+  syn boolean PortDefinition.isPrimitiveType() = getPortTarget().isPrimitiveType();
+  syn boolean PortTarget.isPrimitiveType() = false;
+  eq TokenPortTarget.isPrimitiveType() = getToken().isPrimitiveType();
+  eq AttributePortTarget.isPrimitiveType() = new SimpleJavaTypeUse(getTypeName()).isPrimitiveType();
   syn boolean TokenComponent.isPrimitiveType() = effectiveJavaTypeUse().isPrimitiveType();
   syn boolean JavaTypeUse.isPrimitiveType() = false;
   eq SimpleJavaTypeUse.isPrimitiveType() {
-    switch(getName()) {
+    switch (getName()) {
       case "boolean":
       case "int":
       case "short":
@@ -178,8 +217,10 @@ aspect Mappings {
       case "float":
       case "double":
       case "char":
-      case "byte": return true;
-      default: return false;
+      case "byte":
+        return true;
+      default:
+        return false;
     }
   }
   syn boolean MappingDefinitionType.isPrimitiveType() = false;
@@ -190,154 +231,162 @@ aspect Mappings {
   eq JavaArrayMappingDefinitionType.isArray() = true;
 
   // --- suitableReceiveDefaultMapping ---
-  syn DefaultMappingDefinition EndpointDefinition.suitableReceiveDefaultMapping() {
+  syn DefaultMappingDefinition PortDefinition.suitableReceiveDefaultMapping() {
     switch (targetTypeName()) {
       case "boolean":
-      case "Boolean": return ragconnect().defaultBytesToBooleanMapping();
+      case "Boolean":
+        return ragconnect().defaultBytesToBooleanMapping();
       case "int":
-      case "Integer": return ragconnect().defaultBytesToIntMapping();
+      case "Integer":
+        return ragconnect().defaultBytesToIntMapping();
       case "short":
-      case "Short": return ragconnect().defaultBytesToShortMapping();
+      case "Short":
+        return ragconnect().defaultBytesToShortMapping();
       case "long":
-      case "Long": return ragconnect().defaultBytesToLongMapping();
+      case "Long":
+        return ragconnect().defaultBytesToLongMapping();
       case "float":
-      case "Float": return ragconnect().defaultBytesToFloatMapping();
+      case "Float":
+        return ragconnect().defaultBytesToFloatMapping();
       case "double":
-      case "Double": return ragconnect().defaultBytesToDoubleMapping();
+      case "Double":
+        return ragconnect().defaultBytesToDoubleMapping();
       case "char":
-      case "Character": return ragconnect().defaultBytesToCharMapping();
-      case "String": return ragconnect().defaultBytesToStringMapping();
+      case "Character":
+        return ragconnect().defaultBytesToCharMapping();
+      case "String":
+        return ragconnect().defaultBytesToStringMapping();
       default:
         try {
           TypeDecl typeDecl = program().resolveTypeDecl(targetTypeName());
-          // TODO: also support list-types, if list is first type
-          return ragconnect().defaultBytesToTreeMapping(typeDecl.getName());
-        } catch (Exception ignore) {}
-        System.err.println("Could not find suitable default mapping for " + targetTypeName() + " on " + this);
+          return getPortTarget().isTypePortTarget() && typeIsList() && !getIndexBasedListAccess() ? ragconnect().defaultBytesToListMapping(typeDecl.getName()) : ragconnect().defaultBytesToTreeMapping(typeDecl.getName());
+        } catch (Exception ignore) {
+        }
+        System.err.println("Could not find suitable default receive mapping for " + targetTypeName() + " on " + this);
         return null;
     }
   }
-  eq TypeEndpointDefinition.suitableReceiveDefaultMapping() {
-    try {
-      TypeDecl typeDecl = program().resolveTypeDecl(targetTypeName());
-      return typeIsList() && getUseList() ? ragconnect().defaultBytesToListTreeMapping(typeDecl.getName()) : ragconnect().defaultBytesToTreeMapping(typeDecl.getName());
-    } catch (Exception ignore) {}
-    return super.suitableReceiveDefaultMapping();
-  }
 
   // --- suitableSendDefaultMapping ---
-  syn DefaultMappingDefinition EndpointDefinition.suitableSendDefaultMapping() {
+  syn DefaultMappingDefinition PortDefinition.suitableSendDefaultMapping() {
     switch (targetTypeName()) {
       case "boolean":
-      case "Boolean": return ragconnect().defaultBooleanToBytesMapping();
+      case "Boolean":
+        return ragconnect().defaultBooleanToBytesMapping();
       case "int":
-      case "Integer": return ragconnect().defaultIntToBytesMapping();
+      case "Integer":
+        return ragconnect().defaultIntToBytesMapping();
       case "short":
-      case "Short": return ragconnect().defaultShortToBytesMapping();
+      case "Short":
+        return ragconnect().defaultShortToBytesMapping();
       case "long":
-      case "Long": return ragconnect().defaultLongToBytesMapping();
+      case "Long":
+        return ragconnect().defaultLongToBytesMapping();
       case "float":
-      case "Float": return ragconnect().defaultFloatToBytesMapping();
+      case "Float":
+        return ragconnect().defaultFloatToBytesMapping();
       case "double":
-      case "Double": return ragconnect().defaultDoubleToBytesMapping();
+      case "Double":
+        return ragconnect().defaultDoubleToBytesMapping();
       case "char":
-      case "Character": return ragconnect().defaultCharToBytesMapping();
-      case "String": return ragconnect().defaultStringToBytesMapping();
+      case "Character":
+        return ragconnect().defaultCharToBytesMapping();
+      case "String":
+        return ragconnect().defaultStringToBytesMapping();
       default:
+        if (getPortTarget().isTypePortTarget() && typeIsList() && !getIndexBasedListAccess()) {
+          return ragconnect().defaultListToBytesMapping();
+        }
+        if (getPortTarget().isRelationPortTarget() && typeIsList() && !getIndexBasedListAccess()) {
+          return ragconnect().defaultJavaUtilListToBytesMapping();
+        }
         try {
           TypeDecl typeDecl = program().resolveTypeDecl(targetTypeName());
-          // TODO: also support list-types, if list is last type
           return ragconnect().defaultTreeToBytesMapping(typeDecl.getName());
-        } catch (Exception ignore) {}
-        System.err.println("Could not find suitable default mapping for " + targetTypeName() + " on " + this);
+        } catch (Exception ignore) {
+          // exception should be logged to debug/fine
+        }
+        System.err.println("Could not find suitable default send mapping for " + targetTypeName() + " on " + this);
         return null;
     }
   }
-  eq TypeEndpointDefinition.suitableSendDefaultMapping() {
-    try {
-      TypeDecl typeDecl = program().resolveTypeDecl(targetTypeName());
-      return typeIsList() && getUseList() ? ragconnect().defaultListTreeToBytesMapping() : ragconnect().defaultTreeToBytesMapping(typeDecl.getName());
-    } catch (Exception ignore) {}
-    return super.suitableSendDefaultMapping();
-  }
 
   // --- targetTypeName ---
-  syn String EndpointDefinition.targetTypeName();
-  eq ReceiveTokenEndpointDefinition.targetTypeName() {
-    return getMappingList().isEmpty() ?
-           getToken().effectiveJavaTypeUse().getName() :
-           getMappingList().get(0).getFromType().prettyPrint();
-  }
-  eq ReceiveTypeEndpointDefinition.targetTypeName() {
-    return getMappingList().isEmpty() ?
-           getType().getTypeDecl().getName() :
-           getMappingList().get(0).getFromType().prettyPrint();
-  }
-  eq SendTokenEndpointDefinition.targetTypeName() {
-    return getMappingList().isEmpty() ?
-           getToken().effectiveJavaTypeUse().getName() :
-           getMappingList().get(getMappingList().size() - 1).getToType().prettyPrint();
-  }
-  eq SendTypeEndpointDefinition.targetTypeName() {
-    return getMappingList().isEmpty() ?
-           getType().getTypeDecl().getName() :
-           getMappingList().get(getMappingList().size() - 1).getToType().prettyPrint();
+  syn String PortDefinition.targetTypeName() {
+    if (getMappingList().isEmpty()) {
+      return getPortTarget().targetTypeName();
+    } else {
+      if (getSend()) {
+        return getMappingList().get(getMappingList().size() - 1).getToType().prettyPrint();
+      } else {
+        return getMappingList().get(0).getFromType().prettyPrint();
+      }
+    }
   }
+  syn String PortTarget.targetTypeName();
+  eq AttributePortTarget.targetTypeName() = getTypeName();
+  eq RelationPortTarget.targetTypeName() = getRole().oppositeRole().targetTypeName();
+  eq TokenPortTarget.targetTypeName() = getToken().effectiveJavaTypeUse().getName();
+  eq TypePortTarget.targetTypeName() = getType().getTypeDecl().getName();
+  eq ContextFreeTypePortTarget.targetTypeName() = getTypeDecl().getName();
+
+  syn String Role.targetTypeName() = getType().getName();
+  eq ListRole.targetTypeName() = "java.util.List<" + getType().getName() + ">";
 
-//  eq ReceiveFromRestDefinition.suitableDefaultMapping() {
-//    String typeName = getMappingList().isEmpty() ?
-//        getToken().getJavaTypeUse().getName() :
-//        getMappingList().get(0).getFromType().prettyPrint();
-//    switch(typeName) {
-//      case "int":
-//      case "Integer": return ragconnect().defaultStringToIntMapping();
-//      case "short":
-//      case "Short": return ragconnect().defaultStringToShortMapping();
-//      case "long":
-//      case "Long": return ragconnect().defaultStringToLongMapping();
-//      case "float":
-//      case "Float": return ragconnect().defaultStringToFloatMapping();
-//      case "double":
-//      case "Double": return ragconnect().defaultStringToDoubleMapping();
-//      case "char":
-//      case "Character": return ragconnect().defaultStringToCharMapping();
-//      default: return null;
-//    }
-//  }
-//  eq SendToRestDefinition.suitableDefaultMapping() {
-//    String typeName = getMappingList().isEmpty() ?
-//        getToken().getJavaTypeUse().getName() :
-//        getMappingList().get(getMappingList().size() - 1).getFromType().prettyPrint();
-//    switch(typeName) {
-//      case "int":
-//      case "Integer": return ragconnect().defaultIntToStringMapping();
-//      case "short":
-//      case "Short": return ragconnect().defaultShortToStringMapping();
-//      case "long":
-//      case "Long": return ragconnect().defaultLongToStringMapping();
-//      case "float":
-//      case "Float": return ragconnect().defaultFloatToStringMapping();
-//      case "double":
-//      case "Double": return ragconnect().defaultDoubleToStringMapping();
-//      case "char":
-//      case "Character": return ragconnect().defaultCharToStringMapping();
-//      default: return null;
-//    }
-//  }
+  //  eq ReceiveFromRestDefinition.suitableDefaultMapping() {
+  //    String typeName = getMappingList().isEmpty() ?
+  //        getToken().getJavaTypeUse().getName() :
+  //        getMappingList().get(0).getFromType().prettyPrint();
+  //    switch(typeName) {
+  //      case "int":
+  //      case "Integer": return ragconnect().defaultStringToIntMapping();
+  //      case "short":
+  //      case "Short": return ragconnect().defaultStringToShortMapping();
+  //      case "long":
+  //      case "Long": return ragconnect().defaultStringToLongMapping();
+  //      case "float":
+  //      case "Float": return ragconnect().defaultStringToFloatMapping();
+  //      case "double":
+  //      case "Double": return ragconnect().defaultStringToDoubleMapping();
+  //      case "char":
+  //      case "Character": return ragconnect().defaultStringToCharMapping();
+  //      default: return null;
+  //    }
+  //  }
+  //  eq SendToRestDefinition.suitableDefaultMapping() {
+  //    String typeName = getMappingList().isEmpty() ?
+  //        getToken().getJavaTypeUse().getName() :
+  //        getMappingList().get(getMappingList().size() - 1).getFromType().prettyPrint();
+  //    switch(typeName) {
+  //      case "int":
+  //      case "Integer": return ragconnect().defaultIntToStringMapping();
+  //      case "short":
+  //      case "Short": return ragconnect().defaultShortToStringMapping();
+  //      case "long":
+  //      case "Long": return ragconnect().defaultLongToStringMapping();
+  //      case "float":
+  //      case "Float": return ragconnect().defaultFloatToStringMapping();
+  //      case "double":
+  //      case "Double": return ragconnect().defaultDoubleToStringMapping();
+  //      case "char":
+  //      case "Character": return ragconnect().defaultCharToStringMapping();
+  //      default: return null;
+  //    }
+  //  }
 
   // --- isByteArray ---
   syn boolean MappingDefinitionType.isByteArray() = false;
   eq JavaArrayMappingDefinitionType.isByteArray() = getType().getName().equals("byte");
 
-//  // --- isString ---
-//  syn boolean MappingDefinitionType.isString() = false;
-//  eq JavaMappingDefinitionType.isString() = getType().getName().equals("String");
+  //  // --- isString ---
+  //  syn boolean MappingDefinitionType.isString() = false;
+  //  eq JavaMappingDefinitionType.isString() = getType().getName().equals("String");
 
   // --- allMappingDefinitions ---
   syn java.util.List<MappingDefinition> RagConnect.allMappingDefinitions() {
     java.util.List<MappingDefinition> result = new java.util.ArrayList<>();
     // user-defined mappings
-    allMappingDefinitionList().iterator().forEachRemaining(result::add);
+    givenMappingDefinitionList().iterator().forEachRemaining(result::add);
     // byte[] <-> primitive conversion
     result.add(defaultBytesToBooleanMapping());
     result.add(defaultBytesToIntMapping());
@@ -359,9 +408,13 @@ aspect Mappings {
     for (TypeDecl typeDecl : getProgram().typeDecls()) {
       result.add(defaultBytesToTreeMapping(typeDecl.getName()));
       result.add(defaultTreeToBytesMapping(typeDecl.getName()));
-      result.add(defaultBytesToListTreeMapping(typeDecl.getName()));
+      result.add(defaultBytesToListMapping(typeDecl.getName()));
+      result.add(deserializeListMapping(typeDecl.getName()));
     }
-    result.add(defaultListTreeToBytesMapping());
+    result.add(defaultListToBytesMapping());
+    result.add(defaultJavaUtilListToBytesMapping());
+    result.add(serializeListMapping());
+    result.add(serializeJavaUtilListMapping());
 //    // string conversion
 //    result.add(defaultStringToBooleanMapping());
 //    result.add(defaultStringToIntMapping());
diff --git a/ragconnect.base/src/main/jastadd/NameResolution.jrag b/ragconnect.base/src/main/jastadd/NameResolution.jrag
index 3b75bc0eb633830cb0e63c80d5d51e7d04e1b4f0..e470430d8f5d355cd90d4e79503ee431de994a18 100644
--- a/ragconnect.base/src/main/jastadd/NameResolution.jrag
+++ b/ragconnect.base/src/main/jastadd/NameResolution.jrag
@@ -1,18 +1,91 @@
-aspect NameResolution {
+aspect RagConnectNameResolution {
+  // --- lookupTokenPortDefinition ---
+  inh java.util.List<PortDefinition> PortDefinition.lookupTokenPortDefinitions(TokenComponent token);
+  inh java.util.List<PortDefinition> PortTarget.lookupTokenPortDefinitions(TokenComponent token);
+  eq RagConnect.getConnectSpecificationFile().lookupTokenPortDefinitions(TokenComponent token) = lookupTokenPortDefinitions(token);
+  syn java.util.List<PortDefinition> RagConnect.lookupTokenPortDefinitions(TokenComponent token) {
+    java.util.List<PortDefinition> result = new java.util.ArrayList<>();
+    for (PortTarget target : givenPortTargetList()) {
+      if (target.isTokenPortTarget() && target.asTokenPortTarget().getToken().equals(token)) {
+        result.add(target.containingPortDefinition());
+      }
+    }
+    return result;
+  }
+
+  // --- lookupGivenTypePortDefinition ---
+  inh java.util.List<PortDefinition> PortDefinition.lookupGivenTypePortDefinitions(TypeComponent type);
+  inh java.util.List<PortDefinition> PortTarget.lookupGivenTypePortDefinitions(TypeComponent type);
+  eq RagConnect.getConnectSpecificationFile().lookupGivenTypePortDefinitions(TypeComponent type) = lookupTypePortDefinitions(type, true);
+
+  // --- lookupAllTypePortDefinition ---
+  inh java.util.List<PortDefinition> PortDefinition.lookupAllTypePortDefinitions(TypeComponent type);
+  inh java.util.List<PortDefinition> PortTarget.lookupAllTypePortDefinitions(TypeComponent type);
+  eq RagConnect.getConnectSpecificationFile().lookupAllTypePortDefinitions(TypeComponent type) = lookupTypePortDefinitions(type, false);
+
+  syn java.util.List<PortDefinition> RagConnect.lookupTypePortDefinitions(TypeComponent type, boolean onlyGiven) {
+    java.util.List<PortDefinition> result = new java.util.ArrayList<>();
+    for (PortDefinition def : onlyGiven ? givenPortDefinitionList() : allPortDefinitionList()) {
+      PortTarget target = def.getPortTarget();
+      if (target.isTypePortTarget() && target.asTypePortTarget().getType().equals(type)) {
+        result.add(def);
+      }
+    }
+    return result;
+  }
+
+  // --- lookupContextFreeTypePortDefinition ---
+  inh java.util.List<PortDefinition> PortDefinition.lookupContextFreeTypePortDefinitions(TypeDecl typeDecl);
+  inh java.util.List<PortDefinition> PortTarget.lookupContextFreeTypePortDefinitions(TypeDecl typeDecl);
+  eq RagConnect.getConnectSpecificationFile().lookupContextFreeTypePortDefinitions(TypeDecl typeDecl) = lookupContextFreeTypePortDefinitions(typeDecl);
+  syn java.util.List<PortDefinition> RagConnect.lookupContextFreeTypePortDefinitions(TypeDecl typeDecl) {
+    java.util.List<PortDefinition> result = new java.util.ArrayList<>();
+    for (PortTarget target : givenPortTargetList()) {
+      if (target.isContextFreeTypePortTarget() && target.asContextFreeTypePortTarget().getTypeDecl().equals(typeDecl)) {
+        result.add(target.containingPortDefinition());
+      }
+    }
+    return result;
+  }
 
-  refine RefResolverStubs eq EndpointDefinition.resolveMappingByToken(String id, int position) {
-    // return a MappingDefinition
-    for (MappingDefinition mappingDefinition : ragconnect().allMappingDefinitionList()) {
+  // --- lookupDependencyDefinition ---
+  inh DependencyDefinition DependencyDefinition.lookupDependencyDefinition(TypeDecl source, String id);
+  eq RagConnect.getConnectSpecificationFile().lookupDependencyDefinition(TypeDecl source, String id) {
+    for (DependencyDefinition def : allDependencyDefinitionList()) {
+      if (def.getID().equals(id) && def.getSource().containingTypeDecl().equals(source)) {
+        return def;
+      }
+    }
+    return null;
+  }
+
+  // rel PortDefinition.Mapping* -> MappingDefinition
+  refine RefResolverStubs eq PortDefinition.resolveMappingByToken(String id, int position) {
+    MappingDefinition result = tryResolveMappingByToken(id);
+    if (result == null) {
+      System.err.println("Could not resolve MappingDefinition '" + id + "'.");
+    }
+    return result;
+  }
+  syn MappingDefinition PortDefinition.tryResolveMappingByToken(String id) {
+    for (MappingDefinition mappingDefinition : ragconnect().givenMappingDefinitionList()) {
       if (mappingDefinition.getID().equals(id)) {
         return mappingDefinition;
       }
     }
-    System.err.println("Could not resolve MappingDefinition '" + id + "'.");
     return null;
   }
 
+  // rel ___ -> TypeComponent
   refine RefResolverStubs eq ASTNode.globallyResolveTypeComponentByToken(String id) {
-    // return a TypeComponent. id is of the form 'parent_type_name + "." + child_type_name'
+    TypeComponent result = tryGloballyResolveTypeComponentByToken(id);
+    if (result == null) {
+      System.err.println("Could not resolve TypeComponent '" + id + "'.");
+    }
+    return result;
+  }
+  syn TypeComponent ASTNode.tryGloballyResolveTypeComponentByToken(String id) {
+    // id is of the form 'parent_type_name + "." + child_type_name'
     int dotIndex = id.indexOf(".");
     String parentTypeName = id.substring(0, dotIndex);
     String childTypeName = id.substring(dotIndex + 1);
@@ -23,12 +96,19 @@ aspect NameResolution {
         return comp.asTypeComponent();
       }
     }
-    System.err.println("Could not resolve TypeComponent '" + id + "'.");
     return null;
   }
 
+  // rel ___ -> Component
   refine RefResolverStubs eq ASTNode.globallyResolveComponentByToken(String id) {
-    // return a Component. id is of the form 'parent_type_name + "." + child_type_name'
+    Component result = tryGloballyResolveComponentByToken(id);
+    if (result == null) {
+      System.err.println("Could not resolve Component '" + id + "'.");
+    }
+    return result;
+  }
+  syn Component ASTNode.tryGloballyResolveComponentByToken(String id) {
+    // id is of the form 'parent_type_name + "." + child_type_name'
     int dotIndex = id.indexOf(".");
     String parentTypeName = id.substring(0, dotIndex);
     String childTypeName = id.substring(dotIndex + 1);
@@ -39,8 +119,66 @@ aspect NameResolution {
         return comp;
       }
     }
-    System.err.println("Could not resolve Component '" + id + "'.");
     return null;
   }
 
+  // rel ___ -> TokenComponent (from relast-preprocessor)
+  // refine from relast PP here to have an attribute that does not write on stderr if no TokenComponent was found
+  refine NameResolution eq ASTNode.globallyResolveTokenComponentByToken(String id) {
+    TokenComponent result = tryGloballyResolveTokenComponentByToken(id);
+    if (result == null) {
+      System.err.println("Could not resolve TokenComponent '" + id + "'.");
+    }
+    return result;
+  }
+  syn TokenComponent ASTNode.tryGloballyResolveTokenComponentByToken(String id) {
+    // id is of the form 'type_name + "." + token_name'
+    int dotIndex = id.indexOf(".");
+    String typeName = id.substring(0, dotIndex);
+    String tokenName = id.substring(dotIndex + 1);
+    TypeDecl type = program().resolveTypeDecl(typeName);
+    // iterate over components and find the matching tokenComponent
+    for (Component comp : type.getComponentList()) {
+      if (comp.isTokenComponent() && comp.getName().equals(tokenName)) {
+        return comp.asTokenComponent();
+      }
+    }
+    return null;
+  }
+
+  // rel ___ -> Navigable
+  refine RefResolverStubs eq ASTNode.globallyResolveNavigableRoleByToken(String id) {
+    NavigableRole result = tryGloballyResolveNavigableRoleByToken(id);
+    if (result == null) {
+      System.err.println("Could not resolve role '" + id + "'.");
+    }
+    return result;
+  }
+  syn NavigableRole ASTNode.tryGloballyResolveNavigableRoleByToken(String id) {
+    // id is of the form 'type_name + "." + role_name'
+    int dotIndex = id.indexOf(".");
+    String typeName = id.substring(0, dotIndex);
+    String roleName = id.substring(dotIndex + 1);
+    for (Relation relation : program().relations()) {
+      if (relation.isDirectedRelation()) {
+        if (relation.asDirectedRelation().getSource().matches(typeName, roleName)) {
+          return relation.asDirectedRelation().getSource();
+        }
+      } else {
+        if (relation.asBidirectionalRelation().getLeft().matches(typeName, roleName)) {
+          return relation.asBidirectionalRelation().getLeft();
+        }
+        if (relation.asBidirectionalRelation().getRight().matches(typeName, roleName)) {
+          return relation.asBidirectionalRelation().getRight();
+        }
+      }
+    }
+    return null;
+  }
+
+  syn boolean Role.matches(String typeName, String roleName) = false;
+  eq NavigableRole.matches(String typeName, String roleName) {
+    return getType().getName().equals(typeName) && getName().equals(roleName);
+  }
+
 }
diff --git a/ragconnect.base/src/main/jastadd/Navigation.jrag b/ragconnect.base/src/main/jastadd/Navigation.jrag
index 437f65196c2dc3b8c4f5a112a23e80608084a063..8b23c000fb8136af504d6dc452a42d5dd6ae82ca 100644
--- a/ragconnect.base/src/main/jastadd/Navigation.jrag
+++ b/ragconnect.base/src/main/jastadd/Navigation.jrag
@@ -1,25 +1,130 @@
+aspect GeneratedNavigation {
+
+  /** Tests if PortTarget is a TokenPortTarget.
+  *  @return 'true' if this is a TokenPortTarget, otherwise 'false'
+  */
+  syn boolean PortTarget.isTokenPortTarget() = false;
+  eq TokenPortTarget.isTokenPortTarget() = true;
+
+  /** Tests if PortTarget is a TypePortTarget.
+  *  @return 'true' if this is a TypePortTarget, otherwise 'false'
+  */
+  syn boolean PortTarget.isTypePortTarget() = false;
+  eq TypePortTarget.isTypePortTarget() = true;
+
+  /** Tests if PortTarget is a ContextFreeTypePortTarget.
+  *  @return 'true' if this is a ContextFreeTypePortTarget, otherwise 'false'
+  */
+  syn boolean PortTarget.isContextFreeTypePortTarget() = false;
+  eq ContextFreeTypePortTarget.isContextFreeTypePortTarget() = true;
+
+  /** Tests if PortTarget is a UntypedPortTarget.
+  *  @return 'true' if this is a UntypedPortTarget, otherwise 'false'
+  */
+  syn boolean PortTarget.isUntypedPortTarget() = false;
+  eq UntypedPortTarget.isUntypedPortTarget() = true;
+
+  /** Tests if PortTarget is a AttributePortTarget.
+  *  @return 'true' if this is a AttributePortTarget, otherwise 'false'
+  */
+  syn boolean PortTarget.isAttributePortTarget() = false;
+  eq AttributePortTarget.isAttributePortTarget() = true;
+
+  /** Tests if PortTarget is a RelationPortTarget.
+  *  @return 'true' if this is a RelationPortTarget, otherwise 'false'
+  */
+  syn boolean PortTarget.isRelationPortTarget() = false;
+  eq RelationPortTarget.isRelationPortTarget() = true;
+
+  /** Tests if TemplateDefaultMappingDefinition is a SerializeListMapping.
+  *  @return 'true' if this is a SerializeListMapping, otherwise 'false'
+  */
+  syn boolean TemplateDefaultMappingDefinition.isSerializeListMapping() = false;
+  eq SerializeListMapping.isSerializeListMapping() = true;
+
+  /** Tests if TemplateDefaultMappingDefinition is a SerializeJavaUtilListMapping.
+  *  @return 'true' if this is a SerializeJavaUtilListMapping, otherwise 'false'
+  */
+  syn boolean TemplateDefaultMappingDefinition.isSerializeJavaUtilListMapping() = false;
+  eq SerializeJavaUtilListMapping.isSerializeJavaUtilListMapping() = true;
+
+  /** Tests if TemplateDefaultMappingDefinition is a DeserializeListMapping.
+  *  @return 'true' if this is a DeserializeListMapping, otherwise 'false'
+  */
+  syn boolean TemplateDefaultMappingDefinition.isDeserializeListMapping() = false;
+  eq DeserializeListMapping.isDeserializeListMapping() = true;
+
+  /** casts a PortTarget into a TokenPortTarget if possible.
+   *  @return 'this' cast to a TokenPortTarget or 'null'
+   */
+  syn TokenPortTarget PortTarget.asTokenPortTarget();
+  eq PortTarget.asTokenPortTarget() = null;
+  eq TokenPortTarget.asTokenPortTarget() = this;
+
+  /** casts a PortTarget into a TypePortTarget if possible.
+   *  @return 'this' cast to a TypePortTarget or 'null'
+   */
+  syn TypePortTarget PortTarget.asTypePortTarget();
+  eq PortTarget.asTypePortTarget() = null;
+  eq TypePortTarget.asTypePortTarget() = this;
+
+  /** casts a PortTarget into a ContextFreeTypePortTarget if possible.
+   *  @return 'this' cast to a ContextFreeTypePortTarget or 'null'
+   */
+  syn ContextFreeTypePortTarget PortTarget.asContextFreeTypePortTarget();
+  eq PortTarget.asContextFreeTypePortTarget() = null;
+  eq ContextFreeTypePortTarget.asContextFreeTypePortTarget() = this;
+
+  /** casts a PortTarget into a UntypedPortTarget if possible.
+   *  @return 'this' cast to a UntypedPortTarget or 'null'
+   */
+  syn UntypedPortTarget PortTarget.asUntypedPortTarget();
+  eq PortTarget.asUntypedPortTarget() = null;
+  eq UntypedPortTarget.asUntypedPortTarget() = this;
+
+  /** casts a PortTarget into a AttributePortTarget if possible.
+   *  @return 'this' cast to a AttributePortTarget or 'null'
+   */
+  syn AttributePortTarget PortTarget.asAttributePortTarget();
+  eq PortTarget.asAttributePortTarget() = null;
+  eq AttributePortTarget.asAttributePortTarget() = this;
+
+  /** casts a PortTarget into a RelationPortTarget if possible.
+   *  @return 'this' cast to a RelationPortTarget or 'null'
+   */
+  syn RelationPortTarget PortTarget.asRelationPortTarget();
+  eq PortTarget.asRelationPortTarget() = null;
+  eq RelationPortTarget.asRelationPortTarget() = this;
+}
 aspect RagConnectNavigation {
 
+  // adapted from generated navigation, was defined only for DefaultMappingDefinition
+  /** Tests if MappingDefinition is a TemplateDefaultMappingDefinition.
+  *  @return 'true' if this is a TemplateDefaultMappingDefinition, otherwise 'false'
+  */
+  syn boolean MappingDefinition.isTemplateDefaultMappingDefinition() = false;
+  eq TemplateDefaultMappingDefinition.isTemplateDefaultMappingDefinition() = true;
+
   // --- program ---
   eq RagConnect.getChild().program() = getProgram();
-  eq MRagConnect.getChild().program() = getRagConnect().program();
 
   // --- ragconnect ---
   inh RagConnect ASTNode.ragconnect();
   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;
 
+  // --- containingPortDefinition ---
+  inh PortDefinition PortTarget.containingPortDefinition();
+  eq PortDefinition.getPortTarget().containingPortDefinition() = this;
+
   // --- containedFile
   eq RagConnect.getChild().containedFile() = null;
-  eq MRagConnect.getChild().containedFile() = null;
 
   // --- containedFileName ---
   eq ConnectSpecificationFile.containedFileName() = getFileName();
@@ -31,111 +136,43 @@ aspect RagConnectNavigation {
 //    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;
-  }
+  // --- isFirstInList ---
+  inh boolean DependencyDefinition.isFirstInList();
+  eq ConnectSpecification.getDependencyDefinition(int index).isFirstInList() = index == 0;
 
-  //--- allDependencyDefinitionList ---
-  syn List<DependencyDefinition> RagConnect.allDependencyDefinitionList() {
-    List<DependencyDefinition> result = new ArrayList<>();
-    for (var spec : getConnectSpecificationFileList()) {
-      spec.getDependencyDefinitionList().forEach(result::add);
+  //--- givenPortTargetList ---
+  syn List<PortTarget> RagConnect.givenPortTargetList() {
+    List<PortTarget> result = new ArrayList<>();
+    for (ConnectSpecification spec : getConnectSpecificationFileList()) {
+      spec.getPortDefinitionList().forEach(portDef -> result.add(portDef.getPortTarget()));
     }
     return result;
   }
 
-  //--- allMappingDefinitionList ---
-  syn List<MappingDefinition> RagConnect.allMappingDefinitionList() {
+
+  //--- givenMappingDefinitionList ---
+  syn List<MappingDefinition> RagConnect.givenMappingDefinitionList() {
     List<MappingDefinition> result = new ArrayList<>();
-    for (var spec : getConnectSpecificationFileList()) {
+    for (ConnectSpecification spec : getConnectSpecificationFileList()) {
       spec.getMappingDefinitionList().forEach(result::add);
     }
     return result;
   }
 
-  // --- isTokenEndpointDefinition ---
-  syn boolean EndpointDefinition.isTokenEndpointDefinition() = false;
-  eq TokenEndpointDefinition.isTokenEndpointDefinition() = true;
-
-  // --- asTokenEndpointDefinition ---
-  syn TokenEndpointDefinition EndpointDefinition.asTokenEndpointDefinition() = null;
-  eq TokenEndpointDefinition.asTokenEndpointDefinition() = this;
-
-  // --- isTypeEndpointDefinition ---
-  syn boolean EndpointDefinition.isTypeEndpointDefinition() = false;
-  eq TypeEndpointDefinition.isTypeEndpointDefinition() = true;
-
-  // --- asTypeEndpointDefinition ---
-  syn TypeEndpointDefinition EndpointDefinition.asTypeEndpointDefinition() = null;
-  eq TypeEndpointDefinition.asTypeEndpointDefinition() = this;
-
-  // --- isReceiveTokenEndpointDefinition ---
-  syn boolean EndpointDefinition.isReceiveTokenEndpointDefinition() = false;
-  eq ReceiveTokenEndpointDefinition.isReceiveTokenEndpointDefinition() = true;
-
-  // --- asReceiveTokenEndpointDefinition ---
-  syn ReceiveTokenEndpointDefinition EndpointDefinition.asReceiveTokenEndpointDefinition() = null;
-  eq ReceiveTokenEndpointDefinition.asReceiveTokenEndpointDefinition() = this;
-
-  // --- isSendTokenEndpointDefinition ---
-  syn boolean EndpointDefinition.isSendTokenEndpointDefinition() = false;
-  eq SendTokenEndpointDefinition.isSendTokenEndpointDefinition() = true;
-
-  // --- asSendTokenEndpointDefinition ---
-  syn SendTokenEndpointDefinition EndpointDefinition.asSendTokenEndpointDefinition() = null;
-  eq SendTokenEndpointDefinition.asSendTokenEndpointDefinition() = this;
-
-  // --- isReceiveTypeEndpointDefinition ---
-  syn boolean EndpointDefinition.isReceiveTypeEndpointDefinition() = false;
-  eq ReceiveTypeEndpointDefinition.isReceiveTypeEndpointDefinition() = true;
-
-  // --- asReceiveTypeEndpointDefinition ---
-  syn ReceiveTypeEndpointDefinition EndpointDefinition.asReceiveTypeEndpointDefinition() = null;
-  eq ReceiveTypeEndpointDefinition.asReceiveTypeEndpointDefinition() = this;
-
-  // --- isSendTypeEndpointDefinition ---
-  syn boolean EndpointDefinition.isSendTypeEndpointDefinition() = false;
-  eq SendTypeEndpointDefinition.isSendTypeEndpointDefinition() = true;
-
-  // --- asSendTypeEndpointDefinition ---
-  syn SendTypeEndpointDefinition EndpointDefinition.asSendTypeEndpointDefinition() = null;
-  eq SendTypeEndpointDefinition.asSendTypeEndpointDefinition() = this;
-
-  // --- targetEndpointDefinition ---
-  syn EndpointDefinition DependencyDefinition.targetEndpointDefinition() {
-    // resolve definition in here, as we do not need resolveMethod in any other place (yet)
-    for (EndpointDefinition endpointDefinition : ragconnect().allEndpointDefinitionList()) {
-      if (endpointDefinition.isSendTokenEndpointDefinition() &&
-          endpointDefinition.asSendTokenEndpointDefinition().getToken().equals(this.getTarget())) {
-        return endpointDefinition;
-      }
-      if (endpointDefinition.isSendTypeEndpointDefinition() &&
-          endpointDefinition.asSendTypeEndpointDefinition().getType().equals(this.getTarget())) {
-        return endpointDefinition;
-      }
-    }
-    return null;
-  }
-
   // --- effectiveJavaTypeUse (should be in preprocessor) ---
   syn lazy JavaTypeUse TokenComponent.effectiveJavaTypeUse() = hasJavaTypeUse() ? getJavaTypeUse() : new SimpleJavaTypeUse("String");
 
+  // --- oppositeRole ---
+  inh Role Role.oppositeRole();
+  eq DirectedRelation.getSource().oppositeRole() = getTarget();
+  eq DirectedRelation.getTarget().oppositeRole() = getSource();
+  eq BidirectionalRelation.getLeft().oppositeRole() = getRight();
+  eq BidirectionalRelation.getRight().oppositeRole() = getLeft();
+
   // --- isDefaultMappingDefinition ---
   syn boolean MappingDefinition.isDefaultMappingDefinition() = false;
   eq DefaultMappingDefinition.isDefaultMappingDefinition() = true;
 
-  // --- mragconnect ---
-  inh MRagConnect MHandler.mragconnect();
-  eq MRagConnect.getHandler().mragconnect() = this;
-
-  // --- 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/Printing.jrag b/ragconnect.base/src/main/jastadd/Printing.jrag
index 89b9b82a9abea9786979b3864449f529b57bbd96..bcc7cfb231c1a4e4117c634ff082f78d5fd26320 100644
--- a/ragconnect.base/src/main/jastadd/Printing.jrag
+++ b/ragconnect.base/src/main/jastadd/Printing.jrag
@@ -1,7 +1,7 @@
 aspect Printing {
   syn String MappingDefinitionType.prettyPrint();
-  eq JavaMappingDefinitionType.prettyPrint() = getType().getName();
-  eq JavaArrayMappingDefinitionType.prettyPrint() = getType().getName() + "[]";
+  eq JavaMappingDefinitionType.prettyPrint() = getType().prettyPrint();
+  eq JavaArrayMappingDefinitionType.prettyPrint() = getType().prettyPrint() + "[]";
 
   syn String JavaTypeUse.prettyPrint() {
     StringBuilder sb = new StringBuilder();
diff --git a/ragconnect.base/src/main/jastadd/RagConnect.relast b/ragconnect.base/src/main/jastadd/RagConnect.relast
index c1cbbb2445e55956f41ee780eab2e216175b6b6f..7282bb3fda5771c3b1ea87a4bfae235c3950b640 100644
--- a/ragconnect.base/src/main/jastadd/RagConnect.relast
+++ b/ragconnect.base/src/main/jastadd/RagConnect.relast
@@ -1,30 +1,49 @@
-RagConnect ::= ConnectSpecificationFile* Program ;
-
-abstract ConnectSpecification ::= EndpointDefinition* DependencyDefinition* MappingDefinition* ;
-ConnectSpecificationFile : ConnectSpecification ::= <FileName> ;
-
-abstract EndpointDefinition ::= <AlwaysApply:boolean> ;
-
-rel EndpointDefinition.Mapping* <-> MappingDefinition.UsedAt*;
-
-abstract TokenEndpointDefinition : EndpointDefinition;
-rel TokenEndpointDefinition.Token <-> TokenComponent.TokenEndpointDefinition*;
-
-ReceiveTokenEndpointDefinition : TokenEndpointDefinition;
-SendTokenEndpointDefinition : TokenEndpointDefinition;
-
-abstract TypeEndpointDefinition : EndpointDefinition ::= <UseList:boolean> ;
-rel TypeEndpointDefinition.Type <-> TypeComponent.TypeEndpointDefinition*;
-
-ReceiveTypeEndpointDefinition : TypeEndpointDefinition ::= <WithAdd:boolean>;
-SendTypeEndpointDefinition : TypeEndpointDefinition;
+RagConnect ::= ConnectSpecificationFile* Program Handler* Configuration;
+
+abstract ConnectSpecification ::= PortDefinition* DependencyDefinition* MappingDefinition*;
+ConnectSpecificationFile : ConnectSpecification ::= <FileName>;
+
+PortDefinition ::= <AlwaysApply:boolean> <IndexBasedListAccess:boolean> <WithAdd:boolean> <Send:boolean> PortTarget;
+rel PortDefinition.Mapping* <-> MappingDefinition.UsedAt*;
+
+abstract PortTarget;
+TokenPortTarget : PortTarget;
+rel TokenPortTarget.Token <-> TokenComponent.TokenPortTarget*;
+TypePortTarget : PortTarget;
+rel TypePortTarget.Type <-> TypeComponent.TypePortTarget*;
+ContextFreeTypePortTarget : PortTarget;
+rel ContextFreeTypePortTarget.TypeDecl <-> TypeDecl.ContextFreeTypePortTarget*;
+AttributePortTarget : PortTarget ::= <Name> <TypeName> ;
+rel AttributePortTarget.ParentTypeDecl <-> TypeDecl.AttributePortTarget*;
+RelationPortTarget : PortTarget ;
+rel RelationPortTarget.Role <-> NavigableRole.RelationPortTarget* ;
+UntypedPortTarget : PortTarget ::= <TypeName> <ChildName> <IsAttribute:boolean>;  // only used by parser
 
 DependencyDefinition ::= <ID>;
 rel DependencyDefinition.Source <-> TokenComponent.DependencySourceDefinition*;
 rel DependencyDefinition.Target -> Component;
 
-MappingDefinition ::= <ID> FromType:MappingDefinitionType <FromVariableName> ToType:MappingDefinitionType <Content> ;
+MappingDefinition ::= <ID> FromType:MappingDefinitionType <FromVariableName> ToType:MappingDefinitionType <Content>;
 abstract MappingDefinitionType ::= ;
-JavaMappingDefinitionType : MappingDefinitionType ::= Type:JavaTypeUse ;
-JavaArrayMappingDefinitionType : MappingDefinitionType ::= Type:JavaTypeUse ;
-DefaultMappingDefinition : MappingDefinition ;
+JavaMappingDefinitionType : MappingDefinitionType ::= Type:JavaTypeUse;
+JavaArrayMappingDefinitionType : MappingDefinitionType ::= Type:JavaTypeUse;
+DefaultMappingDefinition : MappingDefinition;
+abstract TemplateDefaultMappingDefinition : DefaultMappingDefinition ;
+SerializeListMapping : TemplateDefaultMappingDefinition ;
+SerializeJavaUtilListMapping : TemplateDefaultMappingDefinition ;
+DeserializeListMapping : TemplateDefaultMappingDefinition ::= <Name> ;
+
+Handler ::= <ClassName> <UniqueName> <InUse:boolean>;
+
+Configuration ::=
+<LoggingEnabledForReads:boolean>
+<LoggingEnabledForWrites:boolean>
+<LoggingEnabledForIncremental:boolean>
+<LoggingTarget:String>
+<JastAddList:String>
+<JastAddOpt:String>
+<IncrementalOptionActive:boolean>
+<CacheAllOptionActive:boolean>
+<EvaluationCounter:boolean>
+<ExperimentalJastAdd329:boolean>;
+rel Configuration.RootNode -> TypeDecl ;
diff --git a/ragconnect.base/src/main/jastadd/Util.jadd b/ragconnect.base/src/main/jastadd/Util.jadd
index 3b59b13c53a8d0e1ca2a05feba43294797a4577e..174f8cdb9e40db6f3b1edef519b31daade2c1bba 100644
--- a/ragconnect.base/src/main/jastadd/Util.jadd
+++ b/ragconnect.base/src/main/jastadd/Util.jadd
@@ -1,5 +1,65 @@
 aspect Util {
   static String ASTNode.capitalize(String s) {
+    if (s == null) return null;
+    if (s.isEmpty()) return "";
     return Character.toUpperCase(s.charAt(0)) + s.substring(1);
   }
+  protected T JastAddList.firstChild() { return getChild(0); }
+  protected T JastAddList.lastChild() { return getChild(getNumChild() - 1); }
+
+  public class CompilerMessage implements Comparable<CompilerMessage> {
+    private final ASTNode node;
+    private final String filename;
+    private final int line;
+    private final int col;
+    private final String message;
+
+    public CompilerMessage(ASTNode node, String message) {
+      this.node = node;
+      this.filename = node.containedFileName();
+      this.line = node.getStartLine();
+      this.col = node.getStartColumn();
+      this.message = message;
+    }
+
+    public ASTNode getNode() {
+      return node;
+    }
+
+    public int getLine() {
+      return line;
+    }
+
+    public int getCol() {
+      return col;
+    }
+
+    public String getMessage() {
+      return message;
+    }
+
+    public String toString() {
+      return filename + " Line " + line + ", column " + col + ": " + message;
+    }
+
+    @Override
+    public int compareTo(CompilerMessage other) {
+      int n = filename.compareTo(other.filename);
+      if (n != 0) {
+        return n;
+      }
+
+      n = line - other.line;
+      if (n != 0) {
+        return n;
+      }
+
+      n = col - other.col;
+      if (n != 0) {
+        return n;
+      }
+
+      return message.compareTo(other.message);
+    }
+  }
 }
diff --git a/ragconnect.base/src/main/jastadd/Warnings.jrag b/ragconnect.base/src/main/jastadd/Warnings.jrag
new file mode 100644
index 0000000000000000000000000000000000000000..49a36233c463f5c8b0b1eb391e490ff526a94472
--- /dev/null
+++ b/ragconnect.base/src/main/jastadd/Warnings.jrag
@@ -0,0 +1,27 @@
+aspect Warnings {
+  coll Set<CompilerMessage> RagConnect.warnings()
+    [new TreeSet<CompilerMessage>()]
+    root RagConnect;
+
+  DependencyDefinition contributes warning("Dependency definition should not be used if incremental evaluation is enabled!")
+    when ragconnect().configIncrementalOptionActive()
+    to RagConnect.warnings();
+
+  DependencyDefinition contributes warning("Dependency definition are deprecated since 1.0.0!")
+    when this.isFirstInList()  // print warning only once for first dependency definition
+    to RagConnect.warnings();
+
+  PortDefinition contributes warning("No dependency definitions are given, and incremental evaluation is disabled. No messages will be sent for this!")
+    when getSend() && ragconnect().allDependencyDefinitionList().isEmpty() && !ragconnect().configIncrementalOptionActive()
+    to RagConnect.warnings();
+
+  ConnectSpecificationFile contributes warning("Incremental evaluation is disabled, but cache=all is set. This might lead to no messages sent!")
+    when !ragconnect().configIncrementalOptionActive() && ragconnect().getConfiguration().getCacheAllOptionActive()
+    to RagConnect.warnings();
+}
+
+aspect WarningHelpers {
+  protected CompilerMessage ASTNode.warning(String message) {
+    return new CompilerMessage(this, message);
+  }
+}
diff --git a/ragconnect.base/src/main/jastadd/intermediate/Generation.jadd b/ragconnect.base/src/main/jastadd/intermediate/Generation.jadd
deleted file mode 100644
index b58b9670abd0abb26db113c3fee6f98033e3f4f7..0000000000000000000000000000000000000000
--- a/ragconnect.base/src/main/jastadd/intermediate/Generation.jadd
+++ /dev/null
@@ -1,428 +0,0 @@
-/*
-Design considerations
-- InnerMappingDefinition needed for iteration attribute (first, last) - not easily possible with list-relation
-*/
-
-aspect AttributesForMustache {
-  // --- EndpointDefinition ---
-  syn String EndpointDefinition.idTokenName() = "InternalRagconnectTopicInList";
-
-  // --- MRagConnect ---
-  eq MRagConnect.getRootTypeComponent(int i).isFirst() = i == 0;
-
-  syn String MRagConnect.closeMethod() = "ragconnectCloseConnections";
-  syn String MRagConnect.mqttHandlerAttribute() = "_mqttHandler";
-  syn String MRagConnect.mqttHandlerField() = "_mqttHandler";
-  syn String MRagConnect.mqttSetupWaitUntilReadyMethod() = "ragconnectSetupMqttWaitUntilReady";
-
-  syn String MRagConnect.restHandlerAttribute() = "_restHandler";
-  syn String MRagConnect.restHandlerField() = "_restHandler";
-
-  syn boolean MRagConnect.hasTreeListEndpoints() = !sendingTreeListEndpoints().isEmpty() || !receivingTreeListEndpoints().isEmpty();
-  syn List<MTypeEndpointDefinition> MRagConnect.sendingTreeListEndpoints() {
-    List<MTypeEndpointDefinition> result = new ArrayList<>();
-    for (var mEndpointDef : getTypeSendDefinitionList()) {
-      if (mEndpointDef.typeIsList()) {
-        result.add(mEndpointDef);
-      }
-    }
-    return result;
-  }
-  syn List<MTypeEndpointDefinition> MRagConnect.receivingTreeListEndpoints() {
-    List<MTypeEndpointDefinition> result = new ArrayList<>();
-    for (var mEndpointDef : getTypeReceiveDefinitionList()) {
-      if (mEndpointDef.typeIsList()) {
-        result.add(mEndpointDef);
-      }
-    }
-    return result;
-  }
-  syn List<TypeDecl> MRagConnect.typesForReceivingListEndpoints() {
-    return receivingTreeListEndpoints().stream()
-      .map(mEndpointDef -> mEndpointDef.type().getTypeDecl())
-      .distinct()
-      .collect(java.util.stream.Collectors.toList());
-  }
-
-  // --- MEndpointDefinition ---
-  syn String MEndpointDefinition.preemptiveExpectedValue();
-  syn String MEndpointDefinition.preemptiveReturn();
-  syn EndpointDefinition MEndpointDefinition.endpointDef();
-  syn String MEndpointDefinition.firstInputVarName();
-  syn String MEndpointDefinition.parentTypeName();
-  syn String MEndpointDefinition.entityName();
-  syn String MEndpointDefinition.updateMethod();
-  syn String MEndpointDefinition.writeMethod();
-  syn String MEndpointDefinition.getterMethod();
-
-  eq MEndpointDefinition.getInnerMappingDefinition(int i).isLast() = i == getNumInnerMappingDefinition() - 1;
-  eq MEndpointDefinition.getInnerMappingDefinition(int i).inputVarName() = i == 0 ? firstInputVarName() : getInnerMappingDefinition(i - 1).outputVarName();
-
-  syn String MEndpointDefinition.connectParameterName() = "uriString";
-  syn String MEndpointDefinition.connectMethod() = "connect" + entityName();
-  syn String MEndpointDefinition.internalConnectMethod() = "_internal_" + connectMethod();
-  syn boolean MEndpointDefinition.isTypeEndpointDefinition() = endpointDef().isTypeEndpointDefinition();
-
-  syn String MEndpointDefinition.disconnectMethod() {
-    // if both (send and receive) are defined for an endpoint, ensure methods with different names
-    String extra;
-    if (endpointDef().isTokenEndpointDefinition()) {
-      extra = endpointDef().asTokenEndpointDefinition().lookupTokenEndpointDefinitions(token()).size() > 1 ? uniqueSuffix() : "";
-    } else if (endpointDef().isTypeEndpointDefinition()) {
-      extra = endpointDef().asTypeEndpointDefinition().lookupTypeEndpointDefinitions(type()).size() > 1 ? uniqueSuffix() : "";
-    } else {
-      extra = "";
-    }
-    return "disconnect" + extra + entityName();
-  }
-
-  syn String MEndpointDefinition.uniqueSuffix();
-  eq MTokenSendDefinition.uniqueSuffix() = "Send";
-  eq MTokenReceiveDefinition.uniqueSuffix() = "Receive";
-  eq MTypeSendDefinition.uniqueSuffix() = "Send";
-  eq MTypeReceiveDefinition.uniqueSuffix() = "Receive";
-
-  // TODO potentially dangerous because asXEndpointDefinition can return null
-  syn TokenComponent MEndpointDefinition.token() = endpointDef().asTokenEndpointDefinition().getToken();
-  syn TypeComponent MEndpointDefinition.type() = endpointDef().asTypeEndpointDefinition().getType();
-  syn boolean MEndpointDefinition.alwaysApply() = endpointDef().getAlwaysApply();
-  syn boolean MEndpointDefinition.typeIsList() = endpointDef().typeIsList();
-  syn String MEndpointDefinition.tokenName() = token().getName();
-  syn String MEndpointDefinition.typeName() = type().getName();
-  syn String MEndpointDefinition.typeDeclName() = type().getTypeDecl().getName();
-  syn MInnerMappingDefinition MEndpointDefinition.lastDefinition() = getInnerMappingDefinition(getNumInnerMappingDefinition() - 1);
-  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().isReceiveTypeEndpointDefinition() && endpointDef().asReceiveTypeEndpointDefinition().getWithAdd()) {
-      // only check if received list is not null
-      return lastResult() + " == null";
-    }
-    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() + ")";
-    }
-    return preemptiveExpectedValue() + " != null ? " + preemptiveExpectedValue() + ".equals(" + lastResult() + ") : " + lastResult() + " == null";
-  }
-  syn String MEndpointDefinition.sender() = null; // only for M*SendDefinitions
-  syn String MEndpointDefinition.lastValue() = sender() + ".lastValue"; // only for M*SendDefinitions
-
-  // --- MTokenEndpointDefinition ---
-  eq MTokenEndpointDefinition.getterMethod() = "get" + tokenName();
-  eq MTokenEndpointDefinition.parentTypeName() = token().containingTypeDecl().getName();
-  eq MTokenEndpointDefinition.entityName() = tokenName();
-
-  // --- MTypeEndpointDefinition ---
-  syn boolean MTypeEndpointDefinition.isWithAdd() = endpointDef().isReceiveTypeEndpointDefinition() ? endpointDef().asReceiveTypeEndpointDefinition().getWithAdd() : false;
-  syn boolean MTypeEndpointDefinition.isUseList() = endpointDef().asTypeEndpointDefinition().getUseList();
-  eq MTypeEndpointDefinition.getterMethod() = "get" + typeName() + (typeIsList() ? "List" : "");
-  eq MTypeEndpointDefinition.parentTypeName() = type().containingTypeDecl().getName();
-  eq MTypeEndpointDefinition.entityName() = typeName() + (isUseList() ? "List" : "");
-
-  // --- MInnerMappingDefinition ---
-  inh boolean MInnerMappingDefinition.isLast();
-  inh String MInnerMappingDefinition.inputVarName();
-  syn String MInnerMappingDefinition.toType() = mappingDef().getToType().prettyPrint();
-  syn String MInnerMappingDefinition.methodName() = getMMappingDefinition().methodName();
-  syn MappingDefinition MInnerMappingDefinition.mappingDef() = getMMappingDefinition().getMappingDefinition();
-  syn String MInnerMappingDefinition.outputVarName() = "result" + methodName();  // we do not need "_" in between here, because methodName begins with one
-
-  // --- MTokenReceiveDefinition ---
-  eq MTokenReceiveDefinition.preemptiveExpectedValue() = getterMethod() + "()";
-  eq MTokenReceiveDefinition.preemptiveReturn() = "return;";
-  eq MTokenReceiveDefinition.endpointDef() = getReceiveTokenEndpointDefinition();
-  eq MTokenReceiveDefinition.firstInputVarName() = "message";
-  eq MTokenReceiveDefinition.updateMethod() = null;
-  eq MTokenReceiveDefinition.writeMethod() = null;
-
-  // --- MTokenSendDefinition ---
-  eq MTokenSendDefinition.preemptiveExpectedValue() = lastValue();
-  eq MTokenSendDefinition.preemptiveReturn() = "return false;";
-  eq MTokenSendDefinition.endpointDef() = getSendTokenEndpointDefinition();
-  eq MTokenSendDefinition.firstInputVarName() = getterMethod() + "()";
-  eq MTokenSendDefinition.updateMethod() = "_update_" + tokenName();
-  eq MTokenSendDefinition.writeMethod() = "_writeLastValue_" + tokenName();
-
-  eq MTokenSendDefinition.sender() = "_sender_" + tokenName();
-  syn String MTokenSendDefinition.tokenResetMethod() = getterMethod() + "_reset";
-  syn boolean MTokenSendDefinition.shouldSendValue() = endpointDef().asTokenEndpointDefinition().shouldSendValue();
-
-  // MTypeReceiveDefinition
-  eq MTypeReceiveDefinition.preemptiveExpectedValue() = getterMethod() + "()";
-  eq MTypeReceiveDefinition.preemptiveReturn() = "return;";
-  eq MTypeReceiveDefinition.endpointDef() = getReceiveTypeEndpointDefinition();
-  eq MTypeReceiveDefinition.firstInputVarName() = "message";
-  eq MTypeReceiveDefinition.updateMethod() = null;
-  eq MTypeReceiveDefinition.writeMethod() = null;
-
-  syn String MTypeReceiveDefinition.resolveInListMethodName() = "_ragconnect_resolve" + entityName() + "InList";
-  syn String MTypeReceiveDefinition.idTokenName() = endpointDef().idTokenName();
-
-  // MTypeSendDefinition
-  eq MTypeSendDefinition.preemptiveExpectedValue() = lastValue();
-  eq MTypeSendDefinition.preemptiveReturn() = "return false;";
-  eq MTypeSendDefinition.endpointDef() = getSendTypeEndpointDefinition();
-  eq MTypeSendDefinition.firstInputVarName() = getterMethod() + "()";
-  eq MTypeSendDefinition.updateMethod() = "_update_" + typeName();
-  eq MTypeSendDefinition.writeMethod() = "_writeLastValue_" + typeName();
-
-  eq MTypeSendDefinition.sender() = "_sender_" + typeName();
-  syn String MTypeSendDefinition.tokenResetMethod() = getterMethod() + "_reset";
-  syn boolean MTypeSendDefinition.shouldSendValue() = endpointDef().asTypeEndpointDefinition().shouldSendValue();
-
-  // --- MMappingDefinition ---
-  syn String MMappingDefinition.toType() = getMappingDefinition().getToType().prettyPrint();
-  syn String MMappingDefinition.methodName() = "_apply_" + getMappingDefinition().getID();
-  syn String MMappingDefinition.fromType() = getMappingDefinition().getFromType().prettyPrint();
-  syn String MMappingDefinition.fromVariableName() = getMappingDefinition().getFromVariableName();
-  syn String MMappingDefinition.content() = getMappingDefinition().getContent();
-  syn boolean MMappingDefinition.isUsed() = !getMappingDefinition().effectiveUsedAt().isEmpty();
-
-  // --- MDependencyDefinition ---
-  syn String MDependencyDefinition.targetParentTypeName() = getDependencyDefinition().getTarget().containingTypeDecl().getName();
-  syn String MDependencyDefinition.dependencyMethod() = "add" + capitalize(getDependencyDefinition().getID());
-  syn String MDependencyDefinition.sourceParentTypeName() = getDependencyDefinition().getSource().containingTypeDecl().getName();
-  syn String MDependencyDefinition.internalRelationPrefix() = "_internal_" + getDependencyDefinition().getID();
-  syn nta MEndpointDefinition MDependencyDefinition.targetEndpointDefinition() {
-    return getDependencyDefinition().targetEndpointDefinition().toMustache();
-  }
-
-  // --- MTypeComponent ---
-  syn String MTypeComponent.name() = getTypeComponent().getName();
-  inh boolean MTypeComponent.isFirst();
-
-  // --- MTokenComponent ---
-  syn String MTokenComponent.parentTypeName() = getTokenComponent().containingTypeDecl().getName();
-  syn String MTokenComponent.name() = getTokenComponent().getName();
-  syn String MTokenComponent.javaType() = getTokenComponent().effectiveJavaTypeUse().prettyPrint();
-  syn String MTokenComponent.internalName() = getTokenComponent().needProxyToken() ? "_internal_" + name() : externalName();
-  syn String MTokenComponent.externalName() = name();
-  syn MTokenSendDefinition MTokenComponent.normalTokenSendDef() {
-    for (TokenEndpointDefinition endpointDef : getTokenComponent().getTokenEndpointDefinitionList()) {
-      if (endpointDef.shouldSendValue()) {
-        return endpointDef.asSendTokenEndpointDefinition().toMustache();
-      }
-    }
-    return null;
-  }
-
-  // --- toMustache ---
-  syn lazy MRagConnect RagConnect.toMustache() {
-    MRagConnect result = new MRagConnect();
-    result.setRagConnect(this);
-    for (EndpointDefinition def : allEndpointDefinitionList()) {
-      if (def.isReceiveTokenEndpointDefinition()) {
-        result.addTokenReceiveDefinition(def.asReceiveTokenEndpointDefinition().toMustache());
-      } else if (def.isSendTokenEndpointDefinition()) {
-        result.addTokenSendDefinition(def.asSendTokenEndpointDefinition().toMustache());
-      } else if (def.isReceiveTypeEndpointDefinition()) {
-        result.addTypeReceiveDefinition(def.asReceiveTypeEndpointDefinition().toMustache());
-      } else if (def.isSendTypeEndpointDefinition()) {
-        result.addTypeSendDefinition(def.asSendTypeEndpointDefinition().toMustache());
-      } else {
-        throw new RuntimeException("Unknown endpoint definition: " + def);
-      }
-    }
-    for (MappingDefinition def : allMappingDefinitions()) {
-      result.addMappingDefinition(def.toMustache());
-    }
-    for (DependencyDefinition def : allDependencyDefinitionList()) {
-      result.addDependencyDefinition(def.toMustache());
-    }
-    for (TokenComponent token : getProgram().allTokenComponents()) {
-      if (token.needProxyToken()) {
-        result.addTokenComponent(token.toMustache());
-      }
-    }
-    for (Component child : rootNode.getComponentList()) {
-      if (child.isTypeComponent()) {
-        result.addRootTypeComponent(child.asTypeComponent().toMustache());
-      }
-    }
-    // MHandler ::= <ClassName> <Construction> <AttributeName> <FieldName> <InUse:boolean>;
-    result.addHandler(new MHandler("MqttServerHandler", "new MqttServerHandler(\"RagConnectMQTT\")",
-                                   result.mqttHandlerAttribute(), result.mqttHandlerField(), usesMqtt));
-    result.addHandler(new MHandler("RestServerHandler", "new RestServerHandler(\"RagConnectREST\")",
-                                   result.restHandlerAttribute(), result.restHandlerField(), usesRest));
-    return result;
-  }
-
-  protected void MEndpointDefinition.addInnerMappings() {
-    for (MappingDefinition def : endpointDef().effectiveMappings()) {
-      MInnerMappingDefinition inner = new MInnerMappingDefinition();
-      inner.setMMappingDefinition(def.toMustache());
-      addInnerMappingDefinition(inner);
-    }
-  }
-
-  public abstract MEndpointDefinition EndpointDefinition.toMustache();
-  syn lazy MTokenReceiveDefinition ReceiveTokenEndpointDefinition.toMustache() {
-    MTokenReceiveDefinition result = new MTokenReceiveDefinition();
-    result.setReceiveTokenEndpointDefinition(this);
-    result.addInnerMappings();
-    return result;
-  }
-
-  syn lazy MTokenSendDefinition SendTokenEndpointDefinition.toMustache() {
-    MTokenSendDefinition result = new MTokenSendDefinition();
-    result.setSendTokenEndpointDefinition(this);
-    result.addInnerMappings();
-    return result;
-  }
-
-  syn lazy MTypeReceiveDefinition ReceiveTypeEndpointDefinition.toMustache() {
-    MTypeReceiveDefinition result = new MTypeReceiveDefinition();
-    result.setReceiveTypeEndpointDefinition(this);
-    result.addInnerMappings();
-    return result;
-  }
-
-  syn lazy MTypeSendDefinition SendTypeEndpointDefinition.toMustache() {
-    MTypeSendDefinition result = new MTypeSendDefinition();
-    result.setSendTypeEndpointDefinition(this);
-    result.addInnerMappings();
-    return result;
-  }
-
-  syn lazy MMappingDefinition MappingDefinition.toMustache() {
-    MMappingDefinition result = new MMappingDefinition();
-    result.setMappingDefinition(this);
-    return result;
-  }
-
-  syn lazy MDependencyDefinition DependencyDefinition.toMustache() {
-    MDependencyDefinition result = new MDependencyDefinition();
-    result.setDependencyDefinition(this);
-    return result;
-  }
-
-  syn lazy MTypeComponent TypeComponent.toMustache() {
-    MTypeComponent result = new MTypeComponent();
-    result.setTypeComponent(this);
-    return result;
-  }
-
-  syn lazy MTokenComponent TokenComponent.toMustache() {
-    MTokenComponent result = new MTokenComponent();
-    result.setTokenComponent(this);
-    for (DependencyDefinition def : getDependencySourceDefinitionList()) {
-      result.addDependencyDefinition(def.toMustache());
-    }
-    return result;
-  }
-}
-
-aspect AspectGeneration {
-  // --- rootNodeName ---
-  syn String ASTNode.rootNodeName() = rootNode.getName();
-
-  public String RagConnect.generateAspect(String rootNodeName) {
-    rootNode = getProgram().resolveTypeDecl(rootNodeName);
-    return toMustache().generateAspect();
-  }
-
-  public String MRagConnect.generateAspect() {
-    StringBuilder sb = new StringBuilder();
-    com.github.mustachejava.reflect.ReflectionObjectHandler roh = new com.github.mustachejava.reflect.ReflectionObjectHandler() {
-      @Override
-      public com.github.mustachejava.Binding createBinding(String name, final com.github.mustachejava.TemplateContext tc, com.github.mustachejava.Code code) {
-        return new com.github.mustachejava.reflect.GuardedBinding(this, name, tc, code) {
-          @Override
-          protected synchronized com.github.mustachejava.util.Wrapper getWrapper(String name, java.util.List<Object> scopes) {
-            com.github.mustachejava.util.Wrapper wrapper = super.getWrapper(name, scopes);
-            if (wrapper instanceof com.github.mustachejava.reflect.MissingWrapper) {
-              throw new com.github.mustachejava.MustacheException(name + " not found in " + tc);
-            }
-            return wrapper;
-          }
-        };
-      }
-    };
-    com.github.mustachejava.DefaultMustacheFactory mf = new com.github.mustachejava.DefaultMustacheFactory();
-    mf.setObjectHandler(roh);
-    com.github.mustachejava.Mustache m = mf.compile("ragconnect.mustache");
-    m.execute(new java.io.PrintWriter(new org.jastadd.ragconnect.compiler.AppendableWriter(sb)), this);
-    return sb.toString();
-  }
-}
-
-aspect GrammarGeneration {
-  syn java.util.List<Relation> RagConnect.additionalRelations() {
-    java.util.List<Relation> result = new java.util.ArrayList<>();
-    for (DependencyDefinition dd : allDependencyDefinitionList()) {
-      result.add(dd.getRelationToCreate());
-    }
-    return result;
-  }
-
-  syn nta Relation DependencyDefinition.getRelationToCreate() {
-    String internalRelationPrefix = toMustache().internalRelationPrefix();
-    BidirectionalRelation result = new BidirectionalRelation();
-    NavigableRole left = new ListRole(internalRelationPrefix + "Source");
-    left.setType(getTarget().containingTypeDecl());
-    NavigableRole right = new ListRole(internalRelationPrefix + "Target");
-    right.setType(getSource().containingTypeDecl());
-    result.setLeft(left);
-    result.setRight(right);
-    result.addComment(new WhitespaceComment("\n"));
-    return result;
-  }
-
-//  coll java.util.Map<TypeDecl, TokenComponent> RagConnect.additionalTokens() [new java.util.HashMap<>()] with put root RagConnect;
-
-//  TypeEndpointDefinition contributes getTokenToCreate()
-//    when typeIsList() && !getUseList()
-//    to RagConnect.additionalTokens()
-////    for ragconnect()
-//    ;
-
-  syn java.util.Map<TypeDecl, TokenComponent> RagConnect.additionalTokens() {
-    java.util.Map<TypeDecl, TokenComponent> result = new java.util.HashMap<>();
-    for (EndpointDefinition def : allEndpointDefinitionList()) {
-      if (def.isTypeEndpointDefinition() && def.getTokenToCreate() != null) {
-        result.put(def.asTypeEndpointDefinition().getType().getTypeDecl(), def.getTokenToCreate());
-      }
-    }
-    return result;
-  }
-
-  syn TokenComponent EndpointDefinition.getTokenToCreate() = null;
-  eq TypeEndpointDefinition.getTokenToCreate() {
-    if (typeIsList() && !getUseList()) {
-      TokenComponent result = new TokenComponent();
-      result.setName(idTokenName());
-      result.setNTA(false);
-      result.setJavaTypeUse(new SimpleJavaTypeUse("String"));
-      return result;
-    } else {
-      return null;
-    }
-  }
-}
-
-aspect GrammarExtension {
-  refine BackendAbstractGrammar public void TokenComponent.generateAbstractGrammar(StringBuilder b) {
-    if (getNTA()) {
-      b.append("/");
-    }
-    b.append("<");
-    if (!getName().equals("")) {
-      b.append(toMustache().internalName()).append(":");
-    }
-    effectiveJavaTypeUse().generateAbstractGrammar(b);
-    b.append(">");
-    if (getNTA()) {
-      b.append("/");
-    }
-  }
-}
diff --git a/ragconnect.base/src/main/jastadd/intermediate/MustacheNodes.relast b/ragconnect.base/src/main/jastadd/intermediate/MustacheNodes.relast
deleted file mode 100644
index 8255cfe5ad9e67e00e53e95b4ddc110a480dce28..0000000000000000000000000000000000000000
--- a/ragconnect.base/src/main/jastadd/intermediate/MustacheNodes.relast
+++ /dev/null
@@ -1,28 +0,0 @@
-MRagConnect ::= TokenReceiveDefinition:MTokenReceiveDefinition* TokenSendDefinition:MTokenSendDefinition* TypeReceiveDefinition:MTypeReceiveDefinition* TypeSendDefinition:MTypeSendDefinition* MappingDefinition:MMappingDefinition* DependencyDefinition:MDependencyDefinition* RootTypeComponent:MTypeComponent* TokenComponent:MTokenComponent* Handler:MHandler*;
-
-abstract MEndpointDefinition ::= InnerMappingDefinition:MInnerMappingDefinition*;
-abstract MTokenEndpointDefinition : MEndpointDefinition;
-MTokenReceiveDefinition : MTokenEndpointDefinition;
-MTokenSendDefinition : MTokenEndpointDefinition;
-abstract MTypeEndpointDefinition : MEndpointDefinition;
-MTypeReceiveDefinition : MTypeEndpointDefinition;
-MTypeSendDefinition : MTypeEndpointDefinition;
-
-MMappingDefinition;
-MInnerMappingDefinition;
-MDependencyDefinition;
-MTypeComponent;
-MTokenComponent;
-MHandler ::= <ClassName> <Construction> <AttributeName> <FieldName> <InUse:boolean>;
-
-rel MRagConnect.RagConnect -> RagConnect;
-rel MInnerMappingDefinition.MMappingDefinition -> MMappingDefinition;
-rel MTokenReceiveDefinition.ReceiveTokenEndpointDefinition -> ReceiveTokenEndpointDefinition;
-rel MTokenSendDefinition.SendTokenEndpointDefinition -> SendTokenEndpointDefinition;
-rel MTypeReceiveDefinition.ReceiveTypeEndpointDefinition -> ReceiveTypeEndpointDefinition;
-rel MTypeSendDefinition.SendTypeEndpointDefinition -> SendTypeEndpointDefinition;
-rel MMappingDefinition.MappingDefinition -> MappingDefinition;
-rel MDependencyDefinition.DependencyDefinition -> DependencyDefinition;
-rel MTypeComponent.TypeComponent -> TypeComponent;
-rel MTokenComponent.TokenComponent -> TokenComponent;
-rel MTokenComponent.DependencyDefinition* -> MDependencyDefinition;
diff --git a/ragconnect.base/src/main/jastadd/intermediate2mustache/MustacheNodesToYAML.jrag b/ragconnect.base/src/main/jastadd/intermediate2mustache/MustacheNodesToYAML.jrag
deleted file mode 100644
index 04b9cf89c248c39740677c88835b56c5325493bf..0000000000000000000000000000000000000000
--- a/ragconnect.base/src/main/jastadd/intermediate2mustache/MustacheNodesToYAML.jrag
+++ /dev/null
@@ -1,212 +0,0 @@
-aspect MustacheNodesToYAML {
-  syn Document MRagConnect.toYAML() {
-    Document doc = new Document();
-    MappingElement root = new MappingElement();
-    root.put("rootNodeName", rootNodeName());
-    root.put("closeMethod", closeMethod());
-    root.put("usesMqtt", usesMqtt);
-    root.put("usesRest", usesRest);
-    // mqtt
-    root.put("mqttHandlerField", mqttHandlerField());
-    root.put("mqttHandlerAttribute", mqttHandlerAttribute());
-    root.put("mqttSetupWaitUntilReadyMethod", mqttSetupWaitUntilReadyMethod());
-
-    // rootTypeComponents
-    ListElement rootTypeComponents = new ListElement();
-    for (MTypeComponent comp : getRootTypeComponentList()) {
-      MappingElement inner = new MappingElement();
-      inner.put("first", comp.isFirst());
-      inner.put("name", comp.name());
-      rootTypeComponents.addElement(inner);
-    }
-    root.put("RootTypeComponents", rootTypeComponents);
-
-    // rest
-    root.put("restHandlerField", restHandlerField());
-    root.put("restHandlerAttribute", restHandlerAttribute());
-
-    // TokenReceiveDefinitions
-    ListElement receiveDefinitions = new ListElement();
-    for (MTokenReceiveDefinition def : getTokenReceiveDefinitionList()) {
-      receiveDefinitions.addElement(def.toYAML());
-    }
-    root.put("TokenReceiveDefinitions", receiveDefinitions);
-
-    // TokenSendDefinitions
-    ListElement sendDefinitions = new ListElement();
-    for (MTokenSendDefinition def : getTokenSendDefinitionList()) {
-      sendDefinitions.addElement(def.toYAML());
-    }
-    root.put("TokenSendDefinitions", sendDefinitions);
-
-    // TypeReceiveDefinitions
-    ListElement typeReceiveDefinitions = new ListElement();
-    for (MTypeReceiveDefinition def : getTypeReceiveDefinitionList()) {
-      typeReceiveDefinitions.addElement(def.toYAML());
-    }
-    root.put("TypeReceiveDefinitions", typeReceiveDefinitions);
-
-    // TypeSendDefinitions
-    ListElement typeSendDefinitions = new ListElement();
-    for (MTypeSendDefinition def : getTypeSendDefinitionList()) {
-      typeSendDefinitions.addElement(def.toYAML());
-    }
-    root.put("TypeSendDefinitions", typeSendDefinitions);
-
-    // MappingDefinitions
-    ListElement mappingDefinitions = new ListElement();
-    for (MMappingDefinition def : getMappingDefinitionList()) {
-      mappingDefinitions.addElement(def.toYAML());
-    }
-    root.put("MappingDefinitions", mappingDefinitions);
-
-    // DependencyDefinitions
-    ListElement dependencyDefinitions = new ListElement();
-    for (MDependencyDefinition def : getDependencyDefinitionList()) {
-      dependencyDefinitions.addElement(def.toYAML());
-    }
-    root.put("DependencyDefinitions", dependencyDefinitions);
-
-    // TokenComponents
-    ListElement tokenComponents = new ListElement();
-    for (MTokenComponent comp : getTokenComponentList()) {
-      tokenComponents.addElement(comp.toYAML());
-    }
-    root.put("TokenComponents", tokenComponents);
-
-    // Handlers
-    ListElement handlers = new ListElement();
-    for (MHandler handler : getHandlerList()) {
-      handlers.add(handler.toYAML()
-                          .put("rootTokenComponents", rootTypeComponents.treeCopy()) );
-    }
-    root.put("Handlers", handlers);
-
-    doc.setRootElement(root);
-    return doc;
-  }
-
-  syn MappingElement MEndpointDefinition.toYAML() {
-    MappingElement result = new MappingElement();
-    result.put("parentTypeName", parentTypeName());
-    result.put("connectMethod", connectMethod());
-    result.put("connectParameterName", connectParameterName());
-    result.put("lastDefinitionToType", lastDefinitionToType());
-    result.put("preemptiveReturn", preemptiveReturn());
-    result.put("alwaysApply", alwaysApply());
-    result.put("condition",
-    condition().replace("\"", "\\\"").replace("\n", "\\n"));
-    result.put("lastResult", lastResult());
-    result.put("tokenName", tokenName());
-    result.put("InnerMappingDefinitions", innerMappingDefinitionsAsListElement());
-    return result;
-  }
-
-  syn MappingElement MTokenReceiveDefinition.toYAML() {
-    MappingElement result = super.toYAML();
-    result.put("loggingEnabledForReads", loggingEnabledForReads);
-    return result;
-  }
-
-  syn MappingElement MTokenSendDefinition.toYAML() {
-    MappingElement result = super.toYAML();
-    result.put("sender", sender());
-    result.put("lastValue", lastValue());
-    result.put("loggingEnabledForWrites", loggingEnabledForWrites);
-    result.put("updateMethod", updateMethod());
-    result.put("writeMethod", writeMethod());
-    result.put("tokenResetMethod", tokenResetMethod());
-    return result;
-  }
-
-  syn MappingElement MTypeReceiveDefinition.toYAML() {
-    MappingElement result = super.toYAML();
-    result.put("typeIsList", typeIsList());
-    result.put("loggingEnabledForReads", loggingEnabledForReads);
-    return result;
-  }
-
-  syn MappingElement MTypeSendDefinition.toYAML() {
-    MappingElement result = super.toYAML();
-    result.put("typeIsList", typeIsList());
-    result.put("sender", sender());
-    result.put("lastValue", lastValue());
-    result.put("loggingEnabledForWrites", loggingEnabledForWrites);
-    result.put("updateMethod", updateMethod());
-    result.put("writeMethod", writeMethod());
-    result.put("tokenResetMethod", tokenResetMethod());
-    return result;
-  }
-
-  syn Element MMappingDefinition.toYAML() {
-    MappingElement result = new MappingElement();
-    result.put("toType", toType());
-    result.put("methodName", methodName());
-    result.put("fromType", fromType());
-    result.put("fromVariableName", fromVariableName());
-    result.put("content",
-        content().replace("\"", "\\\"").replace("\n", "\\n"));
-    return result;
-  }
-
-  syn Element MDependencyDefinition.toYAML() {
-    MappingElement result = new MappingElement();
-    result.put("targetParentTypeName", targetParentTypeName());
-    result.put("dependencyMethod", dependencyMethod());
-    result.put("sourceParentTypeName", sourceParentTypeName());
-    result.put("internalRelationPrefix", internalRelationPrefix());
-    return result;
-  }
-
-  syn Element MTokenComponent.toYAML() {
-    MappingElement result = new MappingElement();
-    result.put("parentTypeName", parentTypeName());
-    result.put("name", name());
-    result.put("javaType", javaType());
-    result.put("internalName", internalName());
-    ListElement dependencyDefinitions = new ListElement();
-    for (MDependencyDefinition def : getDependencyDefinitionList()) {
-      MappingElement inner = new MappingElement();
-      inner.put("targetParentTypeName", def.targetParentTypeName());
-      inner.put("internalRelationPrefix", def.internalRelationPrefix());
-      MappingElement targetEndpointDefinition = new MappingElement();
-    targetEndpointDefinition.put("updateMethod", def.targetEndpointDefinition().updateMethod());
-    targetEndpointDefinition.put("writeMethod", def.targetEndpointDefinition().writeMethod());
-      inner.put("targetEndpointDefinition", targetEndpointDefinition);
-      dependencyDefinitions.addElement(inner);
-    }
-    result.put("DependencyDefinitions", dependencyDefinitions);
-    return result;
-  }
-
-  ListElement MEndpointDefinition.innerMappingDefinitionsAsListElement() {
-    ListElement innerMappingDefinitions = new ListElement();
-    for (MInnerMappingDefinition def : getInnerMappingDefinitionList()) {
-      MappingElement inner = new MappingElement();
-      inner.put("toType", def.toType());
-      inner.put("methodName", def.methodName());
-      inner.put("inputVarName", def.inputVarName());
-      inner.put("outputVarName", def.outputVarName());
-      inner.put("last", def.isLast());
-      innerMappingDefinitions.addElement(inner);
-    }
-    return innerMappingDefinitions;
-  }
-
-  syn MappingElement MHandler.toYAML() {
-    MappingElement result = new MappingElement();
-    result.put("ClassName", getClassName());
-    result.put("Construction", getConstruction());
-    result.put("AttributeName", getAttributeName());
-    result.put("FieldName", getFieldName());
-    result.put("InUse", getInUse());
-    return result;
-  }
-}
-
-aspect Navigation {
-  eq Document.getChild().program() = null;
-  eq Document.getChild().ragconnect() = null;
-  eq Document.getChild().containedFile() = null;
-  eq Document.containedFileName() = getFileName();
-}
diff --git a/ragconnect.base/src/main/jastadd/parser/ParserRewrites.jrag b/ragconnect.base/src/main/jastadd/parser/ParserRewrites.jrag
new file mode 100644
index 0000000000000000000000000000000000000000..364161d52e019d3d72393d277a8d4f71a25d19b5
--- /dev/null
+++ b/ragconnect.base/src/main/jastadd/parser/ParserRewrites.jrag
@@ -0,0 +1,64 @@
+aspect ParserRewrites {
+  rewrite UntypedPortTarget {
+    when (getChildName() != null && tryGloballyResolveTypeComponentByToken(combinedName()) != null)
+    to TypePortTarget {
+      TypePortTarget result = new TypePortTarget();
+      result.copyOtherValuesFrom(this);
+      result.setType(TypeComponent.createRef(this.combinedName()));
+      return result;
+    }
+
+    when (getChildName() != null && tryGloballyResolveTokenComponentByToken(combinedName()) != null)
+    to TokenPortTarget {
+      TokenPortTarget result = new TokenPortTarget();
+      result.copyOtherValuesFrom(this);
+      result.setToken(TokenComponent.createRef(this.combinedName()));
+      return result;
+    }
+
+    when (getChildName() != null && tryGloballyResolveNavigableRoleByToken(combinedName()) != null)
+    to RelationPortTarget {
+      RelationPortTarget result = new RelationPortTarget();
+      result.copyOtherValuesFrom(this);
+      result.setRole(NavigableRole.createRef(this.combinedName()));
+      return result;
+    }
+
+    when (getChildName() == "")
+    to ContextFreeTypePortTarget {
+      ContextFreeTypePortTarget result = new ContextFreeTypePortTarget();
+      result.copyOtherValuesFrom(this);
+      result.setTypeDecl(TypeDecl.createRef(getTypeName()));
+      return result;
+    }
+
+    when (getIsAttribute())
+    to AttributePortTarget {
+      AttributePortTarget result = new AttributePortTarget();
+      String[] tokens = this.getChildName().split(":");
+      String attributeName = tokens[0];
+      String attributeTypeName = tokens[1];
+      result.copyOtherValuesFrom(this);
+      result.setName(attributeName);
+      result.setTypeName(attributeTypeName);
+      result.setParentTypeDecl(TypeDecl.createRef(getTypeName()));
+      return result;
+    }
+  }
+
+  syn String UntypedPortTarget.combinedName() = getTypeName() + "." + getChildName();
+
+  protected void PortTarget.copyOtherValuesFrom(PortTarget source) {
+    this.setStart(source.getStartLine(), source.getStartColumn());
+    this.setEnd(source.getEndLine(), source.getEndColumn());
+  }
+
+  eq UntypedPortTarget.senderName() = "<untyped.senderName>";
+  eq UntypedPortTarget.getterMethodName() = "<untyped.getterMethodName>";
+  eq UntypedPortTarget.parentTypeName() = "<untyped.parentTypeName>";
+  eq UntypedPortTarget.entityName() = "<untyped.entityName>";
+  eq UntypedPortTarget.isAlreadyDefined() = false;
+  eq UntypedPortTarget.hasAttributeResetMethod() = false;
+  eq UntypedPortTarget.targetTypeName() = "<untyped.targetTypeName>";
+  eq UntypedPortTarget.isTypePortTarget() = false;
+}
diff --git a/ragconnect.base/src/main/jastadd/parser/RagConnect.parser b/ragconnect.base/src/main/jastadd/parser/RagConnect.parser
index a7ba289535a01938c1284ad67f46b2a9e81b9fb5..f20588bb5d6cf82fe09f6a525dab543d53340f0d 100644
--- a/ragconnect.base/src/main/jastadd/parser/RagConnect.parser
+++ b/ragconnect.base/src/main/jastadd/parser/RagConnect.parser
@@ -1,7 +1,7 @@
 ConnectSpecificationFile connect_specification_file
-  = endpoint_definition.d connect_specification_file.r
+  = port_definition.d connect_specification_file.r
     {:
-      r.getEndpointDefinitionList().insertChild(d, 0); return r;
+      r.getPortDefinitionList().insertChild(d, 0); return r;
     :}
   | dependency_definition.d connect_specification_file.r
     {:
@@ -22,68 +22,50 @@ ConnectSpecificationFile connect_specification_file
   private Iterable<String> makeMappingDefs(ArrayList<?> raw_mapping_defs) {
     return () -> raw_mapping_defs.stream().map(raw -> ((Symbol) raw).value.toString()).iterator();
   }
-  private TokenEndpointDefinition enableAlwaysApply(TokenEndpointDefinition def) {
-    def.setAlwaysApply(true);
-    return def;
-  }
+//  private TokenPortDefinition enableAlwaysApply(TokenPortDefinition def) {
+//    def.setAlwaysApply(true);
+//    return def;
+//  }
+  private PortDefinition createPortDefinition(
+    PortTarget portTarget, boolean send,
+    boolean indexBasedListAccess, boolean withAdd) {
+      PortDefinition result = new PortDefinition();
+      result.setSend(send);
+      result.setIndexBasedListAccess(indexBasedListAccess);
+      result.setWithAdd(withAdd);
+      result.setPortTarget(portTarget);
+      return result;
+    }
 :} ;
 
-EndpointDefinition endpoint_definition
-  = endpoint_definition_type.endpointDef SCOL
+PortDefinition port_definition
+  = port_definition_type.portDef SCOL
     {:
-      return endpointDef;
+      return portDef;
     :}
-  | endpoint_definition_type.endpointDef USING string_list.mapping_defs SCOL
+  | port_definition_type.portDef USING string_list.mapping_defs SCOL
     {:
       for (String mapping_def : makeMappingDefs(mapping_defs)) {
-        endpointDef.addMapping(MappingDefinition.createRef(mapping_def));
+        portDef.addMapping(MappingDefinition.createRef(mapping_def));
       }
-      return endpointDef;
-    :}
-;
-
-EndpointDefinition endpoint_definition_type
-  = RECEIVE token_ref           {: return new ReceiveTokenEndpointDefinition().setToken(token_ref); :}
-  | SEND token_ref              {: return new SendTokenEndpointDefinition().setToken(token_ref); :}
-  | RECEIVE TREE type_ref       {: return new ReceiveTypeEndpointDefinition().setType(type_ref); :}
-  | RECEIVE TREE WITH ADD type_ref
-    {:
-      ReceiveTypeEndpointDefinition result = new ReceiveTypeEndpointDefinition();
-      result.setType(type_ref);
-      result.setWithAdd(true);
-      return result;
-    :}
-  | SEND TREE type_ref          {: return new SendTypeEndpointDefinition().setType(type_ref); :}
-  | RECEIVE LIST type_ref
-    {:
-      ReceiveTypeEndpointDefinition result = new ReceiveTypeEndpointDefinition();
-      result.setType(type_ref);
-      result.setUseList(true);
-      return result;
-    :}
-  | RECEIVE LIST WITH ADD type_ref
-    {:
-      ReceiveTypeEndpointDefinition result = new ReceiveTypeEndpointDefinition();
-      result.setType(type_ref);
-      result.setWithAdd(true);
-      result.setUseList(true);
-      return result;
-    :}
-  | SEND LIST type_ref
-    {:
-      SendTypeEndpointDefinition result = new SendTypeEndpointDefinition();
-      result.setType(type_ref);
-      result.setUseList(true);
-      return result;
+      return portDef;
     :}
 ;
 
-TokenComponent token_ref
-  = ID.type_name DOT ID.token_name  {: return TokenComponent.createRef(type_name + "." + token_name); :}
+PortDefinition port_definition_type
+  = SEND port_target.t                        {: return createPortDefinition(t, true,  false, false); :}
+  | SEND INDEXED port_target.t                {: return createPortDefinition(t, true,  true,  false); :}
+  | RECEIVE port_target.t                     {: return createPortDefinition(t, false, false, false); :}
+  | RECEIVE INDEXED port_target.t             {: return createPortDefinition(t, false, true,  false); :}
+  | RECEIVE WITH ADD port_target.t            {: return createPortDefinition(t, false, false, true ); :}
+  | RECEIVE INDEXED WITH ADD port_target.t    {: return createPortDefinition(t, false, true,  true ); :}
 ;
 
-TypeComponent type_ref
-  = ID.parent_type_name DOT ID.child_type_name  {: return TypeComponent.createRef(parent_type_name + "." + child_type_name); :}
+PortTarget port_target
+  = ID.type_name DOT ID.child_name    {: return new UntypedPortTarget(type_name, child_name, false); :}
+  | ID.type_name DOT ID.child_name BRACKET_LEFT java_type_use.attribute_type_name BRACKET_RIGHT
+     {: return new UntypedPortTarget(type_name, child_name + ":" + attribute_type_name.prettyPrint(), true); :}
+  | ID.type_name                      {: return new UntypedPortTarget(type_name, "", false); :}
 ;
 
 ArrayList string_list
diff --git a/ragconnect.base/src/main/jastadd/scanner/Keywords.flex b/ragconnect.base/src/main/jastadd/scanner/Keywords.flex
index 69830108bfb0644cd75008b47b58ee149dc3b2ad..cda59f5c346fa12f5b83723ed82a0c8849d639b8 100644
--- a/ragconnect.base/src/main/jastadd/scanner/Keywords.flex
+++ b/ragconnect.base/src/main/jastadd/scanner/Keywords.flex
@@ -5,7 +5,8 @@
 "maps"       { return sym(Terminals.MAPS); }
 "to"         { return sym(Terminals.TO); }
 "as"         { return sym(Terminals.AS); }
-"tree"       { return sym(Terminals.TREE); }
-"list"       { return sym(Terminals.LIST); }
 "with"       { return sym(Terminals.WITH); }
+"indexed"    { return sym(Terminals.INDEXED); }
 "add"        { return sym(Terminals.ADD); }
+"("          { return sym(Terminals.BRACKET_LEFT); }
+")"          { return sym(Terminals.BRACKET_RIGHT); }
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 915d9f92d49cf3a847f05d0b23393bce1832fc1a..be81bfd4ea84123624e97c0047b85e7b29bda348 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
@@ -9,18 +9,19 @@ import org.jastadd.ragconnect.scanner.RagConnectScanner;
 import org.jastadd.relast.compiler.AbstractCompiler;
 import org.jastadd.relast.compiler.CompilerException;
 
-import java.io.*;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.IOException;
 import java.nio.file.Files;
 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;
+import java.util.Collection;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
 
 public class Compiler extends AbstractCompiler {
 
-//  private ValueOption optionOutputDir;
   private ValueOption optionRootNode;
   private ValueOption optionProtocols;
   private BooleanOption optionPrintYaml;
@@ -28,32 +29,42 @@ public class Compiler extends AbstractCompiler {
   private BooleanOption optionLogReads;
   private BooleanOption optionLogWrites;
   private BooleanOption optionLogIncremental;
+  private ValueOption optionLogTarget;
   private BooleanOption optionExperimentalJastAdd329;
+  private BooleanOption optionEvaluationCounter;
 
+  private static final String OPTION_LOGGING_TARGET_CONSOLE = "console";
+  private static final String OPTION_LOGGING_TARGET_SLF4J = "slf4j";
+
+  private static final String OPTION_PROTOCOL_JAVA = "java";
   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);
   }
 
   @Override
   protected int compile() throws CompilerException {
+    compile0();
+    return 0;
+  }
+
+  /**
+   * Compiles with given options. Either successful, or throws exception upon failure.
+   * @throws CompilerException if something went wrong
+   */
+  private void compile0() throws CompilerException {
     if (getConfiguration().shouldPrintVersion()) {
       System.out.println(readVersion());
-      return 0;
+      return;
     }
     if (getConfiguration().shouldPrintHelp()) {
       getConfiguration().printHelp(System.out);
-      return 0;
-    }
-    if (optionVerbose.value()) {
-      LOGGER.setLevel(Level.FINE);
+      return;
     }
 
-    LOGGER.info(() -> "Running RagConnect " + readVersion());
+    System.out.println("Running RagConnect " + readVersion());
 
     if (!getConfiguration().outputDir().exists()) {
       try {
@@ -64,82 +75,88 @@ public class Compiler extends AbstractCompiler {
     }
 
     if (!optionRootNode.isMatched()) {
-      return error("Root node not specified");
+      throw new CompilerException("Root node not specified");
     }
 
     RagConnect ragConnect = parseProgram(getConfiguration().getFiles());
+    try {
+      setConfiguration(ragConnect);
+    } catch (RuntimeException re) {
+      throw new CompilerException("Failed to parse all files", re);
+    }
+
+    if (!ragConnect.warnings().isEmpty()) {
+      StringBuilder sb = new StringBuilder("Warnings:\n");
+      for (CompilerMessage message : ragConnect.warnings()) {
+        sb.append(message).append("\n");
+      }
+      System.err.println(sb);
+    }
 
     if (!ragConnect.errors().isEmpty()) {
       StringBuilder sb = new StringBuilder("Errors:\n");
-      for (ErrorMessage e : ragConnect.errors()) {
-        sb.append(e).append("\n");
+      for (CompilerMessage message : ragConnect.errors()) {
+        sb.append(message).append("\n");
       }
-      LOGGER.severe(sb::toString);
+      System.err.println(sb);
       System.exit(1);
     }
 
     if (optionPrintYaml.value()) {
-      ASTNode.rootNode = ragConnect.getProgram().resolveTypeDecl(optionRootNode.value());
-      String yamlContent = ragConnect.toMustache().toYAML().prettyPrint();
-      System.out.println(yamlContent);
+      String yamlContent = ragConnect.toYAML().prettyPrint();
+      if (isVerbose()) {
+        System.out.println(yamlContent);
+      }
       writeToFile(getConfiguration().outputDir().toPath().resolve("RagConnect.yml"), yamlContent);
-      return 0;
+      return;
     }
 
-    LOGGER.fine("Writing output files");
-    final List<String> handlers = new ArrayList<>();
-    if (ASTNode.usesMqtt) {
-      handlers.add("MqttHandler.jadd");
-    }
-    if (ASTNode.usesRest) {
-      handlers.add("RestHandler.jadd");
-    }
-    // copy handlers into outputDir
-    for (String handlerFileName : handlers) {
-      try {
-        InputStream inputStream = Compiler.class.getClassLoader().getResourceAsStream(handlerFileName);
-        if (inputStream == null) {
-          throw new CompilerException("Could not open " + handlerFileName);
-        }
-        Files.copy(inputStream,
-            getConfiguration().outputDir().toPath().resolve(handlerFileName),
-            StandardCopyOption.REPLACE_EXISTING);
-      } catch (IOException e) {
-        throw new CompilerException("Could not copy " + handlerFileName, e);
-      }
+    if (isVerbose()) {
+      System.out.println("Writing output files");
     }
     for (GrammarFile grammarFile : ragConnect.getProgram().getGrammarFileList()) {
       Path outputFile = getConfiguration().outputDir().toPath().resolve(grammarFile.getFileName());
       writeToFile(outputFile, grammarFile.generateAbstractGrammar());
     }
-    writeToFile(getConfiguration().outputDir().toPath().resolve("RagConnect.jadd"), ragConnect.generateAspect(optionRootNode.value()));
-    return 0;
+    String aspectCode;
+    try {
+      aspectCode = generateAspect(ragConnect);
+    } catch (RuntimeException re) {
+      throw new CompilerException("Could not generate RagConnect aspect", re);
+    }
+    writeToFile(getConfiguration().outputDir().toPath().resolve("RagConnect.jadd"), aspectCode);
   }
 
   public static void main(String[] args) {
     System.setProperty("mustache.debug", "true");
+    Compiler compiler = new Compiler();
     try {
-      new Compiler().run(args);
+      compiler.run(args);
     } catch (CompilerException e) {
-      LOGGER.log(Level.SEVERE, e.getMessage(), e);
+      System.err.println(e.getMessage());
+      e.printStackTrace();
       System.exit(1);
     }
   }
 
+  private boolean isVerbose() {
+    return optionVerbose != null && optionVerbose.value();
+  }
+
   /**
    * Reads the version string.
-   *
+   * <p>
    * The version string is read from the property file
    * src/main/resources/Version.properties. This
    * file should be generated during the build process. If it is missing
    * then there is some problem in the build script.
    *
-   * @author Jesper Öqvist <jesper.oqvist@cs.lth.se>
    * @return the read version string, or <code>version ?</code>
+   * @author Jesper Öqvist <jesper.oqvist@cs.lth.se>
    */
   private String readVersion() {
     try {
-      ResourceBundle resources = ResourceBundle.getBundle("ragConnectVersion");
+      ResourceBundle resources = ResourceBundle.getBundle("ragconnectVersion");
       return resources.getString("version");
     } catch (MissingResourceException e) {
       return "version ?";
@@ -158,32 +175,41 @@ public class Compiler extends AbstractCompiler {
     super.initOptions();
     optionRootNode = addOption(
         new ValueOption("rootNode", "root node in the base grammar.")
-        .acceptAnyValue()
-        .needsValue(true));
+            .acceptAnyValue()
+            .needsValue(true));
     optionProtocols = addOption(
         new ValueOption("protocols", "Protocols to enable")
-        .acceptMultipleValues(true)
-        .addDefaultValue(OPTION_PROTOCOL_MQTT, "Enable MQTT")
-        .addAcceptedValue(OPTION_PROTOCOL_REST, "Enable REST")
+            .acceptMultipleValues(true)
+            .addDefaultValue(OPTION_PROTOCOL_MQTT, "Enable MQTT")
+            .addAcceptedValue(OPTION_PROTOCOL_JAVA, "Enable Java (experimental)")
+            .addAcceptedValue(OPTION_PROTOCOL_REST, "Enable REST")
     );
     optionPrintYaml = addOption(
-        new BooleanOption("printYaml", "Print out YAML instead of generating files")
-        .defaultValue(false));
+        new BooleanOption("printYaml", "Print out YAML instead of generating files and exit.")
+            .defaultValue(false));
     optionVerbose = addOption(
         new BooleanOption("verbose", "Print more messages while compiling.")
-        .defaultValue(false));
+            .defaultValue(false));
     optionLogReads = addOption(
         new BooleanOption("logReads", "Enable logging for every read.")
-        .defaultValue(false));
+            .defaultValue(false));
     optionLogWrites = addOption(
         new BooleanOption("logWrites", "Enable logging for every write.")
-        .defaultValue(false));
+            .defaultValue(false));
     optionLogIncremental = addOption(
         new BooleanOption("logIncremental", "Enable logging for observer in incremental dependency tracking.")
             .defaultValue(false));
+    optionLogTarget = addOption(
+            new ValueOption("logTarget", "Logging target to use")
+                    .addDefaultValue(OPTION_LOGGING_TARGET_CONSOLE, "Use std out and std err")
+                    .addAcceptedValue(OPTION_LOGGING_TARGET_SLF4J, "Use SLF4J API")
+    );
     optionExperimentalJastAdd329 = addOption(
         new BooleanOption("experimental-jastadd-329", "Use trace events INC_FLUSH_START and INC_FLUSH_END (JastAdd issue #329).")
             .defaultValue(false));
+    optionEvaluationCounter = addOption(
+            new BooleanOption("evaluationCounter", "Enable counters for evaluation.")
+                    .defaultValue(false));
   }
 
   private RagConnect parseProgram(Collection<String> files) throws CompilerException {
@@ -203,13 +229,13 @@ public class Compiler extends AbstractCompiler {
       switch (extension) {
         case "ast":
         case "relast":
-          // processGrammar
+          // process grammar
           program.addGrammarFile(parseGrammar(filename));
           atLeastOneGrammar = true;
           break;
         case "connect":
         case "ragconnect":
-          // process ragConnect
+          // process RagConnect specification
           ragConnect.addConnectSpecificationFile(parseConnectSpec(filename));
           atLeastOneRagConnect = true;
           break;
@@ -217,13 +243,13 @@ public class Compiler extends AbstractCompiler {
           throw new CompilerException("Unknown file extension " + extension + " in " + filename);
       }
     }
-    if (!atLeastOneGrammar) {
-      LOGGER.severe(() -> "No grammar file specified! (*.ast, *.relast)");
-    }
+
     if (!atLeastOneRagConnect) {
-      LOGGER.severe(() -> "No ragconnect file specified! (*.connect, *.ragconnect)");
+      System.err.println("No RagConnect specification file (*.connect, *.ragconnect) specified!");
     }
-    if (!atLeastOneGrammar && !atLeastOneRagConnect) {
+    if (!atLeastOneGrammar) {
+      // without a grammar, RagConnect can not operate
+      System.err.println("No grammar file (*.ast, *.relast) specified! Exiting!");
       System.exit(1);
     }
 
@@ -231,22 +257,10 @@ public class Compiler extends AbstractCompiler {
     ragConnect.flushTreeCache();
     ragConnect.treeResolveAll();
 
+    // add new parts to production rule, and new relations
     ragConnect.additionalRelations().forEach(ragConnectGrammarPart::addDeclaration);
     ragConnect.additionalTokens().forEach(TypeDecl::addComponent);
-    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();
-    LOGGER.fine(() -> "ASTNode.incrementalOptionActive = " + ASTNode.incrementalOptionActive);
 
-    // reuse "--List" option of JastAdd
-    ASTNode.JastAddList = getConfiguration().listType();
-
-    ASTNode.usesMqtt = optionProtocols.hasValue(OPTION_PROTOCOL_MQTT);
-    ASTNode.usesRest = optionProtocols.hasValue(OPTION_PROTOCOL_REST);
     return ragConnect;
   }
 
@@ -255,8 +269,8 @@ public class Compiler extends AbstractCompiler {
       RagConnectScanner scanner = new RagConnectScanner(reader);
       RagConnectParser parser = new RagConnectParser();
       GrammarFile grammarFile = (GrammarFile) parser.parse(scanner);
-      if (optionVerbose.value()) {
-        LOGGER.fine(grammarFile::dumpTree);
+      if (isVerbose()) {
+        System.out.println(grammarFile.dumpTree());
       }
       grammarFile.setFileName(toBaseName(filename));
       return grammarFile;
@@ -279,6 +293,7 @@ public class Compiler extends AbstractCompiler {
 
   /**
    * Extracts the basename of the given file, with file extension
+   *
    * @param filename the given filename
    * @return the basename
    */
@@ -286,9 +301,79 @@ public class Compiler extends AbstractCompiler {
     return new File(filename).getName();
   }
 
-//  protected void printUsage() {
-//    System.out.println("Usage: java -jar ragconnect.jar [--option1] [--option2=value] ...  <filename1> <filename2> ... ");
-//    System.out.println("Options:");
-//    System.out.print(commandLine.printOptionHelp());
-//  }
+  /**
+   * Set all configuration values.
+   * @param ragConnect the RagConnect instance to set configuration values
+   */
+  private void setConfiguration(RagConnect ragConnect) throws CompilerException {
+    ragConnect.setConfiguration(new Configuration());
+    ragConnect.getConfiguration().setLoggingEnabledForReads(optionLogReads.value());
+    ragConnect.getConfiguration().setLoggingEnabledForWrites(optionLogWrites.value());
+    ragConnect.getConfiguration().setLoggingEnabledForIncremental(optionLogIncremental.value());
+    ragConnect.getConfiguration().setLoggingTarget(optionLogTarget.value());
+    ragConnect.getConfiguration().setExperimentalJastAdd329(optionExperimentalJastAdd329.value());
+    ragConnect.getConfiguration().setEvaluationCounter(optionEvaluationCounter.value());
+
+    // reuse "--incremental" and "--tracing=flush" options of JastAdd
+    boolean incrementalOptionActive = this.getConfiguration().incremental() && this.getConfiguration().traceFlush();
+    ragConnect.getConfiguration().setIncrementalOptionActive(incrementalOptionActive);
+    if (isVerbose()) {
+      System.out.println("ragConnect.getConfiguration().IncrementalOptionActive = " + incrementalOptionActive);
+    }
+
+    // reuse "--cache=all" option of JastAdd
+    ragConnect.getConfiguration().setCacheAllOptionActive(this.getConfiguration().cacheAll());
+
+    // reuse "--List" and "--Opt" options of JastAdd
+    ragConnect.getConfiguration().setJastAddList(this.getConfiguration().listType());
+    ragConnect.getConfiguration().setJastAddOpt(this.getConfiguration().optType());
+
+    final TypeDecl rootNode;
+    try {
+      rootNode = ragConnect.getProgram().resolveTypeDecl(optionRootNode.value());
+    } catch (RuntimeException re) {
+      // root node was not found
+      throw new CompilerException("Could not resolve root node '" + optionRootNode.value() + "'!", re);
+    }
+    ragConnect.getConfiguration().setRootNode(rootNode);
+
+    // Handler ::= <ClassName> <UniqueName> <InUse:boolean>;
+    ragConnect.addHandler(new Handler("JavaHandler", "java", optionProtocols.hasValue(OPTION_PROTOCOL_JAVA)));
+    ragConnect.addHandler(new Handler("MqttServerHandler", "mqtt", optionProtocols.hasValue(OPTION_PROTOCOL_MQTT)));
+    ragConnect.addHandler(new Handler("RestServerHandler", "rest", optionProtocols.hasValue(OPTION_PROTOCOL_REST)));
+  }
+
+  public String generateAspect(RagConnect ragConnect) {
+    StringBuilder sb = new StringBuilder();
+    // add handler to get error message when template expansion did not find some part
+    com.github.mustachejava.reflect.ReflectionObjectHandler roh = new com.github.mustachejava.reflect.ReflectionObjectHandler() {
+      @Override
+      public com.github.mustachejava.Binding createBinding(String name, final com.github.mustachejava.TemplateContext tc, com.github.mustachejava.Code code) {
+        return new com.github.mustachejava.reflect.GuardedBinding(this, name, tc, code) {
+          @Override
+          protected synchronized com.github.mustachejava.util.Wrapper getWrapper(String name, java.util.List<Object> scopes) {
+            com.github.mustachejava.util.Wrapper wrapper = super.getWrapper(name, scopes);
+            if (wrapper instanceof com.github.mustachejava.reflect.MissingWrapper) {
+              throw new com.github.mustachejava.MustacheException(name + " not found in " + tc);
+            }
+            return wrapper;
+          }
+        };
+      }
+    };
+    com.github.mustachejava.DefaultMustacheFactory mf = new com.github.mustachejava.DefaultMustacheFactory();
+    mf.setObjectHandler(roh);
+    com.github.mustachejava.Mustache m = mf.compile("ragconnect.mustache");
+//    String yaml = ragConnect.toYAML().prettyPrint();
+//    Object context = new org.yaml.snakeyaml.Yaml().load(new StringReader(yaml));
+    m.execute(new java.io.PrintWriter(new org.jastadd.ragconnect.compiler.AppendableWriter(sb)), ragConnect);
+    return sb.toString();
+  }
+
+  @Override
+  protected int error(String message) {
+    System.err.println(message);
+    return 1;
+  }
+
 }
diff --git a/ragconnect.base/src/main/resources/EvaluationCounter.mustache b/ragconnect.base/src/main/resources/EvaluationCounter.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..80563d2b00f3b31f86462bf9b8d037011cc8ac7c
--- /dev/null
+++ b/ragconnect.base/src/main/resources/EvaluationCounter.mustache
@@ -0,0 +1,105 @@
+aspect EvaluationCounter {
+  public String ASTNode.{{evaluationCounterSummaryMethodName}}() {
+  {{#configEvaluationCounter}}
+    return {{evaluationCounterVariable}}.summary();
+  {{/configEvaluationCounter}}
+  {{^configEvaluationCounter}}
+    String message = "Option --evaluationCounter was not set. No Summary available";
+    {{logWarn}}(message);
+    return message;
+  {{/configEvaluationCounter}}
+  }
+{{#configEvaluationCounter}}
+  static EvaluationCounter ASTNode.{{evaluationCounterVariable}} = new EvaluationCounter();
+  public void {{rootNodeName}}.ragconnectResetEvaluationCounter() {
+  {{#configEvaluationCounter}}
+    {{evaluationCounterVariable}}.reset();
+  {{/configEvaluationCounter}}
+  {{^configEvaluationCounter}}
+    {{logWarn}}("Option --evaluationCounter was not set. Nothing to reset!");
+  {{/configEvaluationCounter}}
+  }
+
+  public class EvaluationCounter {
+    private java.util.Map<String, java.util.Map<String, {{evaluationCounterInnerClass}}>> counters = new java.util.HashMap<>();
+    private final java.util.function.Function<? super String, ? extends java.util.Map<String, {{evaluationCounterInnerClass}}>> parentAbsent = key -> {
+      return new java.util.HashMap<>();
+    };
+    private final java.util.function.Function<? super String, ? extends {{evaluationCounterInnerClass}}> entityAbsent = key -> {
+      return new {{evaluationCounterInnerClass}}();
+    };
+
+    public void incrementReceive(String parentTypeName, String entityName) {
+      getCounter(parentTypeName, entityName).receive += 1;
+    }
+
+    public void incrementSend(String parentTypeName, String entityName) {
+      getCounter(parentTypeName, entityName).send += 1;
+    }
+
+    public void incrementCall(String parentTypeName, String entityName) {
+      getCounter(parentTypeName, entityName).call += 1;
+    }
+
+    public void incrementFirstNull(String parentTypeName, String entityName) {
+      getCounter(parentTypeName, entityName).firstNull += 1;
+    }
+
+    public void incrementSkip(String parentTypeName, String entityName) {
+      getCounter(parentTypeName, entityName).skip += 1;
+    }
+
+    public void incrementException(String parentTypeName, String entityName) {
+      getCounter(parentTypeName, entityName).exception += 1;
+    }
+
+    public void incrementReject(String parentTypeName, String entityName) {
+      getCounter(parentTypeName, entityName).reject += 1;
+    }
+
+    public String summary() {
+      StringBuilder sb = new StringBuilder();
+      // header
+      sb.append("parentTypeName,entityName,receive,send,call,firstNull,skip,exception,reject").append("\n");
+      // values
+      java.util.Set<String> sortedParentTypes = new java.util.TreeSet<>(counters.keySet());
+      for (String parentType : sortedParentTypes) {
+        java.util.Set<String> sortedEntityNames = new java.util.TreeSet<>(counters.get(parentType).keySet());
+        for (String entityName : sortedEntityNames) {
+          {{evaluationCounterInnerClass}} count = getCounter(parentType, entityName);
+          java.util.StringJoiner sj = new java.util.StringJoiner(",", "", "\n");
+          sj.add(parentType)
+             .add(entityName)
+             .add(Integer.toString(count.receive))
+             .add(Integer.toString(count.send))
+             .add(Integer.toString(count.call))
+             .add(Integer.toString(count.firstNull))
+             .add(Integer.toString(count.skip))
+             .add(Integer.toString(count.exception))
+             .add(Integer.toString(count.reject))
+          ;
+          sb.append(sj);
+        }
+      }
+      return sb.toString();
+    }
+
+    private {{evaluationCounterInnerClass}} getCounter(String parentTypeName, String entityName) {
+      return counters.computeIfAbsent(parentTypeName, parentAbsent).computeIfAbsent(entityName, entityAbsent);
+    }
+    public void reset() {
+      counters = new java.util.HashMap<>();
+    }
+  }
+
+  class {{evaluationCounterInnerClass}} {
+    int receive = 0;
+    int send = 0;
+    int call = 0;
+    int firstNull = 0;
+    int skip = 0;
+    int exception = 0;
+    int reject = 0;
+  }
+{{/configEvaluationCounter}}
+}
diff --git a/ragconnect.base/src/main/resources/JavaHandler.mustache b/ragconnect.base/src/main/resources/JavaHandler.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..d8d907b2969b114517eaf9944428a6cefd9842fc
--- /dev/null
+++ b/ragconnect.base/src/main/resources/JavaHandler.mustache
@@ -0,0 +1,82 @@
+/**
+ * Singleton class providing routing functionality for byte[] based message calls.
+ */
+public class JavaHandler {
+  public static JavaHandler JAVA_HANDLER_INSTANCE = null;
+  private java.util.Map<String, java.util.List<java.util.function.BiConsumer<String, byte[]>>> callbackList = new java.util.concurrent.ConcurrentHashMap<>();
+  private final java.util.Map<RagConnectToken, java.util.function.BiConsumer<String, byte[]>> tokensForRemoval = new java.util.HashMap<>();
+  private String name;
+
+  private JavaHandler() {
+    this("RagConnect");
+  }
+
+  public JavaHandler(String name) {
+    this.name = name;
+  }
+
+  private static String extractPath(java.net.URI uri) {
+    String path = uri.getPath();
+    if (path.charAt(0) == '/') {
+      path = path.substring(1);
+    }
+    return path;
+  }
+
+  RagConnectToken registerConsumer(String path, java.util.function.Consumer<byte[]> consumer) {
+    RagConnectToken token = new RagConnectToken(java.net.URI.create("internal://host/" + path), null);
+    registerCallback(token, (s, bytes) -> consumer.accept(bytes));
+    return token;
+  }
+
+  boolean registerCallback(RagConnectToken connectToken, java.util.function.BiConsumer<String, byte[]> callback) {
+    String path = extractPath(connectToken.uri);
+    {{logInfo}}("[JAVA_HANDLER] Registering new callback for {{log_}}.", path);
+
+    java.util.List<java.util.function.BiConsumer<String, byte[]>> registeredCallbacks = callbackList.get(path);
+
+    if (registeredCallbacks == null) {
+      registeredCallbacks = java.util.Collections.synchronizedList(new java.util.ArrayList<>());
+      callbackList.put(path, registeredCallbacks);
+    }
+    registeredCallbacks.add(callback);
+    tokensForRemoval.put(connectToken, callback);
+
+    return true;
+  }
+
+  boolean unregisterCallback(RagConnectToken connectToken) {
+    String path = extractPath(connectToken.uri);
+    java.util.function.BiConsumer<String, byte[]> callback = tokensForRemoval.get(connectToken);
+    {{logInfo}}("[JAVA_HANDLER] Unregistering callback with uuid: on path: {{log_}}", path);
+
+    return callbackList.get(path).remove(callback);
+  }
+
+  void close() {
+  }
+
+  boolean push(String uriString, byte[] data) {
+    String path = extractPath(java.net.URI.create(uriString));
+    {{logDebug}}("[JAVA_HANDLER] Pushing a message for {{log_}}.", path);
+    if (data == null) {
+      {{logDebug}}("[JAVA_HANDLER] data was null, aborting");
+      return false;
+    }
+    String dataString = new String(data);
+    {{logDebug}}("[JAVA_HANDLER] Data: {{log_}}", dataString);
+
+    java.util.List<java.util.function.BiConsumer<String, byte[]>> callbacks = callbackList.get(path);
+
+    if (callbacks == null) {
+      {{logError}}("[JAVA_HANDLER] Could not publish message. No callback registered for path {{log_}}", path);
+      return false;
+    }
+
+    for (java.util.function.BiConsumer<String, byte[]> callback : callbacks) {
+      callback.accept(path, data);
+    }
+
+    return true;
+  }
+}
diff --git a/ragconnect.base/src/main/resources/ListAspect.mustache b/ragconnect.base/src/main/resources/ListAspect.mustache
deleted file mode 100644
index 764b5441862c96a010f6d07f86e2c1aad2dec81c..0000000000000000000000000000000000000000
--- a/ragconnect.base/src/main/resources/ListAspect.mustache
+++ /dev/null
@@ -1,24 +0,0 @@
-{{#hasTreeListEndpoints}}
-public void {{JastAddList}}.serialize(com.fasterxml.jackson.core.JsonGenerator g) throws SerializationException {
-  try {
-    g.writeStartArray();
-    for (T child : this) {
-      child.serialize(g);
-    }
-    g.writeEndArray();
-  } catch (java.io.IOException e) {
-    throw new SerializationException("unable to serialize {{JastAddList}}", e);
-  }
-}
-
-{{#typesForReceivingListEndpoints}}
-public static {{JastAddList}}<{{Name}}> {{Name}}.deserializeList(com.fasterxml.jackson.databind.node.ArrayNode node) throws DeserializationException {
-  {{JastAddList}}<{{Name}}> result = new {{JastAddList}}<>();
-  for (java.util.Iterator<com.fasterxml.jackson.databind.JsonNode> it = node.elements(); it.hasNext();) {
-    com.fasterxml.jackson.databind.JsonNode element = it.next();
-    result.add(deserialize(element));
-  }
-  return result;
-}
-{{/typesForReceivingListEndpoints}}
-{{/hasTreeListEndpoints}}
diff --git a/ragconnect.base/src/main/resources/MqttHandler.jadd b/ragconnect.base/src/main/resources/MqttHandler.mustache
similarity index 90%
rename from ragconnect.base/src/main/resources/MqttHandler.jadd
rename to ragconnect.base/src/main/resources/MqttHandler.mustache
index d859d38d819c17df941d4b0179fbeec28df6f83f..d5a83a04d89e67ecc09e45c0487bd37e516fd9ad 100644
--- a/ragconnect.base/src/main/resources/MqttHandler.jadd
+++ b/ragconnect.base/src/main/resources/MqttHandler.mustache
@@ -1,4 +1,3 @@
-aspect MqttHandler {
 public class MqttServerHandler {
   private final java.util.Map<String, MqttHandler> handlers = new java.util.HashMap<>();
   private final java.util.Map<RagConnectToken, java.util.function.BiConsumer<String, byte[]>> tokensForRemoval = new java.util.HashMap<>();
@@ -90,7 +89,6 @@ public class MqttHandler {
   }
   private static final int DEFAULT_PORT = 1883;
 
-  private final org.apache.logging.log4j.Logger logger;
   private final String name;
 
   /** The host running the MQTT broker. */
@@ -112,7 +110,6 @@ public class MqttHandler {
 
   public MqttHandler(String name) {
     this.name = java.util.Objects.requireNonNull(name, "Name must be set");
-    this.logger = org.apache.logging.log4j.LogManager.getLogger(MqttHandler.class);
     this.normalCallbacks = new java.util.HashMap<>();
     this.wildcardCallbacks = new java.util.ArrayList<>();
     this.readyLatch = new java.util.concurrent.CountDownLatch(1);
@@ -149,7 +146,7 @@ public class MqttHandler {
     java.util.Objects.requireNonNull(host, "Host need to be set!");
 
     this.host = java.net.URI.create("tcp://" + host + ":" + port);
-    logger.debug("Host for {} is {}", this.name, this.host);
+    {{logDebug}}("Host for {{log_}} is {{log_}}", this.name, this.host);
 
     org.fusesource.mqtt.client.MQTT mqtt = new org.fusesource.mqtt.client.MQTT();
     mqtt.setHost(this.host);
@@ -159,12 +156,12 @@ public class MqttHandler {
     // add the listener to dispatch messages later
     connection.listener(new org.fusesource.mqtt.client.ExtendedListener() {
       public void onConnected() {
-        logger.debug("Connected");
+        {{logDebug}}("Connected");
       }
 
       @Override
       public void onDisconnected() {
-        logger.debug("Disconnected");
+        {{logDebug}}("Disconnected");
       }
 
       @Override
@@ -172,10 +169,10 @@ public class MqttHandler {
                             org.fusesource.hawtbuf.Buffer body,
                             org.fusesource.mqtt.client.Callback<org.fusesource.mqtt.client.Callback<Void>> ack) {
         // this method is called, whenever a MQTT message is received
-        String topicString = topic.toString();
+        final String topicString = topic.toString();
         java.util.List<java.util.function.BiConsumer<String, byte[]>> callbackList = callbacksFor(topicString);
         if (callbackList.isEmpty()) {
-          logger.debug("Got a message at {}, but no callback to call. Forgot to subscribe?", topic);
+          {{logDebug}}("Got a message at {{log_}}, but no callback to call. Forgot to subscribe?", topic);
         } else {
           byte[] message = body.toByteArray();
           for (java.util.function.BiConsumer<String, byte[]> callback : callbackList) {
@@ -183,7 +180,7 @@ public class MqttHandler {
               astLock.lock();
               callback.accept(topicString, message);
             } catch (Exception e) {
-              logger.catching(e);
+              {{logException}}("Exception in callback for " + topicString, e);
             } finally {
               astLock.unlock();
             }
@@ -197,7 +194,7 @@ public class MqttHandler {
                             org.fusesource.hawtbuf.Buffer body,
                             Runnable ack) {
         // not used by this type of connection
-        logger.warn("onPublish should not be called");
+        {{logWarn}}("onPublish should not be called");
       }
 
       @Override
@@ -219,13 +216,13 @@ public class MqttHandler {
               new org.fusesource.mqtt.client.Callback<>() {
                 @Override
                 public void onSuccess(Void value) {
-                  logger.debug("success sending welcome message");
+                  {{logDebug}}("success sending welcome message");
                   setReady();
                 }
 
             @Override
-            public void onFailure(Throwable value) {
-              logger.debug("failure sending welcome message", value);
+            public void onFailure(Throwable cause) {
+              {{logException}}("failure sending welcome message", cause);
             }
           });
         } else {
@@ -296,7 +293,7 @@ public class MqttHandler {
       return false;
     }
     // register callback
-    logger.debug("new connection for {}", topic);
+    {{logDebug}}("new connection for {{log_}}", topic);
     final boolean needSubscribe;
     if (isWildcardTopic(topic)) {
       String regex = regexForWildcardTopic(topic);
@@ -316,11 +313,8 @@ public class MqttHandler {
       needSubscribe = pairToAddTo.callbacks.isEmpty();
       pairToAddTo.callbacks.add(callback);
     } else { // normal topic
-      java.util.List<java.util.function.BiConsumer<String, byte[]>> callbacksForTopic = normalCallbacks.get(topic);
-      if (callbacksForTopic == null) {
-        callbacksForTopic = new java.util.ArrayList<>();
-        normalCallbacks.put(topic, callbacksForTopic);
-      }
+      java.util.List<java.util.function.BiConsumer<String, byte[]>> callbacksForTopic = normalCallbacks.
+          computeIfAbsent(topic, t -> new java.util.ArrayList<>());
       needSubscribe = callbacksForTopic.isEmpty();
       callbacksForTopic.add(callback);
     }
@@ -333,20 +327,23 @@ public class MqttHandler {
         connection.subscribe(topicArray, new org.fusesource.mqtt.client.Callback<>() {
           @Override
           public void onSuccess(byte[] qoses) {
-            logger.debug("Subscribed to {}, qoses: {}", topic, qoses);
+            {{logDebug}}("Subscribed to {{log_}}, qoses: {{log_}}", topic, qoses);
             operationFinished.countDown();
           }
 
           @Override
           public void onFailure(Throwable cause) {
-            logger.error("Could not subscribe to {}", topic, cause);
+            {{logException}}("Could not subscribe to " + topic, cause);
             success.set(false);
             operationFinished.countDown();
           }
         });
       });
       try {
-        operationFinished.await(2, java.util.concurrent.TimeUnit.SECONDS);
+        boolean finishedInTime = operationFinished.await(2, java.util.concurrent.TimeUnit.SECONDS);
+        if (!finishedInTime) {
+          return false;
+        }
         return success.get();
       } catch (InterruptedException e) {
         return false;
@@ -403,7 +400,7 @@ public class MqttHandler {
     }
 
     if (topicToUnsubscribe == null) {
-      logger.warn("Disconnect for not connected topic '{}'", topic);
+      {{logWarn}}("Disconnect for not connected topic '{{log_}}'", topic);
       return false;
     }
 
@@ -422,15 +419,18 @@ public class MqttHandler {
           @Override
           public void onFailure(Throwable cause) {
             success.set(false);
-            logger.warn("Could not disconnect from {}", topic, cause);
+            {{logException}}("Could not disconnect from " + topic, cause);
             operationFinished.countDown();
           }
         });
       });
       try {
-        operationFinished.await(2, java.util.concurrent.TimeUnit.SECONDS);
+        boolean finishedInTime = operationFinished.await(2, java.util.concurrent.TimeUnit.SECONDS);
+        if (!finishedInTime) {
+          return false;
+        }
       } catch (InterruptedException e) {
-        logger.catching(e);
+        {{logException}}("Interrupted while disconnecting from " + topic, e);
         success.set(false);
       }
     }
@@ -457,14 +457,14 @@ public class MqttHandler {
 
   public void close() {
     if (connection == null) {
-      logger.warn("Stopping without connection. Was setHost() called?");
+      {{logWarn}}("Stopping without connection. Was setHost() called?");
       return;
     }
     connection.getDispatchQueue().execute(() -> {
       connection.disconnect(new org.fusesource.mqtt.client.Callback<>() {
         @Override
         public void onSuccess(Void value) {
-          logger.info("Disconnected {} from {}", name, host);
+          {{logInfo}}("Disconnected {{log_}} from {{log_}}", name, host);
         }
 
         @Override
@@ -490,12 +490,12 @@ public class MqttHandler {
         connection.publish(topic, bytes, qos, retain, new org.fusesource.mqtt.client.Callback<>() {
           @Override
           public void onSuccess(Void value) {
-            logger.debug("Published some bytes to {}", topic);
+            {{logDebug}}("Published some bytes to {{log_}}", topic);
           }
 
           @Override
-          public void onFailure(Throwable value) {
-            logger.warn("Could not publish on topic '{}'", topic, value);
+          public void onFailure(Throwable cause) {
+            {{logException}}("Could not publish on topic " + topic, cause);
           }
         });
       });
@@ -504,4 +504,3 @@ public class MqttHandler {
     }
   }
 }
-}
diff --git a/ragconnect.base/src/main/resources/RagConnectObserver.mustache b/ragconnect.base/src/main/resources/RagConnectObserver.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..24ed61c1a2ed6ceabec733b45f76a51eeae5f1e0
--- /dev/null
+++ b/ragconnect.base/src/main/resources/RagConnectObserver.mustache
@@ -0,0 +1,178 @@
+{{#configIncrementalOptionActive}}
+aspect RagConnectObserver {
+
+  class RagConnectObserver implements ASTState.Trace.Receiver {
+
+    class RagConnectObserverEntry {
+      final ASTNode node;
+      final String attributeString;
+      final boolean compareParams;
+      final Object params;
+      final Runnable attributeCall;
+      //final RagConnectToken connectToken;
+      final java.util.List<RagConnectToken> connectList = new java.util.ArrayList<>();
+
+      RagConnectObserverEntry(ASTNode node, String attributeString,
+                              boolean compareParams, Object params, Runnable attributeCall) {
+        this.node = node;
+        this.attributeString = attributeString;
+        this.compareParams = compareParams;
+        this.params = params;
+        this.attributeCall = attributeCall;
+      }
+
+      boolean baseMembersEqualTo(RagConnectObserverEntry other) {
+        return baseMembersEqualTo(other.node, other.attributeString, other.compareParams, other.params);
+      }
+
+      boolean baseMembersEqualTo(ASTNode otherNode, String otherAttributeString,
+          boolean forceCompareParams, Object otherParams) {
+        return this.node.equals(otherNode) &&
+            this.attributeString.equals(otherAttributeString) &&
+            //this.compareParams == otherCompareParams &&
+            (!(this.compareParams || forceCompareParams) || java.util.Objects.equals(this.params, otherParams));
+      }
+    }
+
+{{#configExperimentalJastAdd329}}
+    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;
+      }
+    }
+{{/configExperimentalJastAdd329}}
+
+    ASTState.Trace.Receiver oldReceiver;
+
+    java.util.List<RagConnectObserverEntry> observedNodes = new java.util.ArrayList<>();
+
+{{#configExperimentalJastAdd329}}
+    java.util.Set<RagConnectObserverEntry> entryQueue = new java.util.HashSet<>();
+    java.util.Deque<RagConnectObserverStartEntry> startEntries = new java.util.LinkedList<>();
+{{/configExperimentalJastAdd329}}
+
+    RagConnectObserver(ASTNode node) {
+      // set the receiver. potentially dangerous because overriding existing receiver!
+      oldReceiver = node.trace().getReceiver();
+      node.trace().setReceiver(this);
+    }
+
+    void add(RagConnectToken connectToken, ASTNode node, boolean compareParams, Object params,
+             Runnable attributeCall, String attributeString) {
+      {{#configLoggingEnabledForIncremental}}
+      {{logDebug}}("** observer add: {{log_}} on {{log_}}{{log_}}",
+        node, attributeString, (compareParams ? " (parameterized)" : ""));
+      {{/configLoggingEnabledForIncremental}}
+      // either add to an existing entry (with same node, attribute, params) or create new entry
+      boolean needNewEntry = true;
+      for (RagConnectObserverEntry entry : observedNodes) {
+        if (entry.baseMembersEqualTo(node, attributeString, true, params)) {
+          entry.connectList.add(connectToken);
+          needNewEntry = false;
+          break;
+        }
+      }
+      if (needNewEntry) {
+        RagConnectObserverEntry newEntry = new RagConnectObserverEntry(node, attributeString,
+            compareParams, params, attributeCall);
+        newEntry.connectList.add(connectToken);
+        observedNodes.add(newEntry);
+      }
+    }
+
+    void remove(RagConnectToken connectToken) {
+      java.util.List<RagConnectObserverEntry> entriesToDelete = new java.util.ArrayList<>();
+      for (RagConnectObserverEntry entry : observedNodes) {
+        entry.connectList.remove(connectToken);
+        if (entry.connectList.isEmpty()) {
+          entriesToDelete.add(entry);
+        }
+      }
+      observedNodes.removeAll(entriesToDelete);
+    }
+
+    @Override
+    public void accept(ASTState.Trace.Event event, ASTNode node, String attribute, Object params, Object value) {
+      oldReceiver.accept(event, node, attribute, params, value);
+{{#configExperimentalJastAdd329}}
+      // react to INC_FLUSH_START and remember entry
+      if (event == ASTState.Trace.Event.INC_FLUSH_START) {
+        {{#configLoggingEnabledForIncremental}}
+        {{logDebug}}("** observer start: {{log_}} on {{log_}}", node, attribute);
+        {{/configLoggingEnabledForIncremental}}
+        startEntries.addFirst(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) {
+        if (startEntries.isEmpty()) {
+          {{#configLoggingEnabledForIncremental}}
+          {{logDebug}}("** observer end without start! for {{log_}} on {{log_}}", node, attribute);
+          {{/configLoggingEnabledForIncremental}}
+          return;
+        }
+        RagConnectObserverStartEntry startEntry = startEntries.peekFirst();
+        if (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();
+          startEntries.removeFirst();
+          {{#configLoggingEnabledForIncremental}}
+          {{logDebug}}("** observer process (entries: {{log_}}): {{log_}} on {{log_}}",
+            entriesToProcess.length, node, attribute);
+          {{/configLoggingEnabledForIncremental}}
+          for (RagConnectObserverEntry entry : entriesToProcess) {
+            entry.attributeCall.run();
+          }
+          return;
+        }
+      }
+
+{{/configExperimentalJastAdd329}}
+      // ignore all other events but INC_FLUSH_ATTR
+      if (event != ASTState.Trace.Event.INC_FLUSH_ATTR) {
+        return;
+      }
+
+      {{#configLoggingEnabledForIncremental}}
+      {{logDebug}}("** observer check INC_FLUSH_ATTR event: {{log_}} on {{log_}}", node, attribute);
+      {{/configLoggingEnabledForIncremental}}
+      // iterate through list, if matching pair. could maybe be more efficient.
+      for (RagConnectObserverEntry entry : observedNodes) {
+        if (entry.baseMembersEqualTo(node, attribute, false, params)) {
+          // hit. call the attribute/nta-token
+          {{#configLoggingEnabledForIncremental}}
+          {{logDebug}}("** observer hit: {{log_}} on {{log_}}", entry.node, entry.attributeString);
+          {{/configLoggingEnabledForIncremental}}
+{{#configExperimentalJastAdd329}}
+          entryQueue.add(entry);
+{{/configExperimentalJastAdd329}}
+{{^configExperimentalJastAdd329}}
+          entry.attributeCall.run();
+{{/configExperimentalJastAdd329}}
+        }
+      }
+    }
+  }
+
+  private static RagConnectObserver ASTNode.{{observerInstanceFieldName}};
+  RagConnectObserver ASTNode.{{observerInstanceSingletonMethodName}}() {
+    if ({{observerInstanceFieldName}} == null) {
+      // does not matter, which node is used to create the observer as ASTState/tracing is also static
+      {{observerInstanceFieldName}} = new RagConnectObserver(this);
+    }
+    return {{observerInstanceFieldName}};
+  }
+  void ASTNode.{{observerInstanceResetMethodName}}() {
+    {{observerInstanceFieldName}} = null;
+  }
+}
+{{/configIncrementalOptionActive}}
diff --git a/ragconnect.base/src/main/resources/RestHandler.jadd b/ragconnect.base/src/main/resources/RestHandler.mustache
similarity index 85%
rename from ragconnect.base/src/main/resources/RestHandler.jadd
rename to ragconnect.base/src/main/resources/RestHandler.mustache
index 9187afdab7a15c0bd7dc10679991505874c8af61..25192af366bfd4da29ace5c11d661b8cc8fd0992 100644
--- a/ragconnect.base/src/main/resources/RestHandler.jadd
+++ b/ragconnect.base/src/main/resources/RestHandler.mustache
@@ -1,14 +1,9 @@
-import java.util.concurrent.TimeUnit;aspect RestHandler {
 public class RestServerHandler {
   private static final int DEFAULT_PORT = 4567;
   private final java.util.Map<Integer, RestHandler> handlers = new java.util.HashMap<>();
   private final java.util.Map<RagConnectToken, Object> tokensForRemoval = new java.util.HashMap<>();
   private String name;
 
-  public RestServerHandler() {
-    this("RagConnect");
-  }
-
   public RestServerHandler(String name) {
     this.name = name;
   }
@@ -39,7 +34,7 @@ public class RestServerHandler {
 
   public boolean disconnect(RagConnectToken connectToken) {
     RestHandler handler = resolveHandler(connectToken.uri);
-    return handler != null ? handler.disconnect(connectToken.uri.getPath(), tokensForRemoval.get(connectToken)) : false;
+    return handler != null && handler.disconnect(connectToken.uri.getPath(), tokensForRemoval.get(connectToken));
   }
 
   public void close() {
@@ -55,23 +50,13 @@ public class RestServerHandler {
 public class RestHandler {
   private static final int DEFAULT_PORT = 4567;
 
-  private final org.apache.logging.log4j.Logger logger;
-  private final String name;
   private int port;
-  private final java.util.concurrent.CountDownLatch exitCondition;
   /** Dispatch knowledge */
   private final java.util.Map<String, java.util.List<java.util.function.Consumer<String>>> callbacks;
   private final java.util.Map<String, SupplierWithException<String>> suppliers;
 
   public RestHandler() {
-    this("RagConnect");
-  }
-
-  public RestHandler(String name) {
-    this.logger = org.apache.logging.log4j.LogManager.getLogger(RestHandler.class);
-    this.name = name;
     this.port = DEFAULT_PORT;
-    this.exitCondition = new java.util.concurrent.CountDownLatch(1);
     this.callbacks = new java.util.HashMap<>();
     this.suppliers = new java.util.HashMap<>();
   }
@@ -111,7 +96,7 @@ public class RestHandler {
 
   public void newGETConnection(String path, SupplierWithException<String> supplier) {
     if (suppliers.get(path) != null) {
-      logger.warn("Overriding existing supplier for '{}'", path);
+      {{logWarn}}("Overriding existing supplier for '{{log_}}'", path);
     }
     suppliers.put(path, supplier);
     spark.Spark.get(path, (request, response) -> {
@@ -142,7 +127,7 @@ public class RestHandler {
   }
 
   private void start() {
-    logger.info("Starting REST server at {}", this.port);
+    {{logInfo}}("Starting REST server at {{log_}}", this.port);
     spark.Spark.port(this.port);
     spark.Spark.init();
     spark.Spark.awaitInitialization();
@@ -156,6 +141,5 @@ public class RestHandler {
 }
 @FunctionalInterface
 public interface SupplierWithException<T> {
-  public T get() throws Exception;
-}
+  T get() throws Exception;
 }
diff --git a/ragconnect.base/src/main/resources/dependencyDefinition.mustache b/ragconnect.base/src/main/resources/dependencyDefinition.mustache
index 0ab8d55242816a7d3ada0491c8c7db6dcd70ee42..7c2ac5ab06d2e52f5f2ba5a086167c815ac5254a 100644
--- a/ragconnect.base/src/main/resources/dependencyDefinition.mustache
+++ b/ragconnect.base/src/main/resources/dependencyDefinition.mustache
@@ -1,3 +1,7 @@
-public void {{targetParentTypeName}}.{{dependencyMethod}}({{sourceParentTypeName}} source) {
+/**
+* @deprecated Manual dependency definitions are not fully tested any longer since 1.0.0, see <a href="https://jastadd.pages.st.inf.tu-dresden.de/ragconnect-dev/dsl/#dependency-definitions">https://jastadd.pages.st.inf.tu-dresden.de/ragconnect-dev/dsl/#dependency-definitions</a>
+*/
+@Deprecated(since = "1.0.0")
+public void {{targetParentTypeName}}.{{dependencyMethodName}}({{sourceParentTypeName}} source) {
   add{{internalRelationPrefix}}Source(source);
 }
diff --git a/ragconnect.base/src/main/resources/handleUri.mustache b/ragconnect.base/src/main/resources/handleUri.mustache
index ff0ea7af7f920ac09a14de75a4afe1882540ac94..efb4aacfbf1e003dcb540ba77bdaca68a8aed6cd 100644
--- a/ragconnect.base/src/main/resources/handleUri.mustache
+++ b/ragconnect.base/src/main/resources/handleUri.mustache
@@ -6,18 +6,18 @@ try {
   host = uri.getHost();
   path = uri.getPath() + (uri.getFragment() != null ? "#" : "");
 } catch (java.net.URISyntaxException e) {
-  System.err.println(e.getMessage());  // Maybe re-throw error?
+  {{logError}}(e.getMessage());  // Maybe re-throw error?
   return false;
 }
 if (scheme == null || scheme.isBlank()) {
-  System.err.println("Missing or empty scheme in " + uri);
+  {{logError}}("Missing or empty scheme in {{log_}}", uri);
   return false;
 }
 if (host == null || host.isBlank()) {
-  System.err.println("Missing or empty host in " + uri);
+  {{logError}}("Missing or empty host in {{log_}}", uri);
   return false;
 }
 if (path == null || path.isBlank()) {
-  System.err.println("Missing or empty path in " + uri);
+  {{logError}}("Missing or empty path in {{log_}}", uri);
   return false;
 }
diff --git a/ragconnect.base/src/main/resources/handler.mustache b/ragconnect.base/src/main/resources/handler.mustache
index fe8d9459a0d1b977f7416da85ba3fb1c4d3e6b8f..75bd14ce26638ceeee267251988a01b516de0970 100644
--- a/ragconnect.base/src/main/resources/handler.mustache
+++ b/ragconnect.base/src/main/resources/handler.mustache
@@ -1,19 +1,51 @@
 aspect RagConnectHandler {
 {{#Handlers}}
   {{#InUse}}
-  private {{ClassName}} {{rootNodeName}}.{{FieldName}} = {{{Construction}}};
+  private {{ClassName}} {{rootNodeName}}.{{fieldName}} = {{{constructionSnippet}}};
+  {{#hasRootTypeComponents}}inh {{ClassName}} ASTNode.{{attributeName}}();{{/hasRootTypeComponents}}
   {{#rootTypeComponents}}
-  {{#first}}inh {{ClassName}} ASTNode.{{AttributeName}}();{{/first}}
-  eq {{rootNodeName}}.get{{name}}().{{AttributeName}}() = {{FieldName}};
+  eq {{rootNodeName}}.get{{Name}}().{{attributeName}}() = {{fieldName}};
   {{/rootTypeComponents}}
-  syn {{ClassName}} {{rootNodeName}}.{{AttributeName}}() = {{FieldName}};
+  syn {{ClassName}} {{rootNodeName}}.{{attributeName}}() = {{fieldName}};
   {{/InUse}}
 {{/Handlers}}
-  public void {{rootNodeName}}.{{closeMethod}}() {
+  public void {{rootNodeName}}.{{closeMethodName}}() {
     {{#Handlers}}
-    {{#InUse}}{{FieldName}}.close();{{/InUse}}
+    {{#InUse}}{{fieldName}}.close();{{/InUse}}
     {{/Handlers}}
+    {{#configIncrementalOptionActive}}
+    trace().setReceiver({{observerInstanceSingletonMethodName}}().oldReceiver);
+    {{observerInstanceResetMethodName}}();
+    {{/configIncrementalOptionActive}}
   }
+
+{{#javaHandler}}
+  {{#InUse}}
+  public RagConnectToken {{rootNodeName}}.{{registerConsumerMethodName}}(String path, java.util.function.Consumer<byte[]> consumer) {
+    return {{fieldName}}.registerConsumer(path, consumer);
+  }
+  public boolean {{rootNodeName}}.{{pushMethodName}}(String uriString, byte[] data) {
+    return {{fieldName}}.push(uriString, data);
+  }
+  {{> JavaHandler}}
+  {{/InUse}}
+{{/javaHandler}}
+
+{{#mqttHandler}}
+  {{#InUse}}
+  public void {{rootNodeName}}.{{setupWaitUntilReadyMethodName}}(long time, java.util.concurrent.TimeUnit unit) {
+    {{fieldName}}.setupWaitUntilReady(time, unit);
+  }
+  {{> MqttHandler}}
+  {{/InUse}}
+{{/mqttHandler}}
+
+{{#restHandler}}
+  {{#InUse}}
+  {{> RestHandler}}
+  {{/InUse}}
+{{/restHandler}}
+
   class RagConnectToken {
     static java.util.concurrent.atomic.AtomicLong counter = new java.util.concurrent.atomic.AtomicLong(0);
     final long id;
@@ -53,7 +85,7 @@ aspect RagConnectHandler {
   class RagConnectPublisher {
     java.util.List<Runnable> senders = new java.util.ArrayList<>();
     java.util.Map<RagConnectToken, Runnable> tokenToSender;
-    byte[] lastValue;
+    private byte[] lastValue;
 
     void add(Runnable sender, RagConnectToken connectToken) {
       if (tokenToSender == null) {
@@ -64,24 +96,99 @@ aspect RagConnectHandler {
     }
 
     boolean remove(RagConnectToken token) {
-      if (tokenToSender == null) {
-        System.err.println("Removing sender before first addition for " + token.entityName + " at " + token.uri);
+      String errorMessage = internal_remove(token);
+      if (errorMessage == null) {
+        return true;
+      } else {
+        {{logWarn}}(errorMessage);
         return false;
       }
+    }
+
+    /**
+    * (internal) Removes the token, returning an error message if there is one.
+    * @param token the token to be removed
+    * @return an error message (upon error), or null (upon success)
+    */
+    String internal_remove(RagConnectToken token) {
+      if (tokenToSender == null) {
+        return "Removing sender before first addition for " + token.entityName + " at " + token.uri;
+      }
       Runnable sender = tokenToSender.remove(token);
       if (sender == null) {
-        System.err.println("Could not find connected sender for " + token.entityName + " at " + token.uri);
-        return false;
+        return "Could not find connected sender for " + token.entityName + " at " + token.uri;
       }
       boolean success = senders.remove(sender);
       if (senders.isEmpty()) {
         lastValue = null;
       }
-      return success;
+      return success ? null : "Could not remove sender for " + token.entityName + " at " + token.uri;
     }
 
     void run() {
       senders.forEach(Runnable::run);
     }
+
+    void run(RagConnectToken token) {
+      tokenToSender.get(token).run();
+    }
+
+    byte[] getLastValue() {
+      return lastValue;
+    }
+
+    void setLastValue(byte[] value) {
+      this.lastValue = value;
+    }
+  }
+
+  class RagConnectMappingPublisher {
+    java.util.Map<Integer, RagConnectPublisher> publishers = new java.util.HashMap<>();
+
+    void add(Runnable sender, int index, RagConnectToken connectToken) {
+      publishers.computeIfAbsent(index, ignoredIndex -> new RagConnectPublisher()).add(sender, connectToken);
+    }
+
+    boolean remove(RagConnectToken token) {
+      // publishers.forEach((index, publisher) -> publisher.remove(token));
+      // remove token from each publisher, at least one has to successfully remove the token to make this call a success
+      boolean result = false;
+      java.util.List<String> errorMessages = new java.util.ArrayList<>();
+      for (RagConnectPublisher publisher : publishers.values()) {
+        String errorMessage = publisher.internal_remove(token);
+        if (errorMessage == null) {
+          result = true;
+        } else {
+          errorMessages.add(errorMessage);
+        }
+      }
+      if (!result) {
+        // only print error message, if all publishers failed to remove the token
+        for (String message : errorMessages) {
+          {{logError}}(message);
+        }
+      }
+      return result;
+    }
+
+    void run(int index) {
+      java.util.Optional.ofNullable(publishers.get(index)).ifPresent(RagConnectPublisher::run);
+    }
+
+    void run(int index, RagConnectToken token) {
+      java.util.Optional.ofNullable(publishers.get(index)).ifPresent(publisher -> publisher.run(token));
+    }
+
+    byte[] getLastValue(int index) {
+      RagConnectPublisher publisher = publishers.get(index);
+      if (publisher == null) {
+        return null;
+      }
+      return publisher.getLastValue();
+    }
+
+    void setLastValue(int index, final byte[] value) {
+      java.util.Optional.ofNullable(publishers.get(index)).ifPresent(publisher -> publisher.setLastValue(value));
+    }
   }
 }
diff --git a/ragconnect.base/src/main/resources/mappingApplication.mustache b/ragconnect.base/src/main/resources/mappingApplication.mustache
index bc59e0b318dcd430b5f21fd0a67d157b35a4bb7c..efd17af4e8947232f44b93c3749d86b1e637dd46 100644
--- a/ragconnect.base/src/main/resources/mappingApplication.mustache
+++ b/ragconnect.base/src/main/resources/mappingApplication.mustache
@@ -1,17 +1,39 @@
+{{#configEvaluationCounter}}
+  {{evaluationCounterVariable}}.incrementCall("{{parentTypeName}}", "{{entityName}}");
+{{/configEvaluationCounter}}
+{{#Send}}
+{{^PrimitiveType}}
+if ({{firstInputVarName}} == null) {
+  {{#configEvaluationCounter}}
+    {{evaluationCounterVariable}}.incrementFirstNull("{{parentTypeName}}", "{{entityName}}");
+  {{/configEvaluationCounter}}
+  {{preemptiveReturn}}
+}
+{{/PrimitiveType}}
+{{/Send}}
 {{{lastDefinitionToType}}} {{lastResult}};
 try {
-  {{#InnerMappingDefinitions}}
+  {{#innerMappingDefinitions}}
   {{^last}}{{{toType}}} {{/last}}{{outputVarName}} = {{methodName}}({{inputVarName}});
-  {{/InnerMappingDefinitions}}
+  {{/innerMappingDefinitions}}
 } catch (RagConnectRejectMappingException e) {
   // do not print message in case of rejection
+  {{#configEvaluationCounter}}
+    {{evaluationCounterVariable}}.incrementReject("{{parentTypeName}}", "{{entityName}}");
+  {{/configEvaluationCounter}}
   {{preemptiveReturn}}
 } catch (Exception e) {
   e.printStackTrace();
+  {{#configEvaluationCounter}}
+    {{evaluationCounterVariable}}.incrementException("{{parentTypeName}}", "{{entityName}}");
+  {{/configEvaluationCounter}}
   {{preemptiveReturn}}
 }
-{{^alwaysApply}}
+{{^AlwaysApply}}
 if ({{{condition}}}) {
+  {{#configEvaluationCounter}}
+    {{evaluationCounterVariable}}.incrementSkip("{{parentTypeName}}", "{{entityName}}");
+  {{/configEvaluationCounter}}
   {{preemptiveReturn}}
 }
-{{/alwaysApply}}
+{{/AlwaysApply}}
diff --git a/ragconnect.base/src/main/resources/mappingDefinition.mustache b/ragconnect.base/src/main/resources/mappingDefinition.mustache
index 09f161f37c3742850b09969d4eb85c7798d2d346..392f44418129861ebc51b7c5e91c5c2721cf58e1 100644
--- a/ragconnect.base/src/main/resources/mappingDefinition.mustache
+++ b/ragconnect.base/src/main/resources/mappingDefinition.mustache
@@ -1,3 +1,46 @@
-protected static {{{toType}}} ASTNode.{{methodName}}({{{fromType}}} {{fromVariableName}}) throws Exception {
-  {{{content}}}
+{{#isUsed}}
+  {{#isTemplateDefaultMappingDefinition}}
+    {{#isSerializeListMapping}}
+public void {{configJastAddList}}.serialize(com.fasterxml.jackson.core.JsonGenerator g) throws SerializationException {
+  try {
+    g.writeStartArray();
+    for (T child : this) {
+      child.serialize(g);
+    }
+    g.writeEndArray();
+  } catch (java.io.IOException e) {
+    throw new SerializationException("unable to serialize {{configJastAddList}}", e);
+  }
 }
+    {{/isSerializeListMapping}}
+    {{#SerializeJavaUtilListMapping}}
+protected static <T extends ASTNode> void ASTNode.serializeJavaUtilList(
+        java.util.List<T> input, com.fasterxml.jackson.core.JsonGenerator g) throws SerializationException {
+  try {
+    g.writeStartArray();
+    for (T child : input) {
+      child.serialize(g);
+    }
+    g.writeEndArray();
+  } catch (java.io.IOException e) {
+    throw new SerializationException("unable to serialize list", e);
+  }
+}
+    {{/SerializeJavaUtilListMapping}}
+    {{#DeserializeListMapping}}
+public static {{configJastAddList}}<{{Name}}> {{Name}}.deserializeListOf{{Name}}(com.fasterxml.jackson.databind.node.ArrayNode node) throws DeserializationException {
+  {{configJastAddList}}<{{Name}}> result = new {{configJastAddList}}<>();
+  for (java.util.Iterator<com.fasterxml.jackson.databind.JsonNode> it = node.elements(); it.hasNext();) {
+    com.fasterxml.jackson.databind.JsonNode element = it.next();
+    result.add(deserialize(element));
+  }
+  return result;
+}
+    {{/DeserializeListMapping}}
+  {{/isTemplateDefaultMappingDefinition}}
+  {{^TemplateDefaultMappingDefinition}}
+protected {{{toType}}} ASTNode.{{methodName}}({{{fromType}}} {{FromVariableName}}) throws Exception {
+  {{{Content}}}
+}
+  {{/TemplateDefaultMappingDefinition}}
+{{/isUsed}}
diff --git a/ragconnect.base/src/main/resources/mqtt.mustache b/ragconnect.base/src/main/resources/mqtt.mustache
deleted file mode 100644
index f12cb8982443f01dd6195e2c86a0ae55f1485811..0000000000000000000000000000000000000000
--- a/ragconnect.base/src/main/resources/mqtt.mustache
+++ /dev/null
@@ -1,5 +0,0 @@
-aspect MQTT {
-  public void {{rootNodeName}}.{{mqttSetupWaitUntilReadyMethod}}(long time, java.util.concurrent.TimeUnit unit) {
-    {{mqttHandlerField}}.setupWaitUntilReady(time, unit);
-  }
-}
diff --git a/ragconnect.base/src/main/resources/ragConnectVersion.properties b/ragconnect.base/src/main/resources/ragConnectVersion.properties
deleted file mode 100644
index 3f322c60cc672a9603f285be04dc8a2d53f8f03b..0000000000000000000000000000000000000000
--- a/ragconnect.base/src/main/resources/ragConnectVersion.properties
+++ /dev/null
@@ -1,2 +0,0 @@
-#Wed Nov 24 10:32:52 CET 2021
-version=0.3.2
diff --git a/ragconnect.base/src/main/resources/ragconnect.mustache b/ragconnect.base/src/main/resources/ragconnect.mustache
index dd509aa53b34e56f425777c16f5f9f3d34c2fc7a..5bd0166a46bb6f02be2df10a2bfd26b9cc63d508 100644
--- a/ragconnect.base/src/main/resources/ragconnect.mustache
+++ b/ragconnect.base/src/main/resources/ragconnect.mustache
@@ -1,45 +1,88 @@
-{{#usesMqtt}}{{> mqtt}}{{/usesMqtt}}
 {{> handler}}
 aspect RagConnect {
-  {{#TokenReceiveDefinitions}}
-  {{> receiveDefinition}}
-  {{/TokenReceiveDefinitions}}
-
-  {{#TokenSendDefinitions}}
-  {{> sendDefinition}}
-  {{/TokenSendDefinitions}}
-
-  {{#TypeReceiveDefinitions}}
-  {{> receiveDefinition}}
-  {{/TypeReceiveDefinitions}}
-
-  {{#TypeSendDefinitions}}
-  {{> sendDefinition}}
-  {{/TypeSendDefinitions}}
+  {{#allPortDefinitionList}}
+    {{#Send}}
+      {{> sendDefinition}}
+    {{/Send}}
+    {{^Send}}
+      {{> receiveDefinition}}
+    {{/Send}}
+  {{/allPortDefinitionList}}
 
   class RagConnectRejectMappingException extends RuntimeException {}
   private static void ASTNode.reject() {
     throw new RagConnectRejectMappingException();
   }
 
-  {{#MappingDefinitions}}
-  {{#isUsed}}
+  {{#allMappingDefinitions}}
   {{> mappingDefinition}}
-  {{/isUsed}}
-  {{/MappingDefinitions}}
+  {{/allMappingDefinitions}}
 
-  {{#DependencyDefinitions}}
+  {{#allDependencyDefinitionList}}
   {{> dependencyDefinition}}
-  {{/DependencyDefinitions}}
+  {{/allDependencyDefinitionList}}
 
-  {{#TokenComponents}}
+  {{#tokenComponentsThatNeedProxy}}
   {{> tokenComponent}}
-  {{/TokenComponents}}
+  {{/tokenComponentsThatNeedProxy}}
+
+  {{#typeDeclsOfContextFreePortTargets}}
+  {{> typeDecl}}
+  {{/typeDeclsOfContextFreePortTargets}}
+
+  {{! --- touchedTerminals ---}}
+  {{#allTypeDecls}}
+    {{Name}} {{Name}}.{{touchedTerminalsMethodName}}() {
+    {{#tokenComponents}}
+      get{{Name}}();
+    {{/tokenComponents}}
+    {{#normalComponents}}
+      get{{Name}}().{{touchedTerminalsMethodName}}();
+    {{/normalComponents}}
+    {{#listComponents}}
+      for ({{#TypeDecl}}{{Name}}{{/TypeDecl}} element : get{{Name}}List()) {
+        element.{{touchedTerminalsMethodName}}();
+      }
+    {{/listComponents}}
+    return this;
+  }
+  {{/allTypeDecls}}
+  ASTNode ASTNode.{{touchedTerminalsMethodName}}() {
+    return this;
+  }
+  {{configJastAddList}}<T> {{configJastAddList}}.{{touchedTerminalsMethodName}}() {
+    for (T child : this) {
+      child.{{touchedTerminalsMethodName}}();
+    }
+    return this;
+  }
+  {{configJastAddOpt}}<T> {{configJastAddOpt}}.{{touchedTerminalsMethodName}}() {
+    if (getChild(0) != null) {
+      getChild(0).{{touchedTerminalsMethodName}}();
+    }
+    return this;
+  }
 
-  {{> ListAspect}}
+  static void ASTNode.{{logConsoleOut}}(String message, Object... args) {
+    System.out.println(String.format(message, args));
+  }
+
+  static void ASTNode.{{logConsoleOut}}(String message, Throwable t) {
+    System.out.println(message);
+    t.printStackTrace();
+  }
+
+  static void ASTNode.{{logConsoleErr}}(String message, Object... args) {
+    System.err.println(String.format(message, args));
+  }
+
+  static void ASTNode.{{logConsoleErr}}(String message, Throwable t) {
+    System.err.println(message);
+    t.printStackTrace();
+  }
 
   public void {{rootNodeName}}.ragconnectCheckIncremental() {
-  {{#incrementalOptionActive}}
+  {{#configIncrementalOptionActive}}
     // check if --tracing is active
     trace().getReceiver();
     // check if tracing of INC_FLUSH_ATTR is possible, i.e., if --tracing=flush
@@ -48,131 +91,10 @@ aspect RagConnect {
     mayHaveRewrite();
     // check if --incremental is active
     Object checkIncremental = inc_throwAway_visited;
-  {{/incrementalOptionActive}}
+  {{/configIncrementalOptionActive}}
   }
 }
 
-{{#incrementalOptionActive}}
-aspect RagConnectObserver {
-
-  class RagConnectObserver implements ASTState.Trace.Receiver {
+{{> RagConnectObserver}}
 
-    class RagConnectObserverEntry {
-      final RagConnectToken connectToken;
-      final ASTNode node;
-      final String attributeString;
-      final Runnable attributeCall;
-
-      RagConnectObserverEntry(RagConnectToken connectToken, ASTNode node, String attributeString, Runnable attributeCall) {
-        this.connectToken = connectToken;
-        this.node = node;
-        this.attributeString = attributeString;
-        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(RagConnectToken connectToken, ASTNode node, String attributeString, Runnable attributeCall) {
-      {{#loggingEnabledForIncremental}}
-      System.out.println("** observer add: " + node + " on " + attributeString);
-      {{/loggingEnabledForIncremental}}
-      observedNodes.add(new RagConnectObserverEntry(connectToken, node, attributeString, attributeCall));
-    }
-    void remove(RagConnectToken connectToken) {
-      observedNodes.removeIf(entry -> entry.connectToken.equals(connectToken));
-    }
-    @Override
-    public void accept(ASTState.Trace.Event event, ASTNode node, String attribute, Object params, Object value) {
-      oldReceiver.accept(event, node, attribute, params, value);
-{{#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;
-      }
-
-      {{#loggingEnabledForIncremental}}
-      System.out.println("** observer check INC_FLUSH_ATTR event: " + node + " on " + attribute);
-      {{/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
-          {{#loggingEnabledForIncremental}}
-          System.out.println("** observer hit: " + entry.node + " on " + entry.attributeString);
-          {{/loggingEnabledForIncremental}}
-{{#experimentalJastAdd329}}
-          entryQueue.add(entry);
-{{/experimentalJastAdd329}}
-{{^experimentalJastAdd329}}
-          entry.attributeCall.run();
-{{/experimentalJastAdd329}}
-        }
-      }
-    }
-  }
-
-  private static RagConnectObserver ASTNode._ragConnectObserverInstance;
-  RagConnectObserver ASTNode._ragConnectObserver() {
-    if (_ragConnectObserverInstance == null) {
-      // does not matter, which node is used to create the observer as ASTState/tracing is also static
-      _ragConnectObserverInstance = new RagConnectObserver(this);
-    }
-    return _ragConnectObserverInstance;
-  }
-}
-{{/incrementalOptionActive}}
+{{> EvaluationCounter}}
diff --git a/ragconnect.base/src/main/resources/ragconnectVersion.properties b/ragconnect.base/src/main/resources/ragconnectVersion.properties
new file mode 100644
index 0000000000000000000000000000000000000000..08b12cf3b90afcc13f9ba32d5b42877333390019
--- /dev/null
+++ b/ragconnect.base/src/main/resources/ragconnectVersion.properties
@@ -0,0 +1,2 @@
+#Tue Sep 06 12:31:39 CEST 2022
+version=1.0.0
diff --git a/ragconnect.base/src/main/resources/receiveDefinition.mustache b/ragconnect.base/src/main/resources/receiveDefinition.mustache
index c29eff1372604f7e302deaeaebb3bb0b83d21827..b2e05ce6b2ce246b5225b98eecc7779bf3b0cfa0 100644
--- a/ragconnect.base/src/main/resources/receiveDefinition.mustache
+++ b/ragconnect.base/src/main/resources/receiveDefinition.mustache
@@ -1,5 +1,5 @@
 {{#typeIsList}}
-{{^UseList}}
+{{#IndexBasedListAccess}}
 private int {{parentTypeName}}.{{resolveInListMethodName}}(String topic) {
   for (int index = 0; index < getNum{{entityName}}(); index++) {
     if (get{{entityName}}(index).get{{idTokenName}}().equals(topic)) {
@@ -8,105 +8,123 @@ private int {{parentTypeName}}.{{resolveInListMethodName}}(String topic) {
   }
   return -1;
 }
-{{/UseList}}
+{{/IndexBasedListAccess}}
 {{/typeIsList}}
 
 /**
- * Connects the receive endpoint {{entityName}}.
-{{#typeIsList}}{{#isWithAdd}}
+ * Connects the receive port {{entityName}}.
+{{#typeIsList}}{{#WithAdd}}
  * New values are appended to the end of the list.
-{{/isWithAdd}}{{/typeIsList}}
+{{/WithAdd}}{{/typeIsList}}
  * @param {{connectParameterName}} string describing protocol and path as an URI
-{{#typeIsList}}{{^UseList}}{{^isWithAdd}}
+{{#typeIsList}}{{#IndexBasedListAccess}}{{^WithAdd}}
  * @param index index of node in list to connect (the list is expected to have enough elements)
-{{/isWithAdd}}{{/UseList}}{{/typeIsList}}
+{{/WithAdd}}{{/IndexBasedListAccess}}{{/typeIsList}}
  * @return true if connect was successful, false otherwise
  * @throws java.io.IOException if connect failed
  */
-public boolean {{parentTypeName}}.{{connectMethod}}(String {{connectParameterName}}{{#typeIsList}}{{^UseList}}{{^isWithAdd}}, int index{{/isWithAdd}}{{/UseList}}{{/typeIsList}}) throws java.io.IOException {
+public boolean {{parentTypeName}}.{{connectMethodName}}(String {{connectParameterName}}{{#typeIsList}}{{#IndexBasedListAccess}}{{^WithAdd}}, int index{{/WithAdd}}{{/IndexBasedListAccess}}{{/typeIsList}}) throws java.io.IOException {
   java.util.function.BiConsumer<String, byte[]> consumer = (topic, message) -> {
     {{> mappingApplication}}
-{{#loggingEnabledForReads}}
-    System.out.println("[Receive] " + {{connectParameterName}} + " -> {{entityName}} = " + {{lastResult}});
-{{/loggingEnabledForReads}}
-{{#isTypeEndpointDefinition}}
+{{#configLoggingEnabledForReads}}
+    {{logDebug}}("[Receive] {{log_}} -> {{entityName}} = {{log_}}", {{connectParameterName}}, {{lastResult}});
+{{/configLoggingEnabledForReads}}
+{{#hasTypePortTarget}}
     {{lastResult}}.treeResolveAll();
  {{#typeIsList}}
-  {{#UseList}}
-   {{#isWithAdd}}
-    {{getterMethod}}().addAll({{lastResult}});
-   {{/isWithAdd}}
-   {{^isWithAdd}}
+  {{^IndexBasedListAccess}}
+   {{#WithAdd}}
+    {{getterMethodCall}}.addAll({{lastResult}});
+   {{/WithAdd}}
+   {{^WithAdd}}
     set{{entityName}}({{lastResult}});
-   {{/isWithAdd}}
-  {{/UseList}}
-  {{^UseList}}
+   {{/WithAdd}}
+  {{/IndexBasedListAccess}}
+  {{#IndexBasedListAccess}}
     {{lastResult}}.set{{idTokenName}}(topic);
-   {{#isWithAdd}}
-    {{getterMethod}}().add({{lastResult}});
-   {{/isWithAdd}}
-   {{^isWithAdd}}
+   {{#WithAdd}}
+    {{getterMethodCall}}.add({{lastResult}});
+   {{/WithAdd}}
+   {{^WithAdd}}
     set{{entityName}}({{lastResult}}, index);
-   {{/isWithAdd}}
-  {{/UseList}}
+   {{/WithAdd}}
+  {{/IndexBasedListAccess}}
  {{/typeIsList}}
  {{^typeIsList}}
     set{{entityName}}({{lastResult}});
  {{/typeIsList}}
-{{/isTypeEndpointDefinition}}
-{{^isTypeEndpointDefinition}}
+{{/hasTypePortTarget}}
+{{^hasTypePortTarget}}
     set{{entityName}}({{lastResult}});
-{{/isTypeEndpointDefinition}}
+{{/hasTypePortTarget}}
+{{#configEvaluationCounter}}
+    {{evaluationCounterVariable}}.incrementReceive("{{parentTypeName}}", "{{entityName}}");
+{{/configEvaluationCounter}}
   };
-  return {{internalConnectMethod}}({{connectParameterName}}, consumer);
+  return {{internalConnectMethodName}}({{connectParameterName}}, consumer);
 }
 
-{{#typeIsList}}{{^UseList}}{{^isWithAdd}}
+{{#typeIsList}}{{#IndexBasedListAccess}}{{^WithAdd}}
 /**
- * Connects the receive endpoint {{entityName}} using a "wildcard" URI (if supported by the chosen protocol).
+ * Connects the receive port {{entityName}} using a "wildcard" URI (if supported by the chosen protocol).
  * @param {{connectParameterName}} string describing protocol and path as an URI
  * @return true if connect was successful, false otherwise
  * @throws java.io.IOException if connect failed
 */
-public boolean {{parentTypeName}}.{{connectMethod}}(String {{connectParameterName}}) throws java.io.IOException {
+public boolean {{parentTypeName}}.{{connectMethodName}}(String {{connectParameterName}}) throws java.io.IOException {
   java.util.function.BiConsumer<String, byte[]> consumer = (topic, message) -> {
+    {{#configEvaluationCounter}}
+      {{evaluationCounterVariable}}.incrementReceive("{{parentTypeName}}", "{{entityName}}");
+    {{/configEvaluationCounter}}
+    int index = {{resolveInListMethodName}}(topic);
     {{> mappingApplication}}
-{{#loggingEnabledForReads}}
-    System.out.println("[Receive] " + {{connectParameterName}} + " (" + topic + ") -> {{entityName}} = " + {{lastResult}});
-{{/loggingEnabledForReads}}
+{{#configLoggingEnabledForReads}}
+    {{logDebug}}("[Receive] {{log_}} ({{log_}}) -> {{entityName}} = {{log_}}",
+      {{connectParameterName}}, topic, {{lastResult}});
+{{/configLoggingEnabledForReads}}
     {{lastResult}}.set{{idTokenName}}(topic);
-    int resolvedIndex = {{resolveInListMethodName}}(topic);
-    if (resolvedIndex == -1) {
+    if (index == -1) {
       add{{entityName}}({{lastResult}});
     } else {
-      set{{entityName}}({{lastResult}}, resolvedIndex);
+      set{{entityName}}({{lastResult}}, index);
     }
   };
-  return {{internalConnectMethod}}({{connectParameterName}}, consumer);
+  return {{internalConnectMethodName}}({{connectParameterName}}, consumer);
 }
-{{/isWithAdd}}{{/UseList}}{{/typeIsList}}
+{{/WithAdd}}{{/IndexBasedListAccess}}{{/typeIsList}}
 
-private boolean {{parentTypeName}}.{{internalConnectMethod}}(String {{connectParameterName}},
+private boolean {{parentTypeName}}.{{internalConnectMethodName}}(String {{connectParameterName}},
     java.util.function.BiConsumer<String, byte[]> consumer) throws java.io.IOException {
   {{>handleUri}}
   RagConnectToken connectToken = new RagConnectToken(uri, "{{entityName}}");
   boolean success;
   switch (scheme) {
-  {{#usesMqtt}}
+  {{#javaHandler}}
+  {{#InUse}}
+    case "java":
+      success = {{attributeName}}().registerCallback(connectToken, consumer);
+      break;
+  {{/InUse}}
+  {{/javaHandler}}
+  {{#mqttHandler}}
+  {{#InUse}}
     case "mqtt":
-      success = {{mqttHandlerAttribute}}().newConnection(connectToken, consumer);
+      success = {{attributeName}}().newConnection(connectToken, consumer);
       break;
-  {{/usesMqtt}}
-  {{#usesRest}}
+  {{/InUse}}
+  {{/mqttHandler}}
+  {{#restHandler}}
+  {{#InUse}}
     case "rest":
-      success = {{restHandlerAttribute}}().newPUTConnection(connectToken, input -> {
+      success = {{attributeName}}().newPUTConnection(connectToken, input -> {
         // TODO wildcard-topic not supported yet
         consumer.accept("", input.getBytes());
       });
       break;
-  {{/usesRest}}
+  {{/InUse}}
+  {{/restHandler}}
     default:
-      System.err.println("Unknown protocol '" + scheme + "'.");
+      {{logError}}("Unknown protocol '{{log_}}'.", scheme);
       success = false;
   }
   if (success) {
@@ -115,25 +133,37 @@ private boolean {{parentTypeName}}.{{internalConnectMethod}}(String {{connectPar
   return success;
 }
 
-public boolean {{parentTypeName}}.{{disconnectMethod}}(String {{connectParameterName}}) throws java.io.IOException {
+public boolean {{parentTypeName}}.{{disconnectMethodName}}(String {{connectParameterName}}) throws java.io.IOException {
   {{>handleUri}}
   java.util.List<RagConnectToken> connectTokens = connectTokenMap.removeAll(this, true, uri, "{{entityName}}");
   if (connectTokens.isEmpty()) {
-    System.err.println("Disconnect called without connection for receiving " + this + ".{{entityName}} to '" + {{connectParameterName}} + "'!");
+    {{logWarn}}("Disconnect called without connection for receiving {{log_}}.{{entityName}} to '{{log_}}'!",
+      this, {{connectParameterName}});
     return false;
   }
   RagConnectDisconnectHandlerMethod disconnectingMethod;
   switch (scheme) {
-  {{#usesMqtt}}
-    case "mqtt": disconnectingMethod = {{mqttHandlerAttribute}}()::disconnect;
+  {{#javaHandler}}
+  {{#InUse}}
+    case "java": disconnectingMethod = {{attributeName}}()::unregisterCallback;
+    break;
+  {{/InUse}}
+  {{/javaHandler}}
+  {{#mqttHandler}}
+  {{#InUse}}
+    case "mqtt": disconnectingMethod = {{attributeName}}()::disconnect;
     break;
-  {{/usesMqtt}}
-  {{#usesRest}}
-    case "rest": disconnectingMethod = {{restHandlerAttribute}}()::disconnect;
+  {{/InUse}}
+  {{/mqttHandler}}
+  {{#restHandler}}
+  {{#InUse}}
+    case "rest": disconnectingMethod = {{attributeName}}()::disconnect;
     break;
-  {{/usesRest}}
+  {{/InUse}}
+  {{/restHandler}}
     default:
-      System.err.println("Unknown protocol '" + scheme + "' in '" + {{connectParameterName}} + "' for disconnecting {{parentTypeName}}.{{entityName}}");
+      {{logError}}("Unknown protocol '{{log_}}' in '{{log_}}' for disconnecting {{parentTypeName}}.{{entityName}}",
+        scheme, {{connectParameterName}});
       return false;
   }
   boolean success = true;
diff --git a/ragconnect.base/src/main/resources/sendDefinition.mustache b/ragconnect.base/src/main/resources/sendDefinition.mustache
index 5cee6a483cda29519905918a505337db7779fce6..9c7dac4fc7a9bccc449fcdfced102c4ba865fbb2 100644
--- a/ragconnect.base/src/main/resources/sendDefinition.mustache
+++ b/ragconnect.base/src/main/resources/sendDefinition.mustache
@@ -1,76 +1,126 @@
-private RagConnectPublisher {{parentTypeName}}.{{sender}} = new RagConnectPublisher();
+private RagConnect{{#IndexBasedListAccess}}Mapping{{/IndexBasedListAccess}}Publisher {{parentTypeName}}.{{senderName}} = new RagConnect{{#IndexBasedListAccess}}Mapping{{/IndexBasedListAccess}}Publisher();
 
-public boolean {{parentTypeName}}.{{connectMethod}}(String {{connectParameterName}}, boolean writeCurrentValue) throws java.io.IOException {
+public boolean {{parentTypeName}}.{{connectMethodName}}(String {{connectParameterName}}{{#IndexBasedListAccess}}, int index{{/IndexBasedListAccess}}, boolean writeCurrentValue) throws java.io.IOException {
   {{>handleUri}}
   RagConnectToken connectToken = new RagConnectToken(uri, "{{entityName}}");
   boolean success;
   switch (scheme) {
-  {{#usesMqtt}}
+  {{#javaHandler}}
+  {{#InUse}}
+    case "java":
+    final JavaHandler handler = {{attributeName}}();
+
+    {{senderName}}.add(() -> {
+      handler.push(path, {{lastValueGetterCall}});
+    }{{#IndexBasedListAccess}}, index{{/IndexBasedListAccess}}, connectToken);
+    {{updateMethodName}}();
+    if (writeCurrentValue) {
+      {{writeMethodName}}({{#IndexBasedListAccess}}index, {{/IndexBasedListAccess}}connectToken);
+    }
+    success = true;
+    break;
+  {{/InUse}}
+  {{/javaHandler}}
+  {{#mqttHandler}}
+  {{#InUse}}
     case "mqtt":
-      final MqttHandler handler = {{mqttHandlerAttribute}}().resolveHandler(uri);
-      final String topic = {{mqttHandlerAttribute}}().extractTopic(uri);
-      {{sender}}.add(() -> {
-        {{#loggingEnabledForWrites}}
-        System.out.println("[Send] {{entityName}} = " + {{getterMethod}}() + " -> " + {{connectParameterName}});
-        {{/loggingEnabledForWrites}}
-        handler.publish(topic, {{lastValue}});
-        }, connectToken);
-      {{updateMethod}}();
+      final MqttHandler handler = {{attributeName}}().resolveHandler(uri);
+      final String topic = {{attributeName}}().extractTopic(uri);
+      {{senderName}}.add(() -> {
+        {{#configLoggingEnabledForWrites}}
+        {{!FIXME circular computation for collection attributes!!}}
+        {{logDebug}}("[Send] {{entityName}} = {{log_}} -> {{log_}}", {{getterMethodCall}}, {{connectParameterName}});
+        {{/configLoggingEnabledForWrites}}
+        if ({{lastValueGetterCall}} != null) {
+          handler.publish(topic, {{lastValueGetterCall}});
+        {{#configLoggingEnabledForWrites}}
+        } else {
+          {{logWarn}}("[Send] {{entityName}} -> {{log_}}: can't send null.", {{connectParameterName}});
+        {{/configLoggingEnabledForWrites}}
+        }
+        }{{#IndexBasedListAccess}}, index{{/IndexBasedListAccess}}, connectToken);
+      {{updateMethodName}}({{#IndexBasedListAccess}}index{{/IndexBasedListAccess}});
       if (writeCurrentValue) {
-        {{writeMethod}}();
+        {{writeMethodName}}({{#IndexBasedListAccess}}index, {{/IndexBasedListAccess}}connectToken);
       }
       success = true;
       break;
-  {{/usesMqtt}}
-  {{#usesRest}}
+  {{/InUse}}
+  {{/mqttHandler}}
+  {{#restHandler}}
+  {{#InUse}}
     case "rest":
-      success = {{restHandlerAttribute}}().newGETConnection(connectToken, () -> {
-        {{updateMethod}}();
-        return new String({{lastValue}});
+      success = {{attributeName}}().newGETConnection(connectToken, () -> {
+        {{updateMethodName}}({{#IndexBasedListAccess}}index{{/IndexBasedListAccess}});
+        return new String({{lastValueGetterCall}});
       });
       break;
-  {{/usesRest}}
+  {{/InUse}}
+  {{/restHandler}}
     default:
-      System.err.println("Unknown protocol '" + scheme + "'.");
+      {{logError}}("Unknown protocol '{{log_}}'.", scheme);
       success = false;
   }
   if (success) {
     connectTokenMap.add(this, false, connectToken);
-    {{#incrementalOptionActive}}
-    _ragConnectObserver().add(connectToken, this, "{{getterMethod}}", () -> {
-      if (this.{{updateMethod}}()) {
-        this.{{writeMethod}}();
-      }
-    });
-    {{/incrementalOptionActive}}
+    {{#configIncrementalOptionActive}}
+    {{#sendIncrementalObserverEntries}}
+    {{observerInstanceSingletonMethodName}}().add(
+      connectToken,
+      this,
+      {{CompareParams}},
+      {{Params}},
+      () -> {
+        if (this.{{updateMethodName}}({{#IndexBasedListAccess}}index{{/IndexBasedListAccess}})) {
+          this.{{writeMethodName}}({{#IndexBasedListAccess}}index{{/IndexBasedListAccess}});
+        }
+      },
+      "{{AttributeString}}"
+    );
+    {{/sendIncrementalObserverEntries}}
+    {{/configIncrementalOptionActive}}
   }
   return success;
 }
 
-public boolean {{parentTypeName}}.{{disconnectMethod}}(String {{connectParameterName}}) throws java.io.IOException {
+{{!todo check if index parameter is needed here for typeIsList and indexBasedListAccess}}
+public boolean {{parentTypeName}}.{{disconnectMethodName}}(String {{connectParameterName}}) throws java.io.IOException {
   {{>handleUri}}
   java.util.List<RagConnectToken> connectTokens = connectTokenMap.removeAll(this, false, uri, "{{entityName}}");
   if (connectTokens.isEmpty()) {
-    System.err.println("Disconnect called without connection for sending " + this + ".{{entityName}} to '" + {{connectParameterName}} + "'!");
+    {{logWarn}}("Disconnect called without connection for sending {{log_}}.{{entityName}} to '{{log_}}'!",
+      this, {{connectParameterName}});
     return false;
   }
-  {{#incrementalOptionActive}}
-  connectTokens.forEach(token -> _ragConnectObserver().remove(token));
-  {{/incrementalOptionActive}}
+  {{#configIncrementalOptionActive}}
+  connectTokens.forEach(token -> {{observerInstanceSingletonMethodName}}().remove(token));
+  {{/configIncrementalOptionActive}}
   RagConnectDisconnectHandlerMethod disconnectingMethod;
   switch (scheme) {
-  {{#usesMqtt}}
+  {{#javaHandler}}
+  {{#InUse}}
+    case "java":
+      disconnectingMethod = {{senderName}}::remove;
+    break;
+  {{/InUse}}
+  {{/javaHandler}}
+  {{#mqttHandler}}
+  {{#InUse}}
     case "mqtt":
-      disconnectingMethod = {{sender}}::remove;
+      disconnectingMethod = {{senderName}}::remove;
       break;
-  {{/usesMqtt}}
-  {{#usesRest}}
+  {{/InUse}}
+  {{/mqttHandler}}
+  {{#restHandler}}
+  {{#InUse}}
     case "rest":
-      disconnectingMethod = {{restHandlerAttribute}}()::disconnect;
+      disconnectingMethod = {{attributeName}}()::disconnect;
       break;
-  {{/usesRest}}
+  {{/InUse}}
+  {{/restHandler}}
     default:
-      System.err.println("Unknown protocol '" + scheme + "' in '" + {{connectParameterName}} + "' for disconnecting {{parentTypeName}}.{{entityName}}");
+      {{logError}}("Unknown protocol '{{log_}}' in '{{log_}}' for disconnecting {{parentTypeName}}.{{entityName}}",
+        scheme, {{connectParameterName}});
       return false;
   }
   boolean success = true;
@@ -80,16 +130,50 @@ public boolean {{parentTypeName}}.{{disconnectMethod}}(String {{connectParameter
   return success;
 }
 
-protected boolean {{parentTypeName}}.{{updateMethod}}() {
-  {{^shouldSendValue}}
-  {{tokenResetMethod}}();
-  {{/shouldSendValue}}
+protected boolean {{parentTypeName}}.{{updateMethodName}}({{#IndexBasedListAccess}}int index{{/IndexBasedListAccess}}) {
+  {{^shouldNotResetValue}}
+  {{tokenResetMethodName}}();
+  {{/shouldNotResetValue}}
   {{> mappingApplication}}
-  {{lastValue}} = {{lastResult}};
-  // normally we would return true here. unless no connect method was called so far to initialize {{sender}} yet
-  return {{sender}} != null;
+  {{lastValueSetter}}({{#IndexBasedListAccess}}index, {{/IndexBasedListAccess}}{{lastResult}});
+  // normally we would return true here. unless no connect method was called so far to initialize {{senderName}} yet
+  {{#configEvaluationCounter}}
+    if ({{senderName}} == null) {
+      {{evaluationCounterVariable}}.incrementSkip("{{parentTypeName}}", "{{entityName}}");
+    }
+  {{/configEvaluationCounter}}
+  return {{senderName}} != null;
+}
+
+protected void {{parentTypeName}}.{{writeMethodName}}({{#IndexBasedListAccess}}int index{{/IndexBasedListAccess}}) {
+  {{#configEvaluationCounter}}
+    {{evaluationCounterVariable}}.incrementSend("{{parentTypeName}}", "{{entityName}}");
+  {{/configEvaluationCounter}}
+  {{senderName}}.run({{#IndexBasedListAccess}}index{{/IndexBasedListAccess}});
 }
 
-protected void {{parentTypeName}}.{{writeMethod}}() {
-  {{sender}}.run();
+protected void {{parentTypeName}}.{{writeMethodName}}({{#IndexBasedListAccess}}int index, {{/IndexBasedListAccess}}RagConnectToken token) {
+  {{#configEvaluationCounter}}
+    {{evaluationCounterVariable}}.incrementSend("{{parentTypeName}}", "{{entityName}}");
+  {{/configEvaluationCounter}}
+  {{senderName}}.run({{#IndexBasedListAccess}}index, {{/IndexBasedListAccess}}token);
+}
+
+{{#needForwarding}}
+syn {{{forwardingType}}} {{parentTypeName}}.{{forwardingName}}({{#IndexBasedListAccess}}int index{{/IndexBasedListAccess}}) {
+{{#relationPortWithListRole}}
+//  for (var element : {{realGetterMethodCall}}) {
+//    element.{{touchedTerminalsMethodName}}();
+//  }
+  {{realGetterMethodCall}}.stream().forEach(element -> element.{{touchedTerminalsMethodName}}());
+  return {{realGetterMethodCall}};
+{{/relationPortWithListRole}}
+{{^relationPortWithListRole}}
+  {{{forwardingType}}} result = {{realGetterMethodCall}};
+  if (result == null) {
+    return null;
+  }
+  return result.{{touchedTerminalsMethodName}}();
+{{/relationPortWithListRole}}
 }
+{{/needForwarding}}
diff --git a/ragconnect.base/src/main/resources/tokenComponent.mustache b/ragconnect.base/src/main/resources/tokenComponent.mustache
index 169f5f64fff01ef2c34abeea34266b5a82deaf3a..12238f4428bf2ef79951015425cf45285cca624b 100644
--- a/ragconnect.base/src/main/resources/tokenComponent.mustache
+++ b/ragconnect.base/src/main/resources/tokenComponent.mustache
@@ -1,22 +1,22 @@
-public {{parentTypeName}} {{parentTypeName}}.set{{name}}({{javaType}} value) {
+public {{parentTypeName}} {{parentTypeName}}.set{{Name}}({{javaType}} value) {
   set{{internalName}}(value);
-  {{#DependencyDefinitions}}
+  {{#DependencySourceDefinitions}}
   for ({{targetParentTypeName}} target : get{{internalRelationPrefix}}TargetList()) {
-    {{#targetEndpointDefinition}}
-    if (target.{{updateMethod}}()) {
-      target.{{writeMethod}}();
+    {{#targetPortDefinition}}
+    if (target.{{updateMethodName}}()) {
+      target.{{writeMethodName}}();
     }
-    {{/targetEndpointDefinition}}
+    {{/targetPortDefinition}}
   }
-  {{/DependencyDefinitions}}
+  {{/DependencySourceDefinitions}}
   {{#normalTokenSendDef}}
-  if ({{updateMethod}}()) {
-    {{writeMethod}}();
+  if ({{updateMethodName}}()) {
+    {{writeMethodName}}();
   }
   {{/normalTokenSendDef}}
   return this;
 }
 
-public {{javaType}} {{parentTypeName}}.get{{name}}() {
+public {{javaType}} {{parentTypeName}}.get{{Name}}() {
   return get{{internalName}}();
 }
diff --git a/ragconnect.base/src/main/resources/typeDecl.mustache b/ragconnect.base/src/main/resources/typeDecl.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..95cb3ee84d3df8f86111efbbfcc2bf61a06dab6e
--- /dev/null
+++ b/ragconnect.base/src/main/resources/typeDecl.mustache
@@ -0,0 +1,45 @@
+uncache {{Name}}._ragconnect_myContext();
+inh String {{Name}}._ragconnect_myContext();
+inh ASTNode {{Name}}._ragconnect_myParent();
+uncache {{Name}}._ragconnect_myIndexInList();
+inh int {{Name}}._ragconnect_myIndexInList();
+{{#occurencesInProductionRules}}
+  eq {{parentTypeName}}.get{{Name}}()._ragconnect_myContext() = "{{parentTypeName}}.{{Name}}";
+  eq {{parentTypeName}}.get{{Name}}()._ragconnect_myParent() = this;
+  {{^isListComponent}}
+  eq {{parentTypeName}}.get{{Name}}()._ragconnect_myIndexInList() = -1;
+  {{/isListComponent}}
+  {{#isListComponent}}
+  eq {{parentTypeName}}.get{{Name}}(int i)._ragconnect_myIndexInList() = i;
+  {{/isListComponent}}
+{{/occurencesInProductionRules}}
+
+{{#ContextFreeTypePortTargets}}{{#containingPortDefinition}}
+public boolean {{Name}}.{{connectMethodName}}(String {{connectParameterName}}{{#Send}}, boolean writeCurrentValue{{/Send}}) throws java.io.IOException {
+  switch (_ragconnect_myContext()) {
+  {{#occurencesInProductionRules}}
+    {{!TODO only using "connectMethodName" is not correct, since the actual name might be different, e.g., if both send and receive are defined. need a reference to the actual port-definition here}}
+    {{^isListComponent}}
+    case "{{parentTypeName}}.{{Name}}": return (({{parentTypeName}}) _ragconnect_myParent()).{{connectMethodName}}{{Name}}({{connectParameterName}}{{#Send}}, writeCurrentValue{{/Send}});
+    {{/isListComponent}}
+    {{#isListComponent}}
+    case "{{parentTypeName}}.{{Name}}": return (({{parentTypeName}}) _ragconnect_myParent()).{{connectMethodName}}{{Name}}({{connectParameterName}}, _ragconnect_myIndexInList(){{#Send}}, writeCurrentValue{{/Send}});
+    {{/isListComponent}}
+  {{/occurencesInProductionRules}}
+    default:
+      {{logError}}("No matching context while connecting {{log_}} to {{log_}}", this, {{connectParameterName}});
+      return false;
+  }
+}
+
+public boolean {{Name}}.{{disconnectMethodName}}(String {{connectParameterName}}) throws java.io.IOException {
+  switch (_ragconnect_myContext()) {
+{{#occurencesInProductionRules}}
+  case "{{parentTypeName}}.{{Name}}": return (({{parentTypeName}}) _ragconnect_myParent()).{{disconnectMethodName}}({{connectParameterName}});
+{{/occurencesInProductionRules}}
+    default:
+      {{logError}}("No matching context while disconnecting {{log_}} to {{log_}}", this, {{connectParameterName}});
+      return false;
+  }
+}
+{{/containingPortDefinition}}{{/ContextFreeTypePortTargets}}
diff --git a/ragconnect.tests/.gitignore b/ragconnect.tests/.gitignore
index 87b4cdd3d7c6a41502ca98703abeeb69a1d536fb..31993cebfe5fdbd5c2727c631ebfec8394963232 100644
--- a/ragconnect.tests/.gitignore
+++ b/ragconnect.tests/.gitignore
@@ -3,3 +3,4 @@ src/gen-res/
 src/gen/
 out/
 *.class
+test.log
diff --git a/ragconnect.tests/build.gradle b/ragconnect.tests/build.gradle
index ee6da58c40c1bb690258d3958ac53a6ec44ac362..c5e6f13b574d6e93ee635a265c6f0cfe9b718793 100644
--- a/ragconnect.tests/build.gradle
+++ b/ragconnect.tests/build.gradle
@@ -1,7 +1,7 @@
+// --- Buildscripts (must be at the top) ---
 buildscript {
     repositories {
         mavenCentral()
-//        mavenLocal()
         maven {
             name "gitlab-maven"
             url "https://git-st.inf.tu-dresden.de/api/v4/groups/jastadd/-/packages/maven"
@@ -9,39 +9,51 @@ buildscript {
     }
     dependencies {
         classpath 'org.jastadd:jastaddgradle:1.13.3'
-        classpath 'org.jastadd.preprocessor:testing:0.2.10'
+        classpath 'org.jastadd.preprocessor:testing:0.3.0-37'
     }
 }
 
+// --- Plugin definitions ---
 import org.jastadd.preprocessor.testing.plugin.PreprocessorPlugin
 import org.jastadd.preprocessor.testing.plugin.RagConnectTest
 
 plugins {
+    id 'com.github.ben-manes.versions'
     id 'java'
-    id 'java-library'
     id 'idea'
-    id 'com.github.ben-manes.versions' version '0.36.0'
+    id 'org.jastadd'
+    id 'java-library'
     id 'com.google.protobuf' version "0.8.14"
 }
 
-apply plugin: 'jastadd'
 apply plugin: PreprocessorPlugin
 
-group = 'de.tudresden.inf.st'
-
+// --- Dependencies ---
 repositories {
     mavenCentral()
+    maven {
+        name "gitlab-maven"
+        url "https://git-st.inf.tu-dresden.de/api/v4/groups/jastadd/-/packages/maven"
+    }
+}
+
+configurations {
+    ragconnect
+    relast
 }
 
 dependencies {
-    implementation project(':ragconnect.base')
+    ragconnect project(':ragconnect.base')
+    testImplementation project(':ragconnect.base')
 
-    runtimeOnly group: 'org.jastadd', name: 'jastadd', version: '2.3.5'
-//    runtimeOnly fileTree(include: ['jastadd2.jar'], dir: '../libs')
+    implementation group: 'org.jastadd', name: 'jastadd2', version: '2.3.5-dresden-7'
+    relast group: 'org.jastadd', name: 'relast', version: "${relast_version}"
 
-    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'
-    testImplementation group: 'org.assertj', name: 'assertj-core', version: '3.12.1'
+    testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.9.0'
+    testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.9.0'
+    testImplementation group: 'org.assertj', name: 'assertj-core', version: '3.23.1'
+    testImplementation group: 'org.awaitility', name: 'awaitility', version: '4.1.1'
+    testImplementation group: 'io.github.artsok', name: 'rerunner-jupiter', version: '2.1.6'
 
     // jackson (for serialization of types)
     implementation group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.12.1'
@@ -52,22 +64,28 @@ dependencies {
 
     // rest and client
     testImplementation group: 'com.sparkjava', name: 'spark-core', version: '2.9.3'
-    testImplementation group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: '2.11.2'
+    testImplementation group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: "${log4j_version}"
     testImplementation group: 'org.glassfish.jersey.core', name: 'jersey-client', version: '2.31'
     testImplementation group: 'org.glassfish.jersey.inject', name: 'jersey-hk2', version: '2.31'
     testImplementation group: 'javax.activation', name: 'activation', version: '1.1.1'
 
     testImplementation group: 'net.sf.beaver', name: 'beaver-rt', version: '0.9.11'
     api group: 'com.google.protobuf', name: 'protobuf-java', version: '3.0.0'
+
+    implementation group: 'de.tudresden.inf.st', name: 'dumpAst', version: "${dumpAst_version}"
 }
 
-//task helper {
-//    doLast {
-//        println(defaultOnlyRead.inputs.files.files)
-//        println(defaultOnlyRead.outputs.files.files)
-//    }
-//}
+// --- Preprocessors ---
+File genSrc = file("src/test/java-gen")
+sourceSets.test.java.srcDir genSrc
+idea.module.generatedSourceDirs += genSrc
+
+preprocessorTesting {
+    relastCompilerConfiguration = configurations.relast
+    ragconnectCompilerConfiguration = configurations.ragconnect
+}
 
+// --- Tests ---
 test {
     useJUnitPlatform {
         excludeTags 'mqtt'
@@ -76,13 +94,6 @@ test {
     maxHeapSize = '1G'
 }
 
-protobuf {
-    protoc {
-        // The artifact spec for the Protobuf Compiler
-        artifact = 'com.google.protobuf:protoc:3.0.0'
-    }
-}
-
 task allTests(type: Test, dependsOn: testClasses) {
     description = 'Run every test'
     group = 'verification'
@@ -96,22 +107,21 @@ task specificTest(type: Test, dependsOn: testClasses) {
     description = 'Run test tagged with tag given by "-PincludeTags="'
     group = 'verification'
     String tags = project.hasProperty("includeTags") ?
-                   project.property("includeTags") : ''
+            project.property("includeTags") : ''
 
     useJUnitPlatform {
         includeTags tags
     }
 }
 
-preprocessorTesting {
-    //noinspection GroovyAssignabilityCheck
-    relastCompilerLocation = '../libs/relast.jar'
-//    ragconnectCompilerLocation = '../libs/ragconnect.jar'
-}
+task newTests(type: Test, dependsOn: testClasses) {
+    description = 'Run test tagged with tag "New"'
+    group = 'verification'
 
-File genSrc = file("src/test/java-gen")
-sourceSets.test.java.srcDir genSrc
-idea.module.generatedSourceDirs += genSrc
+    useJUnitPlatform {
+        includeTags 'New'
+    }
+}
 
 clean {
     delete fileTree(dir: 'src/test/02-after-ragconnect/', exclude: '.gitignore')
@@ -119,6 +129,15 @@ clean {
     delete fileTree(dir: 'src/test/java-gen/', exclude: '.gitignore')
 }
 
+def JASTADD_INCREMENTAL_OPTIONS = ['--tracing=cache,flush',
+                               '--incremental=param',
+                               '--cache=all',
+                               '--rewrite=cnta',
+                               '--flush=full']
+def JASTADD_INCREMENTAL_OPTIONS_TRACING_FULL = JASTADD_INCREMENTAL_OPTIONS.clone()
+JASTADD_INCREMENTAL_OPTIONS_TRACING_FULL.set(0, '--tracing=all')
+JASTADD_INCREMENTAL_OPTIONS_TRACING_FULL.set(1, '--incremental=param,debug')
+
 // --- Test: Example ---
 task compileExampleTest(type: RagConnectTest) {
     ragconnect {
@@ -126,6 +145,7 @@ task compileExampleTest(type: RagConnectTest) {
         inputFiles = [file('src/test/01-input/example/Test.relast'),
                       file('src/test/01-input/example/Test.connect')]
         rootNode = 'Model'
+        extraOptions = defaultRagConnectOptionsAnd()
     }
     relast {
         useJastAddNames = true
@@ -145,6 +165,7 @@ task compileDefaultOnlyRead(type: RagConnectTest) {
         inputFiles = [file('src/test/01-input/defaultOnlyRead/Test.relast'),
                       file('src/test/01-input/defaultOnlyRead/Test.connect')]
         rootNode = 'A'
+        extraOptions = defaultRagConnectOptionsAnd()
     }
     relast {
         useJastAddNames = true
@@ -163,6 +184,7 @@ task compileDefaultOnlyWrite(type: RagConnectTest) {
         inputFiles = [file('src/test/01-input/defaultOnlyWrite/Test.relast'),
                       file('src/test/01-input/defaultOnlyWrite/Test.connect')]
         rootNode = 'A'
+        extraOptions = defaultRagConnectOptionsAnd()
     }
     relast {
         useJastAddNames = true
@@ -182,6 +204,7 @@ task compileRead1write2(type: RagConnectTest) {
         inputFiles = [file('src/test/01-input/read1write2/Test.relast'),
                       file('src/test/01-input/read1write2/Test.connect')]
         rootNode = 'A'
+        extraOptions = defaultRagConnectOptionsAnd()
     }
     relast {
         useJastAddNames = true
@@ -201,6 +224,7 @@ task compileRead2write1(type: RagConnectTest) {
         inputFiles = [file('src/test/01-input/read2write1/Test.relast'),
                       file('src/test/01-input/read2write1/Test.connect')]
         rootNode = 'A'
+        extraOptions = defaultRagConnectOptionsAnd()
     }
     relast {
         useJastAddNames = true
@@ -221,6 +245,7 @@ task compileVia(type: RagConnectTest) {
                       file('src/test/01-input/via/Test.connect')]
         rootNode = 'A'
         protocols = ['mqtt', 'rest']
+        extraOptions = defaultRagConnectOptionsAnd()
     }
     relast {
         useJastAddNames = true
@@ -233,13 +258,15 @@ task compileVia(type: RagConnectTest) {
     }
 }
 
-// --- Test: token-value-send ---
-task compileTokenValueSend(type: RagConnectTest) {
+// --- Test: token-value-send-manual ---
+task compileTokenValueSendManual(type: RagConnectTest) {
     ragconnect {
         outputDir = file('src/test/02-after-ragconnect/tokenValueSend')
         inputFiles = [file('src/test/01-input/tokenValueSend/Test.relast'),
-                      file('src/test/01-input/tokenValueSend/Test.connect')]
+                      file('src/test/01-input/tokenValueSend/Test.connect'),
+                      file('src/test/01-input/tokenValueSend/TestDependencies.connect')]
         rootNode = 'A'
+        extraOptions = defaultRagConnectOptionsAnd()
     }
     relast {
         useJastAddNames = true
@@ -252,6 +279,27 @@ task compileTokenValueSend(type: RagConnectTest) {
     }
 }
 
+// --- Test: token-value-send-incremental ---
+task compileTokenValueSendIncremental(type: RagConnectTest) {
+    ragconnect {
+        outputDir = file('src/test/02-after-ragconnect/tokenValueSendInc')
+        inputFiles = [file('src/test/01-input/tokenValueSend/Test.relast'),
+                      file('src/test/01-input/tokenValueSend/Test.connect')]
+        rootNode = 'A'
+        extraOptions = defaultRagConnectOptionsAnd()
+    }
+    relast {
+        useJastAddNames = true
+        grammarName = 'src/test/03-after-relast/tokenValueSend/tokenValueSendInc'
+    }
+    jastadd {
+        jastAddList = 'JastAddList'
+        packageName = 'tokenValueSendInc.ast'
+        inputFiles = [file('src/test/01-input/tokenValueSend/Test.jadd')]
+        extraOptions = JASTADD_INCREMENTAL_OPTIONS_TRACING_FULL
+    }
+}
+
 // --- Test: tutorial ---
 task compileTutorial(type: RagConnectTest) {
     ragconnect {
@@ -259,6 +307,7 @@ task compileTutorial(type: RagConnectTest) {
         inputFiles = [file('src/test/01-input/tutorial/Test.relast'),
                       file('src/test/01-input/tutorial/Test.connect')]
         rootNode = 'A'
+        extraOptions = defaultRagConnectOptionsAnd()
     }
     relast {
         useJastAddNames = true
@@ -278,7 +327,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
+        extraOptions = defaultRagConnectOptionsAnd()
     }
     relast {
         useJastAddNames = true
@@ -288,11 +337,7 @@ task compileIncremental(type: RagConnectTest) {
         jastAddList = 'JastAddList'
         packageName = 'incremental.ast'
         inputFiles = [file('src/test/01-input/incremental/Test.jadd')]
-        extraOptions = ['--tracing=cache,flush',
-                        '--incremental=param',
-                        '--cache=all',
-                        '--rewrite=cnta',
-                        '--flush=full']
+        extraOptions = JASTADD_INCREMENTAL_OPTIONS
     }
 }
 
@@ -303,9 +348,7 @@ task compileMapping(type: RagConnectTest) {
         inputFiles = [file('src/test/01-input/mapping/Test.relast'),
                       file('src/test/01-input/mapping/Test.connect')]
         rootNode = 'A'
-        logReads = true
-        logWrites = true
-        verbose = true
+        extraOptions = defaultRagConnectOptionsAnd()
     }
     relast {
         useJastAddNames = true
@@ -319,13 +362,14 @@ task compileMapping(type: RagConnectTest) {
 }
 
 // --- Test: tree-manual ---
-task compileTreeManual(type: RagConnectTest, dependsOn: ':ragconnect.base:compileJava') {
+task compileTreeManual(type: RagConnectTest) {
     ragconnect {
         outputDir = file('src/test/02-after-ragconnect/tree')
         inputFiles = [file('src/test/01-input/tree/Test.relast'),
                       file('src/test/01-input/tree/Test.connect'),
                       file('src/test/01-input/tree/TestDependencies.connect')]
         rootNode = 'Root'
+        extraOptions = defaultRagConnectOptionsAnd()
     }
     relast {
         useJastAddNames = true
@@ -346,7 +390,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
+        extraOptions = defaultRagConnectOptionsAnd()
     }
     relast {
         useJastAddNames = true
@@ -357,11 +401,7 @@ task compileTreeIncremental(type: RagConnectTest) {
         jastAddList = 'JastAddList'
         packageName = 'treeInc.ast'
         inputFiles = [file('src/test/01-input/tree/Test.jadd')]
-        extraOptions = ['--tracing=cache,flush',
-                        '--incremental=param',
-                        '--cache=all',
-                        '--rewrite=cnta',
-                        '--flush=full']
+        extraOptions = JASTADD_INCREMENTAL_OPTIONS
     }
 }
 
@@ -373,6 +413,7 @@ task compileTreeAllowedTokens(type: RagConnectTest) {
                       file('src/test/01-input/treeAllowedTokens/Test.connect'),
                       file('src/test/01-input/treeAllowedTokens/TestDependencies.connect')]
         rootNode = 'Root'
+        extraOptions = defaultRagConnectOptionsAnd()
     }
     relast {
         useJastAddNames = true
@@ -393,7 +434,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
+        extraOptions = defaultRagConnectOptionsAnd()
     }
     relast {
         useJastAddNames = true
@@ -404,22 +445,19 @@ task compileTreeAllowedTokensIncremental(type: RagConnectTest) {
         jastAddList = 'JastAddList'
         packageName = 'treeAllowedTokensInc.ast'
         inputFiles = [file('src/test/01-input/treeAllowedTokens/Test.jadd')]
-        extraOptions = ['--tracing=cache,flush',
-                        '--incremental=param',
-                        '--cache=all',
-                        '--rewrite=cnta',
-                        '--flush=full']
+        extraOptions = JASTADD_INCREMENTAL_OPTIONS
     }
 }
 
 // --- Test: list-manual ---
-task compileListManual(type: RagConnectTest, dependsOn: ':ragconnect.base:jar') {
+task compileListManual(type: RagConnectTest) {
     ragconnect {
         outputDir = file('src/test/02-after-ragconnect/list')
         inputFiles = [file('src/test/01-input/list/Test.relast'),
                       file('src/test/01-input/list/Test.connect'),
                       file('src/test/01-input/list/TestDependencies.connect')]
         rootNode = 'Root'
+        extraOptions = defaultRagConnectOptionsAnd()
     }
     relast {
         useJastAddNames = true
@@ -434,12 +472,13 @@ task compileListManual(type: RagConnectTest, dependsOn: ':ragconnect.base:jar')
 }
 
 // --- Test: list-incremental ---
-task compileListIncremental(type: RagConnectTest, dependsOn: ':ragconnect.base:jar') {
+task compileListIncremental(type: RagConnectTest) {
     ragconnect {
         outputDir = file('src/test/02-after-ragconnect/listInc')
         inputFiles = [file('src/test/01-input/list/Test.relast'),
                       file('src/test/01-input/list/Test.connect')]
         rootNode = 'Root'
+        extraOptions = defaultRagConnectOptionsAnd()
     }
     relast {
         useJastAddNames = true
@@ -450,22 +489,19 @@ task compileListIncremental(type: RagConnectTest, dependsOn: ':ragconnect.base:j
         jastAddList = 'JastAddList'
         packageName = 'listInc.ast'
         inputFiles = [file('src/test/01-input/list/Test.jadd')]
-        extraOptions = ['--tracing=cache,flush',
-                        '--incremental=param',
-                        '--cache=all',
-                        '--rewrite=cnta',
-                        '--flush=full']
+        extraOptions = JASTADD_INCREMENTAL_OPTIONS
     }
 }
 
 // --- Test: singleList-manual ---
-task compileSingleListManual(type: RagConnectTest, dependsOn: ':ragconnect.base:jar') {
+task compileSingleListManual(type: RagConnectTest) {
     ragconnect {
         outputDir = file('src/test/02-after-ragconnect/singleList')
         inputFiles = [file('src/test/01-input/singleList/Test.relast'),
                       file('src/test/01-input/singleList/Test.connect'),
                       file('src/test/01-input/singleList/TestDependencies.connect')]
         rootNode = 'Root'
+        extraOptions = defaultRagConnectOptionsAnd()
     }
     relast {
         useJastAddNames = true
@@ -480,12 +516,13 @@ task compileSingleListManual(type: RagConnectTest, dependsOn: ':ragconnect.base:
 }
 
 // --- Test: singleList-incremental ---
-task compileSingleListIncremental(type: RagConnectTest, dependsOn: ':ragconnect.base:jar') {
+task compileSingleListIncremental(type: RagConnectTest) {
     ragconnect {
         outputDir = file('src/test/02-after-ragconnect/singleListInc')
         inputFiles = [file('src/test/01-input/singleList/Test.relast'),
                       file('src/test/01-input/singleList/Test.connect')]
         rootNode = 'Root'
+        extraOptions = defaultRagConnectOptionsAnd()
     }
     relast {
         useJastAddNames = true
@@ -496,22 +533,19 @@ task compileSingleListIncremental(type: RagConnectTest, dependsOn: ':ragconnect.
         jastAddList = 'JastAddList'
         packageName = 'singleListInc.ast'
         inputFiles = [file('src/test/01-input/singleList/Test.jadd')]
-        extraOptions = ['--tracing=cache,flush',
-                        '--incremental=param',
-                        '--cache=all',
-                        '--rewrite=cnta',
-                        '--flush=full']
+        extraOptions = JASTADD_INCREMENTAL_OPTIONS
     }
 }
 
 // --- Test: singleListVariant-manual ---
-task compileSingleListVariantManual(type: RagConnectTest, dependsOn: ':ragconnect.base:jar') {
+task compileSingleListVariantManual(type: RagConnectTest) {
     ragconnect {
         outputDir = file('src/test/02-after-ragconnect/singleListVariant')
         inputFiles = [file('src/test/01-input/singleListVariant/Test.relast'),
                       file('src/test/01-input/singleListVariant/Test.connect'),
                       file('src/test/01-input/singleListVariant/TestDependencies.connect')]
         rootNode = 'Root'
+        extraOptions = defaultRagConnectOptionsAnd()
     }
     relast {
         useJastAddNames = true
@@ -526,12 +560,13 @@ task compileSingleListVariantManual(type: RagConnectTest, dependsOn: ':ragconnec
 }
 
 // --- Test: singleListVariant-incremental ---
-task compileSingleListVariantIncremental(type: RagConnectTest, dependsOn: ':ragconnect.base:jar') {
+task compileSingleListVariantIncremental(type: RagConnectTest) {
     ragconnect {
         outputDir = file('src/test/02-after-ragconnect/singleListVariantInc')
         inputFiles = [file('src/test/01-input/singleListVariant/Test.relast'),
                       file('src/test/01-input/singleListVariant/Test.connect')]
         rootNode = 'Root'
+        extraOptions = defaultRagConnectOptionsAnd(['--experimental-jastadd-329'])
     }
     relast {
         useJastAddNames = true
@@ -542,29 +577,160 @@ task compileSingleListVariantIncremental(type: RagConnectTest, dependsOn: ':ragc
         jastAddList = 'JastAddList'
         packageName = 'singleListVariantInc.ast'
         inputFiles = [file('src/test/01-input/singleListVariant/Test.jadd')]
-        extraOptions = ['--tracing=cache,flush',
-                        '--incremental=param',
-                        '--cache=all',
-                        '--rewrite=cnta',
-                        '--flush=full']
-    }
-}
-
-task cleanCurrentManualTest(type: Delete) {
-//    delete "src/test/02-after-ragconnect/singleListVariant"
-//    delete "src/test/03-after-relast/singleListVariant"
-//    delete "src/test/java-gen/singleListVariant/ast"
-    delete "src/test/02-after-ragconnect/singleList"
-    delete "src/test/03-after-relast/singleList"
-    delete "src/test/java-gen/singleList/ast"
-}
-task cleanCurrentIncrementalTest(type: Delete) {
-//    delete "src/test/02-after-ragconnect/singleListVariantInc"
-//    delete "src/test/03-after-relast/singleListVariantInc"
-//    delete "src/test/java-gen/singleListVariantInc/ast"
-    delete "src/test/02-after-ragconnect/singleListInc"
-    delete "src/test/03-after-relast/singleListInc"
-    delete "src/test/java-gen/singleListInc/ast"
-}
-compileSingleListManual.dependsOn cleanCurrentManualTest
-compileSingleListIncremental.dependsOn cleanCurrentIncrementalTest
+        extraOptions = JASTADD_INCREMENTAL_OPTIONS
+    }
+}
+
+// --- Test: contextFreeSimple-incremental ---
+task compileContextFreeSimpleIncremental(type: RagConnectTest) {
+    ragconnect {
+        outputDir = file('src/test/02-after-ragconnect/contextFreeSimpleInc')
+        inputFiles = [file('src/test/01-input/contextFreeSimple/Test.relast'),
+                      file('src/test/01-input/contextFreeSimple/Test.connect')]
+        rootNode = 'Root'
+        extraOptions = defaultRagConnectOptionsAnd(['--experimental-jastadd-329'])
+    }
+    relast {
+        useJastAddNames = true
+        grammarName = 'src/test/03-after-relast/contextFreeSimpleInc/contextFreeSimpleInc'
+        serializer = 'jackson'
+    }
+    jastadd {
+        jastAddList = 'JastAddList'
+        packageName = 'contextFreeSimpleInc.ast'
+        extraOptions = JASTADD_INCREMENTAL_OPTIONS
+    }
+}
+
+// --- Test: forwarding-incremental ---
+task compileForwardingIncremental(type: RagConnectTest) {
+    ragconnect {
+        outputDir = file('src/test/02-after-ragconnect/forwardingInc')
+        inputFiles = [file('src/test/01-input/forwarding/Test.relast'),
+                      file('src/test/01-input/forwarding/Test.connect')]
+        rootNode = 'Root'
+        extraOptions = defaultRagConnectOptionsAnd(['--experimental-jastadd-329'])
+    }
+    relast {
+        useJastAddNames = true
+        grammarName = 'src/test/03-after-relast/forwardingInc/forwardingInc'
+        serializer = 'jackson'
+    }
+    jastadd {
+        jastAddList = 'JastAddList'
+        packageName = 'forwardingInc.ast'
+        extraOptions = JASTADD_INCREMENTAL_OPTIONS
+    }
+}
+
+// --- Test: indexed-send-incremental ---
+task compileIndexedSendIncremental(type: RagConnectTest) {
+    ragconnect {
+        outputDir = file('src/test/02-after-ragconnect/indexedSendInc')
+        inputFiles = [file('src/test/01-input/indexedSend/Test.relast'),
+                      file('src/test/01-input/indexedSend/Test.connect')]
+        rootNode = 'Root'
+        extraOptions = defaultRagConnectOptionsAnd(['--experimental-jastadd-329'])
+    }
+    relast {
+        useJastAddNames = true
+        grammarName = 'src/test/03-after-relast/indexedSendInc/indexedSendInc'
+        serializer = 'jackson'
+    }
+    jastadd {
+        jastAddList = 'JastAddList'
+        packageName = 'indexedSendInc.ast'
+        inputFiles = [file('src/test/01-input/indexedSend/Test.jadd')]
+        extraOptions = JASTADD_INCREMENTAL_OPTIONS_TRACING_FULL
+    }
+}
+
+// --- Test: attribute-incremental ---
+task compileAttributeIncremental(type: RagConnectTest) {
+    ragconnect {
+        outputDir = file('src/test/02-after-ragconnect/attributeInc')
+        inputFiles = [file('src/test/01-input/attribute/Test.relast'),
+                      file('src/test/01-input/attribute/Test.connect')]
+        rootNode = 'Root'
+        extraOptions = defaultRagConnectOptionsAnd(['--experimental-jastadd-329'])
+    }
+    relast {
+        useJastAddNames = true
+        grammarName = 'src/test/03-after-relast/attributeInc/attributeInc'
+        serializer = 'jackson'
+    }
+    jastadd {
+        jastAddList = 'JastAddList'
+        packageName = 'attributeInc.ast'
+        inputFiles = [file('src/test/01-input/attribute/Test.jadd')]
+        extraOptions = JASTADD_INCREMENTAL_OPTIONS_TRACING_FULL
+    }
+}
+
+// --- Test: relation-incremental ---
+task compileRelationIncremental(type: RagConnectTest) {
+    ragconnect {
+        outputDir = file('src/test/02-after-ragconnect/relationInc')
+        inputFiles = [file('src/test/01-input/relation/Test.relast'),
+                      file('src/test/01-input/relation/Test.connect')]
+        rootNode = 'Root'
+        extraOptions = defaultRagConnectOptionsAnd(['--experimental-jastadd-329', '--evaluationCounter'])
+    }
+    relast {
+        useJastAddNames = true
+        grammarName = 'src/test/03-after-relast/relationInc/relationInc'
+        serializer = 'jackson'
+    }
+    jastadd {
+        jastAddList = 'JastAddList'
+        packageName = 'relationInc.ast'
+        inputFiles = [file('src/test/01-input/relation/Test.jadd')]
+        extraOptions = JASTADD_INCREMENTAL_OPTIONS_TRACING_FULL
+    }
+}
+
+// --- Test: java-incremental ---
+task compileJavaIncremental(type: RagConnectTest) {
+    ragconnect {
+        outputDir = file('src/test/02-after-ragconnect/javaInc')
+        inputFiles = [file('src/test/01-input/java/Test.relast'),
+                      file('src/test/01-input/java/Test.connect')]
+        rootNode = 'Root'
+        protocols = ['java']
+        extraOptions = defaultRagConnectOptionsAnd(['--experimental-jastadd-329', '--evaluationCounter'])
+    }
+    relast {
+        useJastAddNames = true
+        grammarName = 'src/test/03-after-relast/javaInc/javaInc'
+        serializer = 'jackson'
+    }
+    jastadd {
+        jastAddList = 'JastAddList'
+        packageName = 'javaInc.ast'
+        inputFiles = [file('src/test/01-input/java/Test.jadd')]
+        extraOptions = JASTADD_INCREMENTAL_OPTIONS_TRACING_FULL
+    }
+}
+
+// --- Task order ---
+classes.dependsOn(':ragconnect.base:jar')
+
+// --- Misc ---
+static ArrayList<String> defaultRagConnectOptionsAnd(ArrayList<String> options = []) {
+    if (!options.contains('--logTarget=slf4j')) {
+        options.add('--logTarget=slf4j')
+    }
+    return options
+}
+
+protobuf {
+    protoc {
+        // The artifact spec for the Protobuf Compiler
+        artifact = 'com.google.protobuf:protoc:3.0.0'
+    }
+}
+
+dependencyUpdates {
+    gradleReleaseChannel = 'current'
+    revision = 'release'
+}
diff --git a/ragconnect.tests/src/test/01-input/attribute/README.md b/ragconnect.tests/src/test/01-input/attribute/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..08481b7a25582721dfa8435ac199b94acb205de6
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/attribute/README.md
@@ -0,0 +1,3 @@
+# Attribute
+
+Idea: Use send definitions for attributes.
diff --git a/ragconnect.tests/src/test/01-input/attribute/Test.connect b/ragconnect.tests/src/test/01-input/attribute/Test.connect
new file mode 100644
index 0000000000000000000000000000000000000000..a463af6c3ae24241a2dd7bc019bbbb21479a4d09
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/attribute/Test.connect
@@ -0,0 +1,41 @@
+send SenderRoot.basic(String) ;
+send SenderRoot.simple(String) ;
+send SenderRoot.transformed(int) ;
+send SenderRoot.toReferenceType(A) ;
+send SenderRoot.toNTA(A) ;
+send SenderRoot.circularAttribute(int);
+send SenderRoot.collectionAttribute(Set<String>) using SetToString;
+
+AddSuffix maps A a to A {:
+  A result = new A();
+  String changedValue = a.getValue() + "post";
+  result.setValue(changedValue);
+  result.setInner(new Inner("inner" + a.getInner().getInnerValue()));
+  return result;
+:}
+
+AddStringSuffix maps String s to String {:
+  return s + "post";
+:}
+
+AddPlusOne maps int i to int {:
+  return i + 1;
+:}
+
+SetToString maps Set<String> set to String {:
+  return set.toString();
+:}
+
+receive ReceiverRoot.FromBasic;
+receive ReceiverRoot.FromSimpleNoMapping;
+receive ReceiverRoot.FromSimpleWithMapping using AddStringSuffix;
+receive ReceiverRoot.FromTransformedNoMapping;
+receive ReceiverRoot.FromTransformedWithMapping using AddPlusOne;
+receive ReceiverRoot.FromReferenceTypeNoMapping;
+receive ReceiverRoot.FromReferenceTypeWithMapping using AddSuffix;
+receive ReceiverRoot.FromNTANoMapping;
+receive ReceiverRoot.FromNTAWithMapping using AddSuffix;
+receive ReceiverRoot.FromCircularNoMapping;
+receive ReceiverRoot.FromCircularWithMapping using AddPlusOne;
+receive ReceiverRoot.FromCollectionNoMapping;
+receive ReceiverRoot.FromCollectionWithMapping using AddStringSuffix;
diff --git a/ragconnect.tests/src/test/01-input/attribute/Test.jadd b/ragconnect.tests/src/test/01-input/attribute/Test.jadd
new file mode 100644
index 0000000000000000000000000000000000000000..4ad766889588ea23c2abbda5d938359c7c62d4f8
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/attribute/Test.jadd
@@ -0,0 +1,47 @@
+import java.util.Set;
+import java.util.HashSet;
+aspect Computation {
+  syn String SenderRoot.basic() = getInput();
+  syn String SenderRoot.simple() = getInput() + "Post";
+  syn int SenderRoot.transformed() = Integer.parseInt(getInput());
+  syn A SenderRoot.toReferenceType() {
+    A result = new A();
+    result.setValue(getInput());
+    Inner inner = new Inner();
+    inner.setInnerValue("1");
+    result.setInner(inner);
+    return result;
+  }
+  syn nta A SenderRoot.toNTA() {
+    A result = new A();
+    result.setValue(getInput());
+    Inner inner = new Inner();
+    inner.setInnerValue("2");
+    result.setInner(inner);
+    return result;
+  }
+  syn int SenderRoot.circularAttribute() circular [0] {
+    return Integer.parseInt(getInput()) + 2;
+  }
+  coll Set<String> SenderRoot.collectionAttribute() [new HashSet<>()] root SenderRoot ;
+  A contributes getValue() to SenderRoot.collectionAttribute();
+  SenderRoot contributes nta toNTA() to SenderRoot.collectionAttribute();
+}
+aspect MakeCodeCompile {
+
+}
+aspect MakeCodeWork {
+
+}
+aspect NameResolution {
+  // overriding customID guarantees to produce the same JSON representation for equal lists
+  // otherwise, the value for id is different each time
+  @Override
+  protected String A.customID() {
+    return getClass().getSimpleName() + getValue();
+  }
+  @Override
+  protected String Inner.customID() {
+    return getClass().getSimpleName() + getInnerValue();
+  }
+}
diff --git a/ragconnect.tests/src/test/01-input/attribute/Test.relast b/ragconnect.tests/src/test/01-input/attribute/Test.relast
new file mode 100644
index 0000000000000000000000000000000000000000..e3a3a67123589a40446651ed27bab5e6494e90f8
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/attribute/Test.relast
@@ -0,0 +1,18 @@
+Root ::= SenderRoot* ReceiverRoot;
+SenderRoot ::= <Input> A* ;
+ReceiverRoot ::=
+    <FromBasic>
+    <FromSimpleNoMapping>
+    <FromSimpleWithMapping>
+    <FromTransformedNoMapping:int>
+    <FromTransformedWithMapping:int>
+    FromReferenceTypeNoMapping:A
+    FromReferenceTypeWithMapping:A
+    FromNTANoMapping:A
+    FromNTAWithMapping:A
+    <FromCircularNoMapping:int>
+    <FromCircularWithMapping:int>
+    <FromCollectionNoMapping>
+    <FromCollectionWithMapping> ;
+A ::= <Value> Inner ;
+Inner ::= <InnerValue> ;
diff --git a/ragconnect.tests/src/test/01-input/contextFreeSimple/README.md b/ragconnect.tests/src/test/01-input/contextFreeSimple/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..737ca9af132d047a1cceafee2e6b1d2a0552a6b7
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/contextFreeSimple/README.md
@@ -0,0 +1,3 @@
+# ContextFree-Simple
+
+Idea: Use only context-free context, in simple situation (non-recursive)
diff --git a/ragconnect.tests/src/test/01-input/contextFreeSimple/Test.connect b/ragconnect.tests/src/test/01-input/contextFreeSimple/Test.connect
new file mode 100644
index 0000000000000000000000000000000000000000..7a6aeab676953fdba30bb04f111c7b1fd6bc0655
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/contextFreeSimple/Test.connect
@@ -0,0 +1,18 @@
+receive A;
+
+//"receive Root.A;" is implied
+receive Root.SingleA using PrependPrefix;
+receive Root.OptA using AddSuffix;
+//"receive Root.ListA;" would clash as "receive indexed Root.ListA" is implied
+
+PrependPrefix maps A a to A {:
+  A result = new A();
+  result.setValue("pre" + a.getValue());
+  return result;
+:}
+
+AddSuffix maps A a to A {:
+  A result = new A();
+  result.setValue(a.getValue() + "post");
+  return result;
+:}
diff --git a/ragconnect.tests/src/test/01-input/contextFreeSimple/Test.relast b/ragconnect.tests/src/test/01-input/contextFreeSimple/Test.relast
new file mode 100644
index 0000000000000000000000000000000000000000..f5dff3f8b742bc18cfbad715b1326ce483ce8a25
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/contextFreeSimple/Test.relast
@@ -0,0 +1,2 @@
+Root ::= A SingleA:A [OptA:A] ListA:A* ;
+A ::= <Value> ;
diff --git a/ragconnect.tests/src/test/01-input/errors/Errors.relast b/ragconnect.tests/src/test/01-input/errors/Errors.relast
index fcde7df1d2e48b9591ddea31aaa3b0a9efdd4cfa..85e14e3a44d5ea950498f6d4fa092d2aa4fe4be0 100644
--- a/ragconnect.tests/src/test/01-input/errors/Errors.relast
+++ b/ragconnect.tests/src/test/01-input/errors/Errors.relast
@@ -1,10 +1,10 @@
-A ::= B C D ;
+A ::= B C D E* ;
 
 // read definitions
-B ::= /<ErrorNTA:String>/ <ErrorTypeOfFirstMapping:String> <ErrorTypeOfLastMapping:String> <DoubledValue:int> <ErrorTypeMismatch:String> ;
+B ::= /<ErrorNTA:String>/ <ErrorTypeOfFirstMapping:java.util.List<String>> <ErrorTypeOfLastMapping:String> <DoubledValue:int> <ErrorTypeMismatch:String> ;
 
 // write definitions
-C ::= <ErrorNotNTA:String> /<ErrorTypeOfFirstMapping:String>/ /<ErrorTypeOfLastMapping1:String>/ /<ErrorTypeOfLastMapping2:List<String>>/ /<ErrorTypeMismatch:String>/ /<DoubledValue:int>/ ;
+C ::= /<ErrorTypeOfFirstMapping:String>/ /<ErrorTypeOfLastMapping1:String>/ /<ErrorTypeOfLastMapping2:List<String>>/ /<ErrorTypeMismatch:String>/ /<DoubledValue:int>/ ;
 
 // dependency definitions
 D ::= <SourceNonExistingTarget>
@@ -13,3 +13,6 @@ D ::= <SourceNonExistingTarget>
       <SourceSameAsListNode> /<TargetSameAsListNode>/
       <SourceDoubledValue> /<TargetDoubledValue>/
       MyList:D* ;
+
+// context-free ports
+E ::= ;
diff --git a/ragconnect.tests/src/test/01-input/errors/Part.expected b/ragconnect.tests/src/test/01-input/errors/Part.expected
index 70ea4a4baeaffb5b35df672c03e90821eb3cec82..7c212f1fff58e2d1e15f5090485c807ae376f6ef 100644
--- a/ragconnect.tests/src/test/01-input/errors/Part.expected
+++ b/ragconnect.tests/src/test/01-input/errors/Part.expected
@@ -1,11 +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 3, column 1: Port definition already defined for B.DoubledValue
+Part1.connect Line 4, column 1: Port definition already defined for B.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
+Part1.connect Line 13, column 1: No suitable default mapping found for type java.util.List<String>
+Part1.connect Line 16, column 1: to-type of last mapping (java.util.List<String>) 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)!
+Part1.connect Line 26, column 6: Could not resolve port target C.ErrorNotResolved
+Part2.connect Line 5, column 1: Port definition already defined for C.DoubledValue
+Part2.connect Line 6, column 1: Port definition already defined for C.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
index a1be3918b36ee19561a278639555db14efb8da06..d6594a6d84861a9bd846886c58806c1be468539e 100644
--- a/ragconnect.tests/src/test/01-input/errors/Part1.connect
+++ b/ragconnect.tests/src/test/01-input/errors/Part1.connect
@@ -22,8 +22,8 @@ receive B.ErrorTypeMismatch using StringToList, IntToInt ;
 // 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: type not resolved
+send C.ErrorNotResolved ;
 
 // Error: from-type of first mapping must be type of Token
 send C.ErrorTypeOfFirstMapping using IntToInt ;
diff --git a/ragconnect.tests/src/test/01-input/errors/Part2.connect b/ragconnect.tests/src/test/01-input/errors/Part2.connect
index 4d1148260a36855528b37f8b45020e076ff9ff1e..617c595dafc45ba4b9d8fe915eea377c54f4aa79 100644
--- a/ragconnect.tests/src/test/01-input/errors/Part2.connect
+++ b/ragconnect.tests/src/test/01-input/errors/Part2.connect
@@ -27,7 +27,7 @@ ListToList maps java.util.List<String> list to java.util.List<String> {:
   return list;
 :}
 
-StringToList maps String s to List<String> {:
+StringToList maps String s to java.util.List<String> {:
   java.util.List<String> result = new java.util.ArrayList<>();
   result.add(s);
   return result;
diff --git a/ragconnect.tests/src/test/01-input/errors/Standard.connect b/ragconnect.tests/src/test/01-input/errors/Standard.connect
index bd57822a6c70198cc36698f790fa90655ed4db73..68a31b844473634bfaca48d04dd177ce9c2133d3 100644
--- a/ragconnect.tests/src/test/01-input/errors/Standard.connect
+++ b/ragconnect.tests/src/test/01-input/errors/Standard.connect
@@ -1,4 +1,4 @@
-// --- update receive definitions ---
+// --- receive definitions ---
 // Error: there must not be two receive definitions for the same token
 receive B.DoubledValue ;
 receive B.DoubledValue using IntToInt ;
@@ -10,30 +10,34 @@ receive B.DoubledValue using IntToInt ;
 receive B.ErrorNTA ;
 
 // Error: from-type of first mapping must be byte[] or a supported primitive type
-receive B.ErrorTypeOfFirstMapping using ListToList ;
+receive B.ErrorTypeOfFirstMapping using ListToList, ListToList ;
 
 // Error: to-type of last mapping must be type of the Token
-receive B.ErrorTypeOfLastMapping using StringToList ;
+receive B.ErrorTypeOfLastMapping using StringToString, 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: Context-Free port not allowed for root nodes
+receive A;
 
-// Error: Token must be a TokenNTA (i.e., check for Token.getNTA())
-send C.ErrorNotNTA ;
+// Error: Clash with implied port definition of context-free port
+receive E;
+receive A.E;
 
-// Error: from-type of first mapping must be type of Token
-send C.ErrorTypeOfFirstMapping using IntToInt ;
+// --- send definitions ---
+// Error: the token must be resolvable within the parent type
+receive C.NonExisting ;
 
-// Error: to-type of last mapping must be byte[] or a supported primitive type
-send C.ErrorTypeOfLastMapping1 using StringToList ;
-send C.ErrorTypeOfLastMapping2 ;
+// NOT HANDLED \\ // Error: from-type of first mapping must be type of Token
+// NOT HANDLED \\ send C.ErrorTypeOfFirstMapping using IntToInt ;
 
-// Error: types of mappings must match (modulo inheritance)
-send C.ErrorTypeMismatch using StringToList, IntToInt ;
+// NOT HANDLED \\ // Error: to-type of last mapping must be byte[] or a supported primitive type
+// NOT HANDLED \\ send C.ErrorTypeOfLastMapping1 using StringToList ;
+// NOT HANDLED \\ send C.ErrorTypeOfLastMapping2 ;
+
+// NOT HANDLED \\ // Error: types of mappings must match (modulo inheritance)
+// NOT HANDLED \\ send C.ErrorTypeMismatch using StringToList, IntToInt ;
 
 // Error: no more than one send mapping for each TokenComponent
 send C.DoubledValue ;
@@ -44,8 +48,8 @@ send C.DoubledValue using IntToInt ;
 // 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 ;
+// NOT HANDLED \\ // Error: There must be a send update definition for the target token
+// NOT HANDLED \\ 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 ;
@@ -56,17 +60,30 @@ D.SourceDoubledValue canDependOn D.TargetDoubledValue as DoubledValue ;
 D.SourceDoubledValue canDependOn D.TargetDoubledValue as DoubledValue ;
 send D.TargetDoubledValue;
 
+// non-existence of attributes is not checked by RagConnect
+send A.nonExistingAttribute(int);
+
+// Already defined ports for attributes will be reported, however
+send A.nonExistingAttribute(int);
+
+// mappings are not checked, here string would not match
+send A.anotherIntAttribute(int) using StringToString;
+
 // --- mapping definitions ---
 ListToList maps java.util.List<String> list to java.util.List<String> {:
   return list;
 :}
 
-StringToList maps String s to List<String> {:
+StringToList maps String s to java.util.List<String> {:
   java.util.List<String> result = new java.util.ArrayList<>();
   result.add(s);
   return result;
 :}
 
+StringToString maps String s to String {:
+  return s + "1";
+:}
+
 IntToInt maps int number to int {:
   return number + 1;
 :}
diff --git a/ragconnect.tests/src/test/01-input/errors/Standard.expected b/ragconnect.tests/src/test/01-input/errors/Standard.expected
index 3ee660570525834a63c41f26820966a7504f7789..c8ce47b509c4f84f1dc36d2465b0910d506cd5fc 100644
--- a/ragconnect.tests/src/test/01-input/errors/Standard.expected
+++ b/ragconnect.tests/src/test/01-input/errors/Standard.expected
@@ -1,11 +1,15 @@
-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 3, column 1: Port definition already defined for B.DoubledValue
+Standard.connect Line 4, column 1: Port definition already defined for B.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
+Standard.connect Line 13, column 1: No suitable default mapping found for type java.util.List<String>
+Standard.connect Line 16, column 1: to-type of last mapping (java.util.List<String>) 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 22, column 9: Context-Free port not allowed for root node A!
+Standard.connect Line 26, column 1: Clash with implied, indexed port definition of context-free port in line 25!
+Standard.connect Line 30, column 9: Could not resolve port target C.NonExisting
+Standard.connect Line 43, column 1: Port definition already defined for C.DoubledValue
+Standard.connect Line 44, column 1: Port definition already defined for C.DoubledValue
+Standard.connect Line 55, column 1: The name of a dependency definition must not be equal to a list-node on the source
+Standard.connect Line 60, column 1: Dependency definition already defined for D with name DoubledValue
+Standard.connect Line 64, column 1: Port definition already defined for A.nonExistingAttribute
+Standard.connect Line 67, column 1: Port definition already defined for A.nonExistingAttribute
diff --git a/ragconnect.tests/src/test/01-input/forwarding/README.md b/ragconnect.tests/src/test/01-input/forwarding/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..9b12df3139ec9194bfab45996b823fd0578d1b12
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/forwarding/README.md
@@ -0,0 +1,6 @@
+# Forwarding
+
+Idea: Use send definitions targeting only non-NTAs (thus, requiring the creation of implicit NTAs).
+Also test context-free port definitions targeting non-NTAs to test compatibility of those two features.
+
+Note: When a type occurs in a list, the context-free port definition will force an (implicit) indexed send for that context.
diff --git a/ragconnect.tests/src/test/01-input/forwarding/Test.connect b/ragconnect.tests/src/test/01-input/forwarding/Test.connect
new file mode 100644
index 0000000000000000000000000000000000000000..066623e6330e807c058384a4172df7d7f59de594
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/forwarding/Test.connect
@@ -0,0 +1,54 @@
+send SenderRoot.A;
+send SenderRoot.SingleA;
+send SenderRoot.MaybeA;
+send SenderRoot.MultipleA;
+
+send B;
+send indexed SenderRoot.MultipleB using AddOtherSuffixForB;
+
+send SenderRoot.C using PrependPrefix;
+send SenderRoot.SingleC using PrependPrefix;
+send SenderRoot.MaybeC using PrependPrefix;
+send SenderRoot.MultipleC using ListPrependPrefix;
+
+send D using AddSuffix;
+send indexed SenderRoot.MultipleD using AddOtherSuffixForD;
+
+AddOtherSuffixForB maps B b to B {:
+  B result = new B();
+  result.setValue(b.getValue() + "-other");
+  return result;
+:}
+
+PrependPrefix maps C c to C {:
+  C result = new C();
+  result.setValue("pre-" + c.getValue());
+  return result;
+:}
+
+ListPrependPrefix maps JastAddList<C> cList to JastAddList<C> {:
+  JastAddList<C> result = new JastAddList<C>();
+  for (C c : cList) {
+    result.add(c.treeCopy().setValue("pre-" + c.getValue()));
+  }
+  return result;
+:}
+
+AddSuffix maps D d to D {:
+  D result = new D();
+  result.setValue(d.getValue() + "-post");
+  return result;
+:}
+
+AddOtherSuffixForD maps D d to D {:
+  D result = new D();
+  result.setValue(d.getValue() + "-other");
+  return result;
+:}
+
+receive ReceiverRoot.A;
+receive ReceiverRoot.ManyA;
+receive ReceiverRoot.B;
+receive ReceiverRoot.C;
+receive ReceiverRoot.ManyC;
+receive ReceiverRoot.D;
diff --git a/ragconnect.tests/src/test/01-input/forwarding/Test.relast b/ragconnect.tests/src/test/01-input/forwarding/Test.relast
new file mode 100644
index 0000000000000000000000000000000000000000..058aaf00e4a3ca7d36734ac924ec110486820ce9
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/forwarding/Test.relast
@@ -0,0 +1,14 @@
+Root ::= SenderRoot ReceiverRoot;
+
+SenderRoot ::= A SingleA:A [MaybeA:A] MultipleA:A*
+         B SingleB:B [MaybeB:B] MultipleB:B*
+         C SingleC:C [MaybeC:C] MultipleC:C*
+         D SingleD:D [MaybeD:D] MultipleD:D*
+;
+
+ReceiverRoot ::= A ManyA:A* B C ManyC:C* D ;
+
+A ::= <Value> ;
+B ::= <Value> ;
+C ::= <Value> ;
+D ::= <Value> ;
diff --git a/ragconnect.tests/src/test/01-input/incremental/Test.connect b/ragconnect.tests/src/test/01-input/incremental/Test.connect
index 8135be4e3afc9801b45d858ce9bda9016351e4a4..070f4e1b159a5537d263067d617a2cab22303fb9 100644
--- a/ragconnect.tests/src/test/01-input/incremental/Test.connect
+++ b/ragconnect.tests/src/test/01-input/incremental/Test.connect
@@ -1,4 +1,4 @@
-// endpoint definitions
+// port definitions
 receive A.Input ;
 send A.OutputOnA ;
 send B.OutputOnB using Transformation ;
diff --git a/ragconnect.tests/src/test/01-input/indexedSend/README.md b/ragconnect.tests/src/test/01-input/indexedSend/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..0de8c51d137e617dc8a8a5f6234c93a89b4b6fd5
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/indexedSend/README.md
@@ -0,0 +1,3 @@
+# Indexed Send
+
+Idea: Use send definitions on (parts of) a list to send only some of its elements, similar to `receive indexed`.
diff --git a/ragconnect.tests/src/test/01-input/indexedSend/Test.connect b/ragconnect.tests/src/test/01-input/indexedSend/Test.connect
new file mode 100644
index 0000000000000000000000000000000000000000..9e390cd63028cc0708969285792222a42f11b2fd
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/indexedSend/Test.connect
@@ -0,0 +1,17 @@
+send indexed SenderRoot.MultipleA;
+send indexed SenderRoot.MultipleAWithSuffix using AddSuffix;
+send indexed SenderRoot.A;
+send indexed SenderRoot.ComputedA using AddSuffix;
+
+AddSuffix maps A a to A {:
+  A result = new A();
+  String changedValue = a.getValue() + "post";
+  result.setValue(changedValue);
+  result.setInner(new Inner("inner" + changedValue));
+  return result;
+:}
+
+receive indexed ReceiverRoot.ManyA;
+receive indexed ReceiverRoot.ManyAWithSuffix;
+receive indexed ReceiverRoot.FromNTA;
+receive indexed ReceiverRoot.FromNTAWithSuffix;
diff --git a/ragconnect.tests/src/test/01-input/indexedSend/Test.jadd b/ragconnect.tests/src/test/01-input/indexedSend/Test.jadd
new file mode 100644
index 0000000000000000000000000000000000000000..fc8893efc9579eeebc415c2b51450f66092a2055
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/indexedSend/Test.jadd
@@ -0,0 +1,30 @@
+aspect Computation {
+  syn JastAddList<A> SenderRoot.getAList() {
+    JastAddList<A> result = new JastAddList<>();
+    getMultipleAList().forEach(a -> result.add(a.touchedTerminals()));
+    return result;
+  }
+  syn JastAddList<A> SenderRoot.getComputedAList() {
+    JastAddList<A> result = new JastAddList<>();
+    getMultipleAWithSuffixList().forEach(a -> result.add(a.touchedTerminals()));
+    return result;
+  }
+  A A.touchedTerminals() {
+    getValue();
+    getInner().getInnerValue();
+    return this;
+  }
+}
+
+aspect NameResolution {
+  // overriding customID guarantees to produce the same JSON representation for equal lists
+  // otherwise, the value for id is different each time
+  @Override
+  protected String A.customID() {
+    return getClass().getSimpleName() + getValue();
+  }
+  @Override
+  protected String Inner.customID() {
+    return getClass().getSimpleName() + getInnerValue();
+  }
+}
diff --git a/ragconnect.tests/src/test/01-input/indexedSend/Test.relast b/ragconnect.tests/src/test/01-input/indexedSend/Test.relast
new file mode 100644
index 0000000000000000000000000000000000000000..2e9924b0316578cb221e9778cc28fec83cfddc03
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/indexedSend/Test.relast
@@ -0,0 +1,5 @@
+Root ::= SenderRoot ReceiverRoot;
+SenderRoot ::= MultipleA:A* MultipleAWithSuffix:A* /A*/ /ComputedA:A*/ ;
+ReceiverRoot ::= ManyA:A* ManyAWithSuffix:A* FromNTA:A* FromNTAWithSuffix:A* ;
+A ::= <Value> Inner ;
+Inner ::= <InnerValue> ;
diff --git a/ragconnect.tests/src/test/01-input/java/README.md b/ragconnect.tests/src/test/01-input/java/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..272a8518ce52567f6528810d1c3225a163675be5
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/java/README.md
@@ -0,0 +1,3 @@
+# Java
+
+Idea: Use receive and send definitions using the Java handler.
diff --git a/ragconnect.tests/src/test/01-input/java/Test.connect b/ragconnect.tests/src/test/01-input/java/Test.connect
new file mode 100644
index 0000000000000000000000000000000000000000..d516cfe5cda16dbd0c50a3f485d5477d704ecdba
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/java/Test.connect
@@ -0,0 +1,26 @@
+send SenderRoot.SendToken ;
+send SenderRoot.SendNode ;
+send SenderRoot.SendManyNode ;
+send SenderRoot.SendNTA ;
+
+AddSuffix maps A a to A {:
+  A result = new A();
+  String changedValue = a.getValue() + "-post";
+  result.setValue(changedValue);
+  result.setInner(new Inner(a.getInner().getInnerValue() + "-post"));
+  return result;
+:}
+
+AddStringSuffix maps String s to String {:
+  return s + "post";
+:}
+
+AddPlusOne maps int i to int {:
+  return i + 1;
+:}
+
+receive ReceiverRoot.SomeToken;
+receive ReceiverRoot.SomeNode;
+receive ReceiverRoot.SomeNodeWithMapping using AddSuffix;
+receive ReceiverRoot.ManyNode;
+receive ReceiverRoot.NTA;
diff --git a/ragconnect.tests/src/test/01-input/java/Test.jadd b/ragconnect.tests/src/test/01-input/java/Test.jadd
new file mode 100644
index 0000000000000000000000000000000000000000..0bb643ed9a95cf0ca83d312d7cb9717eaacc2436
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/java/Test.jadd
@@ -0,0 +1,30 @@
+aspect Computation {
+  syn String SenderRoot.basic() = getInput();
+  syn String SenderRoot.simple() = getInput() + "Post";
+  syn A SenderRoot.getSendNTA() {
+    A result = new A();
+    result.setValue(getInput());
+    Inner inner = new Inner();
+    inner.setInnerValue("1");
+    result.setInner(inner);
+    return result;
+  }
+}
+aspect MakeCodeCompile {
+
+}
+aspect MakeCodeWork {
+
+}
+aspect NameResolution {
+  // overriding customID guarantees to produce the same JSON representation for equal lists
+  // otherwise, the value for id is different each time
+  @Override
+  protected String A.customID() {
+    return getClass().getSimpleName() + getValue();
+  }
+  @Override
+  protected String Inner.customID() {
+    return getClass().getSimpleName() + getInnerValue();
+  }
+}
diff --git a/ragconnect.tests/src/test/01-input/java/Test.relast b/ragconnect.tests/src/test/01-input/java/Test.relast
new file mode 100644
index 0000000000000000000000000000000000000000..62a23c231f4f54a99fad7d104173c3128b64393d
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/java/Test.relast
@@ -0,0 +1,10 @@
+Root ::= SenderRoot* ReceiverRoot;
+SenderRoot ::= <Input> <SendToken> SendNode:A SendManyNode:A* /SendNTA:A/ ;
+ReceiverRoot ::=
+    <SomeToken>
+    SomeNode:A
+    SomeNodeWithMapping:A
+    ManyNode:A*
+    NTA:A;
+A ::= <Value> Inner ;
+Inner ::= <InnerValue> ;
diff --git a/ragconnect.tests/src/test/01-input/list/Test.connect b/ragconnect.tests/src/test/01-input/list/Test.connect
index c0efaea9d5ad307c0c43b7d577b1cff5884c0d8b..f069ad8e63d78be4ad79da978148927fe81e3595 100644
--- a/ragconnect.tests/src/test/01-input/list/Test.connect
+++ b/ragconnect.tests/src/test/01-input/list/Test.connect
@@ -1,6 +1,6 @@
-send list SenderRoot.A ;
-send list SenderRoot.SingleA ;
-receive list ReceiverRoot.A ;
-receive list ReceiverRoot.FromSingleA ;
-receive list with add ReceiverRoot.WithAddFromA ;
-receive list with add ReceiverRoot.WithAddFromSingleA ;
+send SenderRoot.A ;
+send SenderRoot.SingleA ;
+receive ReceiverRoot.A ;
+receive ReceiverRoot.FromSingleA ;
+receive with add ReceiverRoot.WithAddFromA ;
+receive with add ReceiverRoot.WithAddFromSingleA ;
diff --git a/ragconnect.tests/src/test/01-input/relation/README.md b/ragconnect.tests/src/test/01-input/relation/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..12556e1ad76171b40a2e4a8e58717dfa41d4c52b
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/relation/README.md
@@ -0,0 +1,3 @@
+# Relation
+
+Idea: Use send definitions for relations.
diff --git a/ragconnect.tests/src/test/01-input/relation/Test.connect b/ragconnect.tests/src/test/01-input/relation/Test.connect
new file mode 100644
index 0000000000000000000000000000000000000000..61e81d25e3a3bcb898a59112340402c7f21ecccc
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/relation/Test.connect
@@ -0,0 +1,43 @@
+send SenderRoot.MyA;
+send SenderRoot.OptionalA;
+send SenderRoot.ManyA;
+
+send SenderRoot.BiMyA;
+send SenderRoot.BiOptionalA;
+send SenderRoot.BiManyA;
+
+send SenderRoot.MyB using ConcatValues;
+send SenderRoot.OptionalB using ConcatValues;
+send SenderRoot.ManyB using ConcatValueList;
+
+send SenderRoot.BiMyB using ConcatValues;
+send SenderRoot.BiOptionalB using ConcatValues;
+send SenderRoot.BiManyB using ConcatValueList;
+
+ConcatValues maps B b to String {:
+  return b.getValue() + "+" + b.getInner().getInnerValue();
+:}
+
+ConcatValueList maps java.util.List<B> list to String {:
+  StringBuilder sb = new StringBuilder();
+  for (B b : list) {
+    sb.append(b.getValue() + "+" + b.getInner().getInnerValue()).append(";");
+  }
+  return sb.toString();
+:}
+
+receive ReceiverRoot.FromMyA;
+receive ReceiverRoot.FromOptionalA;
+receive ReceiverRoot.FromManyA;
+
+receive ReceiverRoot.FromBiMyA;
+receive ReceiverRoot.FromBiOptionalA;
+receive ReceiverRoot.FromBiManyA;
+
+receive ReceiverRoot.FromMyB;
+receive ReceiverRoot.FromOptionalB;
+receive ReceiverRoot.FromManyB;
+
+receive ReceiverRoot.FromBiMyB;
+receive ReceiverRoot.FromBiOptionalB;
+receive ReceiverRoot.FromBiManyB;
diff --git a/ragconnect.tests/src/test/01-input/relation/Test.jadd b/ragconnect.tests/src/test/01-input/relation/Test.jadd
new file mode 100644
index 0000000000000000000000000000000000000000..ee797705faeb4814f881f9778a539c508402d77b
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/relation/Test.jadd
@@ -0,0 +1,36 @@
+aspect Computation {
+}
+aspect MakeCodeCompile {
+
+}
+aspect MakeCodeWork {
+}
+aspect NameResolution {
+  // overriding customID guarantees to produce the same JSON representation for equal lists
+  // otherwise, the value for id is different each time
+  @Override
+  protected String A.customID() {
+    return getClass().getSimpleName() + getValue();
+  }
+  @Override
+  protected String B.customID() {
+    return getClass().getSimpleName() + getValue();
+  }
+  @Override
+  protected String Inner.customID() {
+    return getClass().getSimpleName() + getInnerValue();
+  }
+
+  // override resolving of SenderRoot for As in ReceiverRoot
+  refine RefResolverStubs eq ASTNode.globallyResolveSenderRootByToken(String id) = getParent() != null && !getParent().isListWithoutParent() ? resolveSenderRootInh(id) : null;
+
+  syn boolean ASTNode.isList() = false;
+  eq JastAddList.isList() = true;
+  syn boolean ASTNode.isListWithoutParent() = isList() && getParent() == null;
+
+  inh SenderRoot ASTNode.resolveSenderRootInh(String id);
+  eq SenderRoot.getChild().resolveSenderRootInh(String id) = globallyResolveSenderRootByToken(id);
+  eq ReceiverRoot.getChild().resolveSenderRootInh(String id) = getFakeSenderRoot(id);
+
+  syn nta SenderRoot ReceiverRoot.getFakeSenderRoot(String id) = new SenderRoot();
+}
diff --git a/ragconnect.tests/src/test/01-input/relation/Test.relast b/ragconnect.tests/src/test/01-input/relation/Test.relast
new file mode 100644
index 0000000000000000000000000000000000000000..7effc867e0f5f45f444ac87607be607f1a23e31a
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/relation/Test.relast
@@ -0,0 +1,28 @@
+Root ::= SenderRoot* ReceiverRoot;
+SenderRoot ::= A* B* ;
+rel SenderRoot.MyA -> A;
+rel SenderRoot.OptionalA? -> A;
+rel SenderRoot.ManyA* -> A;
+
+rel SenderRoot.BiMyA <-> A.ToMyA?;
+rel SenderRoot.BiOptionalA? <-> A.ToOptionalA?;
+rel SenderRoot.BiManyA* <-> A.ToManyA?;
+
+rel SenderRoot.MyB -> B;
+rel SenderRoot.OptionalB? -> B;
+rel SenderRoot.ManyB* -> B;
+
+rel SenderRoot.BiMyB <-> B.ToMyB?;
+rel SenderRoot.BiOptionalB? <-> B.ToOptionalB?;
+rel SenderRoot.BiManyB* <-> B.ToManyB?;
+
+ReceiverRoot ::=
+FromMyA:A   FromOptionalA:A   FromManyA:A*
+FromBiMyA:A FromBiOptionalA:A FromBiManyA:A*
+<FromMyB:String>   <FromOptionalB:String>   <FromManyB:String>
+<FromBiMyB:String> <FromBiOptionalB:String> <FromBiManyB:String>
+;
+
+A ::= <Value> Inner ;
+B ::= <Value> Inner ;
+Inner ::= <InnerValue> ;
diff --git a/ragconnect.tests/src/test/01-input/singleList/README.md b/ragconnect.tests/src/test/01-input/singleList/README.md
index 6a76bb4d8457d1f246edaeb96d6d72048ef4ced1..8c062b6d8c9fc58a2b21a070559938f75e63cbd0 100644
--- a/ragconnect.tests/src/test/01-input/singleList/README.md
+++ b/ragconnect.tests/src/test/01-input/singleList/README.md
@@ -18,30 +18,30 @@ SenderRoot                         ReceiverRoot
 
 ## Computation
 
-A _n_ = Input _n_ + 1, e.g., A1 = Input1 + 1
+A _n_ = Input _n_ + _n_, e.g., A1 = Input1 + 1 and A3 = Input3 + 3
 
 ## Execution-Trace (SendInitialValue)
 
-| Input | [A1,A2,A3,A4,IO] | # | A* | UsingWcA | WithAddA | UsingWcWithAddA:A |
-|---|---|---|---|---|---|---|
-| * | [1,2,3,4,0] | 5 | [1,2,3,4,0] | [1,2,3,4,0] | [1,2,3,4,0] | [1,2,3,4,0] |
-| I1:1 | [2,2,3,4,0] | 6 | [2,2,3,4,0] | [2,2,3,4,0] | [1,2,3,4,0,2] | [1,2,3,4,0,2] |
-| I1:1 | [2,2,3,4,0] | 6 | [2,2,3,4,0] | [2,2,3,4,0] | [1,2,3,4,0,2] | [1,2,3,4,0,2] |
-| I1:2 | [3,2,3,4,0] | 7 | [3,2,3,4,0] | [3,2,3,4,0] | [1,2,3,4,0,2,3] | [1,2,3,4,0,2,3] |
-| IO:5 | [3,2,3,4,5] | 8 | [3,2,3,4,5] | [3,2,3,4,5] | [1,2,3,4,0,2,3,5] | [1,2,3,4,0,2,3,5]
-| I3:4 | [3,2,7,4,5] | 9 | [3,2,7,4,5] | [3,2,7,4,5] | [1,2,3,4,0,2,3,5,7] | [1,2,3,4,0,2,3,5,7] |
+| Input | [A1,A2,A3,A4,IO] | #   | A*          | UsingWcA    | WithAddA            | UsingWcWithAddA:A   |
+|-------|------------------|-----|-------------|-------------|---------------------|---------------------|
+| *     | [1,2,3,4,0]      | 5   | [1,2,3,4,0] | [1,2,3,4,0] | [1,2,3,4,0]         | [1,2,3,4,0]         |
+| I1:1  | [2,2,3,4,0]      | 6   | [2,2,3,4,0] | [2,2,3,4,0] | [1,2,3,4,0,2]       | [1,2,3,4,0,2]       |
+| I1:1  | [2,2,3,4,0]      | 6   | [2,2,3,4,0] | [2,2,3,4,0] | [1,2,3,4,0,2]       | [1,2,3,4,0,2]       |
+| I1:2  | [3,2,3,4,0]      | 7   | [3,2,3,4,0] | [3,2,3,4,0] | [1,2,3,4,0,2,3]     | [1,2,3,4,0,2,3]     |
+| IO:5  | [3,2,3,4,5]      | 8   | [3,2,3,4,5] | [3,2,3,4,5] | [1,2,3,4,0,2,3,5]   | [1,2,3,4,0,2,3,5]   |
+| I3:4  | [3,2,7,4,5]      | 9   | [3,2,7,4,5] | [3,2,7,4,5] | [1,2,3,4,0,2,3,5,7] | [1,2,3,4,0,2,3,5,7] |
 
 *: (1:0, 2:0, 3:0, 4:0, 5:0)
 
 ## Execution-Trace (OnlyUpdate)
 
-| Input | [A1,A2,A3,A4,IO] | # | A* | UsingWcA | WithAddA | UsingWcWithAddA:A |
-|---|---|---|---|---|---|---|
-| * | [-,-,-,-,-] | 0 | [0,0,0,0,0] | [] | [] | [] |
-| I1:1 | [2,-,-,-,-] | 1 | [2,0,0,0,0] | [2] | [2] | [2] |
-| I1:1 | [2,-,-,-,-] | 1 | [2,0,0,0,0] | [2] | [2] | [2] |
-| I1:2 | [3,-,-,-,-] | 2 | [3,0,0,0,0] | [3] | [2,3] | [2,3] |
-| IO:5 | [2,-,-,-,5] | 3 | [3,0,0,0,5] | [3,5] | [2,3,5] | [2,3,5]
-| I3:4 | [2,-,7,-,5] | 4 | [3,0,7,0,5] | [3,5,7] | [2,3,5,7] | [2,3,5,7] |
+| Input | [A1,A2,A3,A4,IO] | #   | A*          | UsingWcA | WithAddA  | UsingWcWithAddA:A |
+|-------|------------------|-----|-------------|----------|-----------|-------------------|
+| *     | [-,-,-,-,-]      | 0   | [0,0,0,0,0] | []       | []        | []                |
+| I1:1  | [2,-,-,-,-]      | 1   | [2,0,0,0,0] | [2]      | [2]       | [2]               |
+| I1:1  | [2,-,-,-,-]      | 1   | [2,0,0,0,0] | [2]      | [2]       | [2]               |
+| I1:2  | [3,-,-,-,-]      | 2   | [3,0,0,0,0] | [3]      | [2,3]     | [2,3]             |
+| IO:5  | [2,-,-,-,5]      | 3   | [3,0,0,0,5] | [3,5]    | [2,3,5]   | [2,3,5]           |
+| I3:4  | [2,-,7,-,5]      | 4   | [3,0,7,0,5] | [3,5,7]  | [2,3,5,7] | [2,3,5,7]         |
 
 *: (1:0, 2:0, 3:0, 4:0, 5:0)
diff --git a/ragconnect.tests/src/test/01-input/singleList/Test.connect b/ragconnect.tests/src/test/01-input/singleList/Test.connect
index 21b2796544ae65e96da5b03f088e5f7966620a7f..d4f5dec01667765e0e6e4604c502589b036d5440 100644
--- a/ragconnect.tests/src/test/01-input/singleList/Test.connect
+++ b/ragconnect.tests/src/test/01-input/singleList/Test.connect
@@ -1,13 +1,13 @@
-send tree SenderRoot.A1 ;
-send tree SenderRoot.A2 ;
-send tree SenderRoot.A3 ;
-send tree SenderRoot.A4 ;
+send SenderRoot.A1 ;
+send SenderRoot.A2 ;
+send SenderRoot.A3 ;
+send SenderRoot.A4 ;
 send SenderRoot.InOutput using IntToA ;
 
-receive tree ReceiverRoot.A ;
-receive tree ReceiverRoot.UsingWildcardA ;
-receive tree with add ReceiverRoot.WithAddA ;
-receive tree with add ReceiverRoot.UsingWildcardWithAddA ;
+receive indexed ReceiverRoot.A ;
+receive indexed ReceiverRoot.UsingWildcardA ;
+receive indexed with add ReceiverRoot.WithAddA ;
+receive indexed with add ReceiverRoot.UsingWildcardWithAddA ;
 
 IntToA maps int i to A {:
     return new A().setID(i);
diff --git a/ragconnect.tests/src/test/01-input/singleListVariant/Test.connect b/ragconnect.tests/src/test/01-input/singleListVariant/Test.connect
index 23365109901425e8a6060c3ef3c2d8fe4cfc7656..eca7f99f1155885ca819973e8ab2b7cc00303f90 100644
--- a/ragconnect.tests/src/test/01-input/singleListVariant/Test.connect
+++ b/ragconnect.tests/src/test/01-input/singleListVariant/Test.connect
@@ -1,28 +1,28 @@
-send tree SenderRoot.T_Empty ;
-send tree SenderRoot.T_Token ;
-send tree SenderRoot.T_OneChild ;
-send tree SenderRoot.T_OneOpt ;
-send tree SenderRoot.T_OneList ;
-send tree SenderRoot.T_TwoChildren ;
-send tree SenderRoot.T_OneOfEach ;
-send tree SenderRoot.T_Abstract ;
+send SenderRoot.T_Empty ;
+send SenderRoot.T_Token ;
+send SenderRoot.T_OneChild ;
+send SenderRoot.T_OneOpt ;
+send SenderRoot.T_OneList ;
+send SenderRoot.T_TwoChildren ;
+send SenderRoot.T_OneOfEach ;
+send SenderRoot.T_Abstract ;
 
-receive tree ReceiverRoot.T_Empty ;
-receive tree ReceiverRoot.T_Token ;
-receive tree ReceiverRoot.T_OneChild ;
-receive tree ReceiverRoot.T_OneOpt ;
-receive tree ReceiverRoot.T_OneList ;
-receive tree ReceiverRoot.T_TwoChildren ;
-receive tree ReceiverRoot.T_OneOfEach ;
-receive tree ReceiverRoot.T_Abstract ;
+receive indexed ReceiverRoot.T_Empty ;
+receive indexed ReceiverRoot.T_Token ;
+receive indexed ReceiverRoot.T_OneChild ;
+receive indexed ReceiverRoot.T_OneOpt ;
+receive indexed ReceiverRoot.T_OneList ;
+receive indexed ReceiverRoot.T_TwoChildren ;
+receive indexed ReceiverRoot.T_OneOfEach ;
+receive indexed ReceiverRoot.T_Abstract ;
 
-receive tree ReceiverRoot.MyEmpty ;
+receive indexed ReceiverRoot.MyEmpty ;
 
-receive tree with add ReceiverRoot.EmptyWithAdd ;
-receive tree with add ReceiverRoot.TokenWithAdd ;
-receive tree with add ReceiverRoot.OneChildWithAdd ;
-receive tree with add ReceiverRoot.OneOptWithAdd ;
-receive tree with add ReceiverRoot.OneListWithAdd ;
-receive tree with add ReceiverRoot.TwoChildrenWithAdd ;
-receive tree with add ReceiverRoot.OneOfEachWithAdd ;
-receive tree with add ReceiverRoot.AbstractWithAdd ;
+receive indexed with add ReceiverRoot.EmptyWithAdd ;
+receive indexed with add ReceiverRoot.TokenWithAdd ;
+receive indexed with add ReceiverRoot.OneChildWithAdd ;
+receive indexed with add ReceiverRoot.OneOptWithAdd ;
+receive indexed with add ReceiverRoot.OneListWithAdd ;
+receive indexed with add ReceiverRoot.TwoChildrenWithAdd ;
+receive indexed with add ReceiverRoot.OneOfEachWithAdd ;
+receive indexed with add ReceiverRoot.AbstractWithAdd ;
diff --git a/ragconnect.tests/src/test/01-input/tokenValueSend/Test.connect b/ragconnect.tests/src/test/01-input/tokenValueSend/Test.connect
index bcc26d1fba55b6241227f8e37e93097ced82284b..f92ab8cde71d49f5f9907acfcaf6d51deab667b0 100644
--- a/ragconnect.tests/src/test/01-input/tokenValueSend/Test.connect
+++ b/ragconnect.tests/src/test/01-input/tokenValueSend/Test.connect
@@ -10,14 +10,10 @@ receive ReceiveSendAndDepend.Value using AddPrefix ;
 send ReceiveSendAndDepend.Value using AddPostfix ;
 send ReceiveSendAndDepend.OtherOutput using AddPostfix ;
 
-ReceiveSendAndDepend.OtherOutput canDependOn ReceiveSendAndDepend.Value as dependency1 ;
-
 AddPrefix maps String s to String {:
-  System.out.println("receive " + s);
   return "Pre-" + s;
 :}
 
 AddPostfix maps String s to String {:
-  System.out.println("send " + s);
   return s + "-Post";
 :}
diff --git a/ragconnect.tests/src/test/01-input/tokenValueSend/TestDependencies.connect b/ragconnect.tests/src/test/01-input/tokenValueSend/TestDependencies.connect
new file mode 100644
index 0000000000000000000000000000000000000000..02e8fcdca30c9e381714b939336ff527c349ca71
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/tokenValueSend/TestDependencies.connect
@@ -0,0 +1 @@
+ReceiveSendAndDepend.OtherOutput canDependOn ReceiveSendAndDepend.Value as dependency1 ;
diff --git a/ragconnect.tests/src/test/01-input/tree/Test.connect b/ragconnect.tests/src/test/01-input/tree/Test.connect
index 857f6297f12249451b026051201ebfc004c2f6a6..3fc8564ee5c8f53076d8e27ef29c590885205a58 100644
--- a/ragconnect.tests/src/test/01-input/tree/Test.connect
+++ b/ragconnect.tests/src/test/01-input/tree/Test.connect
@@ -1,2 +1,2 @@
-send tree SenderRoot.Alfa ;
-receive tree ReceiverRoot.Alfa ;
+send SenderRoot.Alfa ;
+receive ReceiverRoot.Alfa ;
diff --git a/ragconnect.tests/src/test/01-input/treeAllowedTokens/Test.connect b/ragconnect.tests/src/test/01-input/treeAllowedTokens/Test.connect
index d872ce56c59097e678e6cff0b25ec422130b6129..c17a58e7d9798217c582ac5e65492ba25bcb7ea2 100644
--- a/ragconnect.tests/src/test/01-input/treeAllowedTokens/Test.connect
+++ b/ragconnect.tests/src/test/01-input/treeAllowedTokens/Test.connect
@@ -1,13 +1,13 @@
-send tree SenderRoot.Alfa ;
-receive tree ReceiverRoot.Alfa ;
+send SenderRoot.Alfa ;
+receive ReceiverRoot.Alfa ;
 
 receive SenderRoot.Input1WhenFlagIsTrue ;
 receive SenderRoot.Input1WhenFlagIsFalse ;
 receive SenderRoot.Input2 ;
 receive SenderRoot.Input3 ;
 
-send tree SenderRoot.AlfaPrimitive using Alfa2String ;
-receive tree ReceiverRoot.AlfaPrimitive using String2Alfa ;
+send SenderRoot.AlfaPrimitive using Alfa2String ;
+receive ReceiverRoot.AlfaPrimitive using String2Alfa ;
 
 Alfa2String maps Alfa alfa to String {:
   StringBuilder sb = new StringBuilder();
diff --git a/ragconnect.tests/src/test/01-input/tutorial/Test.connect b/ragconnect.tests/src/test/01-input/tutorial/Test.connect
index c6bdc7b3c4f5dcf661ce2ce757301e42eb39a1a7..f1f3af2d719924b5c5ddaccfd10771c5a2240c0f 100644
--- a/ragconnect.tests/src/test/01-input/tutorial/Test.connect
+++ b/ragconnect.tests/src/test/01-input/tutorial/Test.connect
@@ -1,4 +1,4 @@
-// endpoint definitions
+// port definitions
 receive A.Input ;
 send A.OutputOnA ;
 send B.OutputOnB using Transformation ;
diff --git a/ragconnect.tests/src/test/01-input/warnings/NoDependenciesAndNoInc.expected b/ragconnect.tests/src/test/01-input/warnings/NoDependenciesAndNoInc.expected
new file mode 100644
index 0000000000000000000000000000000000000000..9ed8fcfcf83026f52c4824941f369c05b677af39
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/warnings/NoDependenciesAndNoInc.expected
@@ -0,0 +1 @@
+TestNoDependencies.connect Line 1, column 1: No dependency definitions are given, and incremental evaluation is disabled. No messages will be sent for this!
diff --git a/ragconnect.tests/src/test/01-input/warnings/NoDependenciesAndNoIncAndCacheAll.expected b/ragconnect.tests/src/test/01-input/warnings/NoDependenciesAndNoIncAndCacheAll.expected
new file mode 100644
index 0000000000000000000000000000000000000000..e2743af9f787c4f047407b0f47814bcc752fa856
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/warnings/NoDependenciesAndNoIncAndCacheAll.expected
@@ -0,0 +1,2 @@
+TestNoDependencies.connect Line 1, column 1: Incremental evaluation is disabled, but cache=all is set. This might lead to no messages sent!
+TestNoDependencies.connect Line 1, column 1: No dependency definitions are given, and incremental evaluation is disabled. No messages will be sent for this!
diff --git a/ragconnect.tests/src/test/01-input/warnings/SomeDependenciesAndInc.expected b/ragconnect.tests/src/test/01-input/warnings/SomeDependenciesAndInc.expected
new file mode 100644
index 0000000000000000000000000000000000000000..f1154cf41a38be8e3e54e7d1933837705e838ac4
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/warnings/SomeDependenciesAndInc.expected
@@ -0,0 +1,2 @@
+TestSomeDependencies.connect Line 3, column 1: Dependency definition are deprecated since 1.0.0!
+TestSomeDependencies.connect Line 3, column 1: Dependency definition should not be used if incremental evaluation is enabled!
diff --git a/ragconnect.tests/src/test/01-input/warnings/SomeDependenciesAndNoIncAndCacheAll.expected b/ragconnect.tests/src/test/01-input/warnings/SomeDependenciesAndNoIncAndCacheAll.expected
new file mode 100644
index 0000000000000000000000000000000000000000..e4d442f74bfdcb87dbe720381cb8d05b51734527
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/warnings/SomeDependenciesAndNoIncAndCacheAll.expected
@@ -0,0 +1,2 @@
+TestSomeDependencies.connect Line 1, column 1: Incremental evaluation is disabled, but cache=all is set. This might lead to no messages sent!
+TestSomeDependencies.connect Line 3, column 1: Dependency definition are deprecated since 1.0.0!
diff --git a/ragconnect.tests/src/test/01-input/warnings/Test.jrag b/ragconnect.tests/src/test/01-input/warnings/Test.jrag
new file mode 100644
index 0000000000000000000000000000000000000000..7a1b4984f520b6ca5d39317f258c2b83a0ba72e0
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/warnings/Test.jrag
@@ -0,0 +1,3 @@
+aspect Computation {
+  syn String Root.getOutput() = getInput() + "out";
+}
diff --git a/ragconnect.tests/src/test/01-input/warnings/Test.relast b/ragconnect.tests/src/test/01-input/warnings/Test.relast
new file mode 100644
index 0000000000000000000000000000000000000000..5acb9e5493771e0115fcc5d22e0e84ea9d7d4da7
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/warnings/Test.relast
@@ -0,0 +1 @@
+Root ::= <Input> /<Output>/ ;
diff --git a/ragconnect.tests/src/test/01-input/warnings/TestNoDependencies.connect b/ragconnect.tests/src/test/01-input/warnings/TestNoDependencies.connect
new file mode 100644
index 0000000000000000000000000000000000000000..e2efbebcc85c375c23feba62cab48617a77b8a83
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/warnings/TestNoDependencies.connect
@@ -0,0 +1 @@
+send Root.Output ;
diff --git a/ragconnect.tests/src/test/01-input/warnings/TestSomeDependencies.connect b/ragconnect.tests/src/test/01-input/warnings/TestSomeDependencies.connect
new file mode 100644
index 0000000000000000000000000000000000000000..30409ea90623fd59ce1efacf4b64a1f04aab37e0
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/warnings/TestSomeDependencies.connect
@@ -0,0 +1,3 @@
+send Root.Output ;
+
+Root.Output canDependOn Root.Input as AttributeDependency;
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AbstractMqttTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AbstractMqttTest.java
index db48fdf9a525e242bed1c39f7adf54260668a08a..9720f2f943ec1914f5efc813d5a24b3eec3e2a23 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AbstractMqttTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AbstractMqttTest.java
@@ -1,6 +1,8 @@
 package org.jastadd.ragconnect.tests;
 
 import defaultOnlyRead.ast.MqttHandler;
+import io.github.artsok.RepeatedIfExceptionsTest;
+import org.jastadd.ragconnect.tests.utils.TestUtils;
 import org.junit.jupiter.api.*;
 
 import java.io.IOException;
@@ -12,13 +14,24 @@ import java.util.concurrent.TimeUnit;
  * @author rschoene - Initial contribution
  */
 @Tag("mqtt")
-public abstract class AbstractMqttTest {
+public abstract class AbstractMqttTest extends RagConnectTest {
 
+  protected static final int TEST_REPETITIONS = 3;
   private static boolean checkDone = false;
+
   protected static MqttHandler publisher;
 
+  /**
+   * if the initial/current value shall be sent upon connecting
+   */
+  protected boolean writeCurrentValue;
+
+  public boolean isWriteCurrentValue() {
+    return writeCurrentValue;
+  }
+
   @BeforeAll
-  public static void createPublishAndOnceCheckMqttConnection() {
+  public static void createPublisherAndCheckMqttConnectionOnce() {
     boolean checkResult;
     try {
       publisher = new MqttHandler("Publisher")
@@ -43,11 +56,14 @@ public abstract class AbstractMqttTest {
   }
 
   @Tag("mqtt")
-  @Test
-  public final void testCommunicateSendInitialValue() throws IOException, InterruptedException {
+  @RepeatedIfExceptionsTest(repeats = TEST_REPETITIONS)
+  public void testCommunicateSendInitialValue() throws IOException, InterruptedException {
+    this.writeCurrentValue = true;
+
     createModel();
-    setupReceiverAndConnect(true);
+    setupReceiverAndConnect();
 
+    logger.info("Calling communicateSendInitialValue");
     communicateSendInitialValue();
   }
 
@@ -58,11 +74,14 @@ public abstract class AbstractMqttTest {
   protected abstract void communicateSendInitialValue() throws IOException, InterruptedException;
 
   @Tag("mqtt")
-  @Test
-  public final void testCommunicateOnlyUpdatedValue() throws IOException, InterruptedException {
+  @RepeatedIfExceptionsTest(repeats = TEST_REPETITIONS)
+  public void testCommunicateOnlyUpdatedValue() throws IOException, InterruptedException {
+    this.writeCurrentValue = false;
+
     createModel();
-    setupReceiverAndConnect(false);
+    setupReceiverAndConnect();
 
+    logger.info("Calling communicateOnlyUpdatedValue");
     communicateOnlyUpdatedValue();
   }
 
@@ -80,32 +99,36 @@ public abstract class AbstractMqttTest {
   /**
    * Begin with this snippet
    * <pre>
-   *     model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS);
+   * {@code
+   * model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS);
    *
-   *     handler = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost());
-   *     assertTrue(handler.waitUntilReady(2, TimeUnit.SECONDS));
+   * handler = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost());
+   * assertTrue(handler.waitUntilReady(2, TimeUnit.SECONDS));
+   * }
    * </pre>
    *
    * And then add dependencies, initialise receiver, add connections to those receivers,
    * and finally call generated connect* methods on model elements.
-   * @param writeCurrentValue if the initial/current value shall be sent upon connecting
    */
-  protected abstract void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException, InterruptedException;
+  protected abstract void setupReceiverAndConnect() throws IOException, InterruptedException;
 
   @AfterEach
   public void alwaysCloseConnections() {
+    logger.debug("Closing connections");
     closeConnections();
   }
 
   /**
    * Write the following snippet (using your correct handler and model):
    * <pre>
+   * {@code
    * if (handler != null) {
    *    handler.close();
    * }
    * if (model != null) {
    *   model.ragconnectCloseConnections();
    * }
+   * }
    * </pre>
    */
   protected abstract void closeConnections();
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AttributeTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AttributeTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..7073e6d4f05d35ab7d42d84b0d1d31072b92efc2
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AttributeTest.java
@@ -0,0 +1,360 @@
+package org.jastadd.ragconnect.tests;
+
+import attributeInc.ast.*;
+import de.tudresden.inf.st.jastadd.dumpAst.ast.Dumper;
+import org.jastadd.ragconnect.tests.utils.TestUtils;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Tag;
+
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.Objects;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import org.jastadd.ragconnect.tests.utils.TestChecker;
+
+import static java.util.function.Predicate.isEqual;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.jastadd.ragconnect.tests.utils.TestUtils.*;
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Test case "attribute".
+ *
+ * @author rschoene - Initial contribution
+ */
+@Tag("Incremental")
+@Tag("New")
+public class AttributeTest extends AbstractMqttTest {
+
+  private static final String TOPIC_WILDCARD = "attr/#";
+  private static final String TOPIC_BASIC = "attr/string/basic";
+  private static final String TOPIC_SIMPLE_NO_MAPPING = "attr/string/simple/plain";
+  private static final String TOPIC_SIMPLE_WITH_MAPPING = "attr/string/simple/mapped";
+  private static final String TOPIC_TRANSFORMED_NO_MAPPING = "attr/int/transformed/plain";
+  private static final String TOPIC_TRANSFORMED_WITH_MAPPING = "attr/int/transformed/mapped";
+  private static final String TOPIC_REFERENCE_TYPE_NO_MAPPING = "attr/a/ref/plain";
+  private static final String TOPIC_REFERENCE_TYPE_WITH_MAPPING = "attr/a/ref/mapped";
+  private static final String TOPIC_NTA_NO_MAPPING = "attr/a/nta/plain";
+  private static final String TOPIC_NTA_WITH_MAPPING = "attr/a/nta/mapped";
+  private static final String TOPIC_CIRCULAR_NO_MAPPING = "attr/a/circular/plain";
+  private static final String TOPIC_CIRCULAR_WITH_MAPPING = "attr/a/circular/mapped";
+  private static final String TOPIC_COLLECTION_NO_MAPPING = "attr/a/collection/plain";
+  private static final String TOPIC_COLLECTION_WITH_MAPPING = "attr/a/collection/mapped";
+
+  private static final String INITIAL_STRING = "initial";
+  private static final String INITIAL_STRING_FOR_INT = "1";
+  private static final String INITIAL_STRING_FOR_INT_PLUS_2 = Integer.toString(Integer.parseInt(INITIAL_STRING_FOR_INT) + 2);
+
+  private static final String CHECK_BASIC = "basic";
+  private static final String CHECK_SIMPLE = "simple";
+  private static final String CHECK_TRANSFORMED = "transformed";
+  private static final String CHECK_A = "a";
+  private static final String CHECK_NTA = "nta";
+  private static final String CHECK_CIRCULAR = "circular";
+  private static final String CHECK_COLLECTION = "collection";
+
+  private MqttHandler handler;
+  private ReceiverData data;
+  private TestChecker checker;
+
+  private Root model;
+  private SenderRoot senderString;
+  private SenderRoot senderInt;
+  private SenderRoot senderA;
+  private ReceiverRoot receiverRoot;
+
+  @Override
+  protected void createModel() {
+    model = new Root();
+//    model.trace().setReceiver(TestUtils::logEvent);
+    senderString = new SenderRoot().setInput(INITIAL_STRING);
+    senderInt = new SenderRoot().setInput(INITIAL_STRING_FOR_INT);
+    senderA = new SenderRoot().setInput(INITIAL_STRING);
+    receiverRoot = new ReceiverRoot();
+    model.addSenderRoot(senderString);
+    model.addSenderRoot(senderInt);
+    model.addSenderRoot(senderA);
+    model.setReceiverRoot(receiverRoot);
+  }
+
+  @Override
+  protected void setupReceiverAndConnect() throws IOException, InterruptedException {
+    model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS);
+    handler = new MqttHandler().setHost(TestUtils.getMqttHost()).dontSendWelcomeMessage();
+    assertTrue(handler.waitUntilReady(2, TimeUnit.SECONDS));
+
+    data = new ReceiverData();
+    assertTrue(handler.newConnection(TOPIC_WILDCARD, bytes -> data.numberOfValues += 1));
+
+    checker = new TestChecker();
+    checker.setActualNumberOfValues(() -> data.numberOfValues)
+            .setCheckForString(CHECK_BASIC, this::checkBasic)
+            .setCheckForString(CHECK_SIMPLE, this::checkSimple)
+            .setCheckForString(CHECK_TRANSFORMED, this::checkTransformed)
+            .setCheckForString(CHECK_A, this::checkA)
+            .setCheckForString(CHECK_NTA, this::checkNta)
+            .setCheckForString(CHECK_CIRCULAR, this::checkCircular)
+            .setCheckForString(CHECK_COLLECTION, this::checkCollection)
+    ;
+
+    // connect receive
+    assertTrue(receiverRoot.connectFromBasic(mqttUri(TOPIC_BASIC)));
+    assertTrue(receiverRoot.connectFromSimpleNoMapping(mqttUri(TOPIC_SIMPLE_NO_MAPPING)));
+    assertTrue(receiverRoot.connectFromSimpleWithMapping(mqttUri(TOPIC_SIMPLE_WITH_MAPPING)));
+    assertTrue(receiverRoot.connectFromTransformedNoMapping(mqttUri(TOPIC_TRANSFORMED_NO_MAPPING)));
+    assertTrue(receiverRoot.connectFromTransformedWithMapping(mqttUri(TOPIC_TRANSFORMED_WITH_MAPPING)));
+    assertTrue(receiverRoot.connectFromReferenceTypeNoMapping(mqttUri(TOPIC_REFERENCE_TYPE_NO_MAPPING)));
+    assertTrue(receiverRoot.connectFromReferenceTypeWithMapping(mqttUri(TOPIC_REFERENCE_TYPE_WITH_MAPPING)));
+    assertTrue(receiverRoot.connectFromNTANoMapping(mqttUri(TOPIC_NTA_NO_MAPPING)));
+    assertTrue(receiverRoot.connectFromNTAWithMapping(mqttUri(TOPIC_NTA_WITH_MAPPING)));
+    assertTrue(receiverRoot.connectFromCircularNoMapping(mqttUri(TOPIC_CIRCULAR_NO_MAPPING)));
+    assertTrue(receiverRoot.connectFromCircularWithMapping(mqttUri(TOPIC_CIRCULAR_WITH_MAPPING)));
+    assertTrue(receiverRoot.connectFromCollectionNoMapping(mqttUri(TOPIC_COLLECTION_NO_MAPPING)));
+    assertTrue(receiverRoot.connectFromCollectionWithMapping(mqttUri(TOPIC_COLLECTION_WITH_MAPPING)));
+
+    // connect send, and wait to receive (if writeCurrentValue is set)
+    assertTrue(senderString.connectBasic(mqttUri(TOPIC_BASIC), isWriteCurrentValue()));
+    assertTrue(senderString.connectSimple(mqttUri(TOPIC_SIMPLE_NO_MAPPING), isWriteCurrentValue()));
+    assertTrue(senderString.connectSimple(mqttUri(TOPIC_SIMPLE_WITH_MAPPING), isWriteCurrentValue()));
+    assertTrue(senderString.connectCollectionAttribute(mqttUri(TOPIC_COLLECTION_NO_MAPPING), isWriteCurrentValue()));
+    assertTrue(senderString.connectCollectionAttribute(mqttUri(TOPIC_COLLECTION_WITH_MAPPING), isWriteCurrentValue()));
+
+    assertTrue(senderInt.connectTransformed(mqttUri(TOPIC_TRANSFORMED_NO_MAPPING), isWriteCurrentValue()));
+    assertTrue(senderInt.connectTransformed(mqttUri(TOPIC_TRANSFORMED_WITH_MAPPING), isWriteCurrentValue()));
+    assertTrue(senderInt.connectCircularAttribute(mqttUri(TOPIC_CIRCULAR_NO_MAPPING), isWriteCurrentValue()));
+    assertTrue(senderInt.connectCircularAttribute(mqttUri(TOPIC_CIRCULAR_WITH_MAPPING), isWriteCurrentValue()));
+
+    assertTrue(senderA.connectToReferenceType(mqttUri(TOPIC_REFERENCE_TYPE_NO_MAPPING), isWriteCurrentValue()));
+    assertTrue(senderA.connectToReferenceType(mqttUri(TOPIC_REFERENCE_TYPE_WITH_MAPPING), isWriteCurrentValue()));
+    assertTrue(senderA.connectToNTA(mqttUri(TOPIC_NTA_NO_MAPPING), isWriteCurrentValue()));
+    assertTrue(senderA.connectToNTA(mqttUri(TOPIC_NTA_WITH_MAPPING), isWriteCurrentValue()));
+
+    waitForValue(senderString.basic(), receiverRoot::getFromBasic);
+    waitForValue(senderString.simple(), receiverRoot::getFromSimpleNoMapping);
+    waitForValue(senderInt.transformed(), receiverRoot::getFromTransformedNoMapping);
+    waitForNonNull(receiverRoot::getFromCollectionNoMapping);
+    waitForNonNull(receiverRoot::getFromReferenceTypeNoMapping);
+    waitForNonNull(receiverRoot::getFromNTANoMapping);
+  }
+
+  @Override
+  protected void communicateSendInitialValue() throws IOException, InterruptedException {
+    // 13 = basic, simple(2), collection(2), transformed(2), circular(2), ref-type(2), nta(2)
+    checker.addToNumberOfValues(13)
+            .put(CHECK_BASIC, INITIAL_STRING)
+            .put(CHECK_SIMPLE, INITIAL_STRING + "Post")
+            .put(CHECK_TRANSFORMED, INITIAL_STRING_FOR_INT)
+            .put(CHECK_A, INITIAL_STRING)
+            .put(CHECK_NTA, INITIAL_STRING)
+            .put(CHECK_CIRCULAR, INITIAL_STRING_FOR_INT_PLUS_2)
+            .put(CHECK_COLLECTION, "[" + INITIAL_STRING + "]");
+
+    if (!TestUtils.isCi()) {
+      Dumper.read(model)
+              .includeAttributeWhen((node, attributeName, isNTA, value) -> {
+                switch (attributeName) {
+                  case "basic":
+                  case "simple":
+                  case "collectionAttribute":
+                    return node.equals(senderString);
+                  case "transformed":
+                  case "circularAttribute":
+                    return node.equals(senderInt);
+                  case "toReferenceType":
+                  case "toNTA":
+                    return node.equals(senderA);
+                }
+                return false;
+              })
+              .includeChildWhen((parentNode, childNode, contextName) -> !contextName.equals("Inner"))
+              .setNameMethod(node -> {
+                if (node instanceof SenderRoot) {
+                  String suffix = node.equals(senderString) ? "(String)" :
+                          node.equals(senderInt) ? "(int)" : "(reference type)";
+                  return "SenderRoot " + suffix;
+                }
+                return node.getClass().getSimpleName();
+              })
+              .dumpAsPNG(Paths.get("attribute.png"));
+    }
+
+    communicateBoth();
+  }
+
+  @Override
+  protected void communicateOnlyUpdatedValue() throws IOException, InterruptedException {
+    checker.put(CHECK_BASIC, (String) null)
+            .put(CHECK_SIMPLE, (String) null)
+            .put(CHECK_TRANSFORMED, (String) null)
+            .put(CHECK_A, (String) null)
+            .put(CHECK_NTA, (String) null)
+            .put(CHECK_COLLECTION, (String) null);
+
+    communicateBoth();
+  }
+
+  private void communicateBoth() throws IOException {
+    // basic, simple(2), collection(2) <-- senderString
+    // transformed(2), circular(2)     <-- senderInt
+    // ref-type(2), nta(2)             <-- senderA
+    checker.check();
+
+    senderString.setInput("test-01");
+    checker.addToNumberOfValues(5) // basic, simple(2), collection(2)
+            .put(CHECK_BASIC, "test-01")
+            .put(CHECK_SIMPLE, "test-01Post")
+            .put(CHECK_COLLECTION, "[test-01]")
+            .check();
+
+    // no change for same value
+    senderString.setInput("test-01");
+    checker.check();
+
+    senderString.addA(new A().setValue("test-02").setInner(new Inner().setInnerValue("inner")));
+    checker.addToNumberOfValues(2) // collection(2)
+            .put(CHECK_COLLECTION, "[test-01, test-02]")
+            .check();
+
+    senderInt.setInput("20");
+    checker.addToNumberOfValues(4) // transformed(2), circular(2)
+            .put(CHECK_TRANSFORMED, "20")
+            .put(CHECK_CIRCULAR, "22")
+            .check();
+
+    senderA.setInput("test-03");
+    checker.addToNumberOfValues(4) // ref-type(2), nta(2)
+            .put(CHECK_A, "test-03")
+            .put(CHECK_NTA, "test-03")
+            .check();
+
+    assertTrue(senderString.disconnectSimple(mqttUri(TOPIC_SIMPLE_NO_MAPPING)));
+    assertTrue(senderString.disconnectSimple(mqttUri(TOPIC_SIMPLE_WITH_MAPPING)));
+    senderString.setInput("test-04");
+    checker.addToNumberOfValues(3) // basic, collection(2)
+            .put(CHECK_BASIC, "test-04")
+            .put(CHECK_COLLECTION, "[test-02, test-04]")
+            .check();
+
+    assertTrue(senderString.disconnectCollectionAttribute(mqttUri(TOPIC_COLLECTION_NO_MAPPING)));
+    assertTrue(senderString.disconnectCollectionAttribute(mqttUri(TOPIC_COLLECTION_WITH_MAPPING)));
+    senderString.setInput("test-05");
+    checker.incNumberOfValues() // basic
+            .put(CHECK_BASIC, "test-05")
+            .check();
+
+    assertTrue(senderA.disconnectToNTA(mqttUri(TOPIC_NTA_NO_MAPPING)));
+    senderA.setInput("test-06");
+    checker.addToNumberOfValues(3)
+            .put(CHECK_A, "test-06")
+            .check();
+  }
+
+  private <T> void waitForValue(T expectedValue, Callable<T> callable) {
+    if (isWriteCurrentValue()) {
+      awaitMqtt().until(callable, isEqual(expectedValue));
+    }
+  }
+
+  private <T> void waitForNonNull(Callable<T> callable) {
+    if (isWriteCurrentValue()) {
+      awaitMqtt().until(callable, Predicate.not(isEqual(null)));
+    }
+  }
+
+  private void checkBasic(String name, String expected) {
+    Assertions.assertEquals(Objects.requireNonNullElse(expected, ""), receiverRoot.getFromBasic(), name);
+  }
+
+  private void checkSimple(String name, String expected) {
+    if (expected != null) {
+      assertEquals(expected, receiverRoot.getFromSimpleNoMapping(), "simple");
+      assertEquals(expected + "post", receiverRoot.getFromSimpleWithMapping(), "simple mapped");
+    } else {
+      assertEquals("", receiverRoot.getFromSimpleNoMapping(), "simple null");
+      assertEquals("", receiverRoot.getFromSimpleWithMapping(), "simple mapped null");
+    }
+  }
+
+  private void checkTransformed(String name, String expected) {
+    _checkInt(name, expected, receiverRoot::getFromTransformedNoMapping, receiverRoot::getFromTransformedWithMapping);
+  }
+
+  private void _checkInt(String name, String expected, Supplier<Integer> noMapping, Supplier<Integer> withMapping) {
+    if (expected != null) {
+      assertEquals(Integer.parseInt(expected), noMapping.get(), name);
+      assertEquals(Integer.parseInt(expected) + 1, withMapping.get(), name + " mapped");
+    } else {
+      assertEquals(0, noMapping.get(), name + " null");
+      assertEquals(0, withMapping.get(), name + " mapped null");
+    }
+  }
+
+  private void checkA(String name, String expected) {
+    if (expected != null) {
+      assertA(expected, "1",
+              receiverRoot.getFromReferenceTypeNoMapping(), "ref-type");
+      assertA(expected + "post", "inner1",
+              receiverRoot.getFromReferenceTypeWithMapping(), "ref-type mapped");
+      assertA(expected + "post", "inner2",
+              receiverRoot.getFromNTAWithMapping(), "nta mapped");
+    } else {
+      assertNull(receiverRoot.getFromReferenceTypeNoMapping(), "manual ref-type null");
+      assertNull(receiverRoot.getFromReferenceTypeWithMapping(), "ref-type mapped null");
+      assertNull(receiverRoot.getFromNTAWithMapping(), "nta mapped null");
+    }
+  }
+
+  private void checkNta(String name, String expected) {
+    if (expected != null) {
+      assertA(expected, "2", receiverRoot.getFromNTANoMapping(), "nta");
+    } else {
+      assertNull(receiverRoot.getFromNTANoMapping(), "nta null");
+    }
+  }
+
+  private void checkCircular(String name, String expected) {
+    _checkInt(name, expected, receiverRoot::getFromCircularNoMapping, receiverRoot::getFromCircularWithMapping);
+  }
+
+  private void checkCollection(String name, String expected) {
+    if (expected != null) {
+      assertThat(receiverRoot.getFromCollectionWithMapping()).hasSizeGreaterThan(4).endsWith("post");
+      checkCollectionContent(name, expected, receiverRoot.getFromCollectionNoMapping());
+      checkCollectionContent(name + " mapped", expected, receiverRoot.getFromCollectionWithMapping().substring(0, receiverRoot.getFromCollectionWithMapping().length() - 4));
+    } else {
+      assertEquals("", receiverRoot.getFromCollectionNoMapping(), "collection null");
+      assertEquals("", receiverRoot.getFromCollectionWithMapping(), "collection mapped null");
+    }
+  }
+
+  private void checkCollectionContent(String name, String expected, String actual) {
+    assertThat(actual).as(name)
+            .startsWith("[")
+            .endsWith("]");
+    String[] actualValues = actual.substring(1, actual.length() - 1).split(", ");
+    String[] expectedValues = expected.substring(1, expected.length() - 1).split(", ");
+    assertThat(actualValues).containsExactlyInAnyOrder(expectedValues);
+  }
+
+  private void assertA(String expectedValue, String expectedInner, A actual, String message) {
+    assertEquals(expectedValue, actual.getValue(), message + " value");
+    assertEquals(expectedInner, actual.getInner().getInnerValue(), message + " inner");
+  }
+
+  @Override
+  protected void closeConnections() {
+    if (handler != null) {
+      handler.close();
+    }
+    if (model != null) {
+      model.ragconnectCloseConnections();
+    }
+  }
+
+  private static class ReceiverData {
+    int numberOfValues = 0;
+  }
+}
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ContextFreeSimpleTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ContextFreeSimpleTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..a74b4d9bc47b0a35a413dd7259fc8947f8c6c27c
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ContextFreeSimpleTest.java
@@ -0,0 +1,177 @@
+package org.jastadd.ragconnect.tests;
+
+import contextFreeSimpleInc.ast.A;
+import contextFreeSimpleInc.ast.MqttHandler;
+import contextFreeSimpleInc.ast.Root;
+import contextFreeSimpleInc.ast.SerializationException;
+import org.jastadd.ragconnect.tests.utils.DefaultMappings;
+import org.jastadd.ragconnect.tests.utils.TestChecker;
+import org.jastadd.ragconnect.tests.utils.TestUtils;
+import org.junit.jupiter.api.Tag;
+
+import java.io.IOException;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+import static org.jastadd.ragconnect.tests.utils.TestUtils.mqttUri;
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Test case "context free simple".
+ *
+ * @author rschoene - Initial contribution
+ */
+@Tag("Incremental")
+public class ContextFreeSimpleTest extends AbstractMqttTest {
+
+  private static final String TOPIC_WILDCARD = "context-free/#";
+  private static final String TOPIC_UNNAMED = "context-free/unnamed";
+  private static final String TOPIC_SINGLE = "context-free/single";
+  private static final String TOPIC_SINGLE_ALTERNATIVE = "context-free/double";
+  private static final String TOPIC_OPT = "context-free/opt";
+  private static final String TOPIC_LIST_1 = "context-free/list1";
+  private static final String TOPIC_LIST_2 = "context-free/list2";
+
+  /** Use initially created members as values in {@link #check(String, String, A, A)} */
+  private static final String INITIAL_VALUE = null;
+
+  private Root model;
+  private A unnamedA;
+  private A singleA;
+  private A optA;
+  private A listA1;
+  private A listA2;
+
+  private ReceiverData data;
+  private MqttHandler handler;
+  private TestChecker checker;
+
+  @Override
+  protected void createModel() {
+    model = new Root();
+    unnamedA = new A().setValue("unnamed");
+    singleA = new A().setValue("single");
+    optA = new A().setValue("opt");
+    listA1 = new A().setValue("a1");
+    listA2 = new A().setValue("a2");
+
+    model.setA(unnamedA);
+    model.setSingleA(singleA);
+    model.setOptA(optA);
+    model.addListA(listA1);
+    model.addListA(listA2);
+
+    data = new ReceiverData();
+  }
+
+  @Override
+  protected void setupReceiverAndConnect() throws IOException, InterruptedException {
+    model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS);
+
+    handler = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost());
+    assertTrue(handler.waitUntilReady(2, TimeUnit.SECONDS));
+
+    handler.newConnection(TOPIC_WILDCARD, (topic, bytes) -> data.valuesSent += 1);
+
+    checker = new TestChecker();
+    checker.alwaysWait()
+            .setCheckForString(TOPIC_UNNAMED, (name, expected) -> this.check(name, expected, model.getA(), unnamedA))
+            .setCheckForString(TOPIC_SINGLE, (name, expected) -> this.check(name, expected, model.getSingleA(), singleA))
+            .setCheckForString(TOPIC_OPT, (name, expected) -> this.check(name, expected, model.getOptA(), optA))
+            .setCheckForString(TOPIC_LIST_1, (name, expected) -> this.check(name, expected, model.getListA(0), listA1))
+            .setCheckForString(TOPIC_LIST_2, (name, expected) -> this.check(name, expected, model.getListA(1), listA2));
+
+    assertTrue(unnamedA.connect(mqttUri(TOPIC_UNNAMED)));
+    assertTrue(singleA.connect(mqttUri(TOPIC_SINGLE)));
+    assertTrue(singleA.connect(mqttUri(TOPIC_SINGLE_ALTERNATIVE)));
+    assertTrue(optA.connect(mqttUri(TOPIC_OPT)));
+    assertTrue(listA1.connect(mqttUri(TOPIC_LIST_1)));
+    assertTrue(listA2.connect(mqttUri(TOPIC_LIST_2)));
+  }
+
+  @Override
+  protected void communicateSendInitialValue() throws IOException, InterruptedException {
+    // empty
+  }
+
+  @Override
+  protected void communicateOnlyUpdatedValue() throws IOException, InterruptedException {
+    checker.put(TOPIC_UNNAMED, INITIAL_VALUE)
+            .put(TOPIC_SINGLE, INITIAL_VALUE)
+            .put(TOPIC_OPT, INITIAL_VALUE)
+            .put(TOPIC_LIST_1, INITIAL_VALUE)
+            .put(TOPIC_LIST_2, INITIAL_VALUE);
+
+    checker.check();
+
+    send(TOPIC_UNNAMED, "1");
+    checker.put(TOPIC_UNNAMED, "1").check();
+
+    send(TOPIC_SINGLE, "2");
+    checker.put(TOPIC_SINGLE, "pre2").check();
+
+    send(TOPIC_SINGLE, "2.1");
+    checker.put(TOPIC_SINGLE, "pre2.1").check();
+
+    send(TOPIC_SINGLE, "2.2");
+    checker.put(TOPIC_SINGLE, "pre2.2").check();
+
+    send(TOPIC_OPT, "3");
+    checker.put(TOPIC_OPT, "3post").check();
+
+    send(TOPIC_LIST_1, "4");
+    checker.put(TOPIC_LIST_1, "4").check();
+
+    send(TOPIC_LIST_2, "5");
+    checker.put(TOPIC_LIST_2, "5").check();
+
+    send(TOPIC_SINGLE_ALTERNATIVE, "fix");
+    checker.put(TOPIC_SINGLE, "prefix").check();
+
+    assertTrue(model.getSingleA().disconnect(mqttUri(TOPIC_SINGLE)));
+    send(TOPIC_SINGLE, "6");
+    // no change to previous check since disconnected
+    checker.check();
+
+    send(TOPIC_SINGLE_ALTERNATIVE, "7");
+    // alternative topic is still active
+    checker.put(TOPIC_SINGLE, "pre7").check();
+
+    assertTrue(model.getSingleA().disconnect(mqttUri(TOPIC_SINGLE_ALTERNATIVE)));
+    send(TOPIC_SINGLE_ALTERNATIVE, "8");
+    // no change to previous check since alternative topic is also disconnected now
+    checker.check();
+  }
+
+  private void send(String topic, String value) throws IOException {
+    A a = new A().setValue(value);
+    try {
+      publisher.publish(topic, DefaultMappings.TreeToBytes(a::serialize));
+    } catch (SerializationException e) {
+      throw new IOException(e);
+    }
+  }
+
+  private void check(String name, String expected, A actual, A expectedIfInitial) {
+    if (Objects.equals(INITIAL_VALUE, expected)) {
+      assertEquals(expectedIfInitial, actual, name);
+    } else {
+      assertNotEquals(expectedIfInitial, actual, name);
+      assertEquals(expected, actual.getValue(), name);
+    }
+  }
+
+  @Override
+  protected void closeConnections() {
+    if (handler != null) {
+      handler.close();
+    }
+    if (model != null) {
+      model.ragconnectCloseConnections();
+    }
+  }
+
+  static class ReceiverData {
+    int valuesSent = 0;
+  }
+}
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 d9cf9d92a456a02cd2acba9e5f8990527d5842e9..f72a80a781abfd6e22bebfbc467541c0d371aedd 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
@@ -3,14 +3,16 @@ package org.jastadd.ragconnect.tests;
 import defaultOnlyRead.ast.A;
 import defaultOnlyRead.ast.BoxedTypes;
 import defaultOnlyRead.ast.NativeTypes;
+import org.jastadd.ragconnect.tests.utils.DefaultMappings;
+import org.jastadd.ragconnect.tests.utils.TestUtils;
 import org.junit.jupiter.api.Test;
 
 import java.io.IOException;
 import java.nio.file.Paths;
 import java.util.concurrent.TimeUnit;
 
-import static org.jastadd.ragconnect.tests.TestUtils.mqttUri;
-import static org.jastadd.ragconnect.tests.TestUtils.testJaddContainReferenceToJackson;
+import static org.jastadd.ragconnect.tests.utils.TestUtils.mqttUri;
+import static org.jastadd.ragconnect.tests.utils.TestUtils.testJaddContainReferenceToJackson;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
@@ -65,7 +67,7 @@ public class DefaultOnlyReadTest extends AbstractMqttTest {
   }
 
   @Override
-  protected void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException {
+  protected void setupReceiverAndConnect() throws IOException {
     model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS);
 
     assertTrue(integers.connectBooleanValue(mqttUri(TOPIC_NATIVE_BOOLEAN)));
@@ -119,22 +121,22 @@ public class DefaultOnlyReadTest extends AbstractMqttTest {
     final char expectedCharValue = 'c';
     final String expectedStringValue = "6.3";
 
-    publisher.publish(TOPIC_NATIVE_BOOLEAN, TestUtils.DefaultMappings.BoolToBytes(expectedBooleanValue));
-    publisher.publish(TOPIC_NATIVE_INT, TestUtils.DefaultMappings.IntToBytes(expectedIntValue));
-    publisher.publish(TOPIC_NATIVE_SHORT, TestUtils.DefaultMappings.ShortToBytes(expectedShortValue));
-    publisher.publish(TOPIC_NATIVE_LONG, TestUtils.DefaultMappings.LongToBytes(expectedLongValue));
-    publisher.publish(TOPIC_NATIVE_FLOAT, TestUtils.DefaultMappings.FloatToBytes(expectedFloatValue));
-    publisher.publish(TOPIC_NATIVE_DOUBLE, TestUtils.DefaultMappings.DoubleToBytes(expectedDoubleValue));
-    publisher.publish(TOPIC_NATIVE_CHAR, TestUtils.DefaultMappings.CharToBytes(expectedCharValue));
-    publisher.publish(TOPIC_NATIVE_STRING, TestUtils.DefaultMappings.StringToBytes(expectedStringValue));
-
-    publisher.publish(TOPIC_BOXED_BOOLEAN, TestUtils.DefaultMappings.BoolToBytes(expectedBooleanValue));
-    publisher.publish(TOPIC_BOXED_INTEGER, TestUtils.DefaultMappings.IntToBytes(expectedIntValue));
-    publisher.publish(TOPIC_BOXED_SHORT, TestUtils.DefaultMappings.ShortToBytes(expectedShortValue));
-    publisher.publish(TOPIC_BOXED_LONG, TestUtils.DefaultMappings.LongToBytes(expectedLongValue));
-    publisher.publish(TOPIC_BOXED_FLOAT, TestUtils.DefaultMappings.FloatToBytes(expectedFloatValue));
-    publisher.publish(TOPIC_BOXED_DOUBLE, TestUtils.DefaultMappings.DoubleToBytes(expectedDoubleValue));
-    publisher.publish(TOPIC_BOXED_CHARACTER, TestUtils.DefaultMappings.CharToBytes(expectedCharValue));
+    publisher.publish(TOPIC_NATIVE_BOOLEAN, DefaultMappings.BoolToBytes(expectedBooleanValue));
+    publisher.publish(TOPIC_NATIVE_INT, DefaultMappings.IntToBytes(expectedIntValue));
+    publisher.publish(TOPIC_NATIVE_SHORT, DefaultMappings.ShortToBytes(expectedShortValue));
+    publisher.publish(TOPIC_NATIVE_LONG, DefaultMappings.LongToBytes(expectedLongValue));
+    publisher.publish(TOPIC_NATIVE_FLOAT, DefaultMappings.FloatToBytes(expectedFloatValue));
+    publisher.publish(TOPIC_NATIVE_DOUBLE, DefaultMappings.DoubleToBytes(expectedDoubleValue));
+    publisher.publish(TOPIC_NATIVE_CHAR, DefaultMappings.CharToBytes(expectedCharValue));
+    publisher.publish(TOPIC_NATIVE_STRING, DefaultMappings.StringToBytes(expectedStringValue));
+
+    publisher.publish(TOPIC_BOXED_BOOLEAN, DefaultMappings.BoolToBytes(expectedBooleanValue));
+    publisher.publish(TOPIC_BOXED_INTEGER, DefaultMappings.IntToBytes(expectedIntValue));
+    publisher.publish(TOPIC_BOXED_SHORT, DefaultMappings.ShortToBytes(expectedShortValue));
+    publisher.publish(TOPIC_BOXED_LONG, DefaultMappings.LongToBytes(expectedLongValue));
+    publisher.publish(TOPIC_BOXED_FLOAT, DefaultMappings.FloatToBytes(expectedFloatValue));
+    publisher.publish(TOPIC_BOXED_DOUBLE, DefaultMappings.DoubleToBytes(expectedDoubleValue));
+    publisher.publish(TOPIC_BOXED_CHARACTER, DefaultMappings.CharToBytes(expectedCharValue));
 
     TestUtils.waitForMqtt();
 
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 d8ce0c083148b671590c6129d16f8d3ed071e313..b56f5ddd0e8fd6e35fa5a37013d86cae91e99b39 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
@@ -4,13 +4,15 @@ import defaultOnlyWrite.ast.A;
 import defaultOnlyWrite.ast.BoxedTypesSyn;
 import defaultOnlyWrite.ast.MqttHandler;
 import defaultOnlyWrite.ast.NativeTypesSyn;
+import org.jastadd.ragconnect.tests.utils.DefaultMappings;
+import org.jastadd.ragconnect.tests.utils.TestUtils;
 import org.junit.jupiter.api.Test;
 
 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.utils.TestUtils.*;
 import static org.junit.jupiter.api.Assertions.*;
 
 /**
@@ -94,7 +96,7 @@ public class DefaultOnlyWriteTest extends AbstractMqttTest {
   }
 
   @Override
-  protected void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException {
+  protected void setupReceiverAndConnect() throws IOException {
     model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS);
 
     receiver = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost());
@@ -137,39 +139,39 @@ public class DefaultOnlyWriteTest extends AbstractMqttTest {
     dataNormal = createReceiver(false);
     dataTransformed = createReceiver(true);
 
-    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));
+    assertTrue(nativeIntegers.connectBooleanValue(mqttUri(TOPIC_NATIVE_BOOLEAN), isWriteCurrentValue()));
+    assertTrue(nativeIntegers.connectIntValue(mqttUri(TOPIC_NATIVE_INT), isWriteCurrentValue()));
+    assertTrue(nativeIntegers.connectShortValue(mqttUri(TOPIC_NATIVE_SHORT), isWriteCurrentValue()));
+    assertTrue(nativeIntegers.connectLongValue(mqttUri(TOPIC_NATIVE_LONG), isWriteCurrentValue()));
+    assertTrue(nativeFloats.connectFloatValue(mqttUri(TOPIC_NATIVE_FLOAT), isWriteCurrentValue()));
+    assertTrue(nativeFloats.connectDoubleValue(mqttUri(TOPIC_NATIVE_DOUBLE), isWriteCurrentValue()));
+    assertTrue(nativeChars.connectCharValue(mqttUri(TOPIC_NATIVE_CHAR), isWriteCurrentValue()));
+    assertTrue(nativeChars.connectStringValue(mqttUri(TOPIC_NATIVE_STRING), isWriteCurrentValue()));
+
+    assertTrue(nativeIntegers.connectBooleanValueTransformed(mqttUri(TOPIC_NATIVE_BOOLEAN_TRANSFORMED), isWriteCurrentValue()));
+    assertTrue(nativeIntegers.connectIntValueTransformed(mqttUri(TOPIC_NATIVE_INT_TRANSFORMED), isWriteCurrentValue()));
+    assertTrue(nativeIntegers.connectShortValueTransformed(mqttUri(TOPIC_NATIVE_SHORT_TRANSFORMED), isWriteCurrentValue()));
+    assertTrue(nativeIntegers.connectLongValueTransformed(mqttUri(TOPIC_NATIVE_LONG_TRANSFORMED), isWriteCurrentValue()));
+    assertTrue(nativeFloats.connectFloatValueTransformed(mqttUri(TOPIC_NATIVE_FLOAT_TRANSFORMED), isWriteCurrentValue()));
+    assertTrue(nativeFloats.connectDoubleValueTransformed(mqttUri(TOPIC_NATIVE_DOUBLE_TRANSFORMED), isWriteCurrentValue()));
+    assertTrue(nativeChars.connectCharValueTransformed(mqttUri(TOPIC_NATIVE_CHAR_TRANSFORMED), isWriteCurrentValue()));
+    assertTrue(nativeChars.connectStringValueTransformed(mqttUri(TOPIC_NATIVE_STRING_TRANSFORMED), isWriteCurrentValue()));
+
+    assertTrue(boxedIntegers.connectBooleanValue(mqttUri(TOPIC_BOXED_BOOLEAN), isWriteCurrentValue()));
+    assertTrue(boxedIntegers.connectIntValue(mqttUri(TOPIC_BOXED_INTEGER), isWriteCurrentValue()));
+    assertTrue(boxedIntegers.connectShortValue(mqttUri(TOPIC_BOXED_SHORT), isWriteCurrentValue()));
+    assertTrue(boxedIntegers.connectLongValue(mqttUri(TOPIC_BOXED_LONG), isWriteCurrentValue()));
+    assertTrue(boxedFloats.connectFloatValue(mqttUri(TOPIC_BOXED_FLOAT), isWriteCurrentValue()));
+    assertTrue(boxedFloats.connectDoubleValue(mqttUri(TOPIC_BOXED_DOUBLE), isWriteCurrentValue()));
+    assertTrue(boxedChars.connectCharValue(mqttUri(TOPIC_BOXED_CHARACTER), isWriteCurrentValue()));
+
+    assertTrue(boxedIntegers.connectBooleanValueTransformed(mqttUri(TOPIC_BOXED_BOOLEAN_TRANSFORMED), isWriteCurrentValue()));
+    assertTrue(boxedIntegers.connectIntValueTransformed(mqttUri(TOPIC_BOXED_INTEGER_TRANSFORMED), isWriteCurrentValue()));
+    assertTrue(boxedIntegers.connectShortValueTransformed(mqttUri(TOPIC_BOXED_SHORT_TRANSFORMED), isWriteCurrentValue()));
+    assertTrue(boxedIntegers.connectLongValueTransformed(mqttUri(TOPIC_BOXED_LONG_TRANSFORMED), isWriteCurrentValue()));
+    assertTrue(boxedFloats.connectFloatValueTransformed(mqttUri(TOPIC_BOXED_FLOAT_TRANSFORMED), isWriteCurrentValue()));
+    assertTrue(boxedFloats.connectDoubleValueTransformed(mqttUri(TOPIC_BOXED_DOUBLE_TRANSFORMED), isWriteCurrentValue()));
+    assertTrue(boxedChars.connectCharValueTransformed(mqttUri(TOPIC_BOXED_CHARACTER_TRANSFORMED), isWriteCurrentValue()));
   }
 
   private ReceiverData createReceiver(boolean transformed) {
@@ -177,63 +179,63 @@ public class DefaultOnlyWriteTest extends AbstractMqttTest {
 
     receiver.newConnection(transformed ? TOPIC_NATIVE_BOOLEAN_TRANSFORMED : TOPIC_NATIVE_BOOLEAN, bytes -> {
       result.numberOfNativeBoolValues += 1;
-      result.lastNativeBoolValue = TestUtils.DefaultMappings.BytesToBool(bytes);
+      result.lastNativeBoolValue = DefaultMappings.BytesToBool(bytes);
     });
     receiver.newConnection(transformed ? TOPIC_NATIVE_INT_TRANSFORMED : TOPIC_NATIVE_INT, bytes -> {
       result.numberOfNativeIntValues += 1;
-      result.lastNativeIntValue = TestUtils.DefaultMappings.BytesToInt(bytes);
+      result.lastNativeIntValue = DefaultMappings.BytesToInt(bytes);
     });
     receiver.newConnection(transformed ? TOPIC_NATIVE_SHORT_TRANSFORMED : TOPIC_NATIVE_SHORT, bytes -> {
       result.numberOfNativeShortValues += 1;
-      result.lastNativeShortValue = TestUtils.DefaultMappings.BytesToShort(bytes);
+      result.lastNativeShortValue = DefaultMappings.BytesToShort(bytes);
     });
     receiver.newConnection(transformed ? TOPIC_NATIVE_LONG_TRANSFORMED : TOPIC_NATIVE_LONG, bytes -> {
       result.numberOfNativeLongValues += 1;
-      result.lastNativeLongValue = TestUtils.DefaultMappings.BytesToLong(bytes);
+      result.lastNativeLongValue = DefaultMappings.BytesToLong(bytes);
     });
     receiver.newConnection(transformed ? TOPIC_NATIVE_FLOAT_TRANSFORMED : TOPIC_NATIVE_FLOAT, bytes -> {
       result.numberOfNativeFloatValues += 1;
-      result.lastNativeFloatValue = TestUtils.DefaultMappings.BytesToFloat(bytes);
+      result.lastNativeFloatValue = DefaultMappings.BytesToFloat(bytes);
     });
     receiver.newConnection(transformed ? TOPIC_NATIVE_DOUBLE_TRANSFORMED : TOPIC_NATIVE_DOUBLE, bytes -> {
       result.numberOfNativeDoubleValues += 1;
-      result.lastNativeDoubleValue = TestUtils.DefaultMappings.BytesToDouble(bytes);
+      result.lastNativeDoubleValue = DefaultMappings.BytesToDouble(bytes);
     });
     receiver.newConnection(transformed ? TOPIC_NATIVE_CHAR_TRANSFORMED : TOPIC_NATIVE_CHAR, bytes -> {
       result.numberOfNativeCharValues += 1;
-      result.lastNativeCharValue = TestUtils.DefaultMappings.BytesToChar(bytes);
+      result.lastNativeCharValue = DefaultMappings.BytesToChar(bytes);
     });
     receiver.newConnection(transformed ? TOPIC_NATIVE_STRING_TRANSFORMED : TOPIC_NATIVE_STRING, bytes -> {
       result.numberOfNativeStringValues += 1;
-      result.lastNativeStringValue = TestUtils.DefaultMappings.BytesToString(bytes);
+      result.lastNativeStringValue = DefaultMappings.BytesToString(bytes);
     });
     receiver.newConnection(transformed ? TOPIC_BOXED_BOOLEAN_TRANSFORMED : TOPIC_BOXED_BOOLEAN, bytes -> {
       result.numberOfBoxedBoolValues += 1;
-      result.lastBoxedBoolValue = TestUtils.DefaultMappings.BytesToBool(bytes);
+      result.lastBoxedBoolValue = DefaultMappings.BytesToBool(bytes);
     });
     receiver.newConnection(transformed ? TOPIC_BOXED_INTEGER_TRANSFORMED : TOPIC_BOXED_INTEGER, bytes -> {
       result.numberOfBoxedIntValues += 1;
-      result.lastBoxedIntValue = TestUtils.DefaultMappings.BytesToInt(bytes);
+      result.lastBoxedIntValue = DefaultMappings.BytesToInt(bytes);
     });
     receiver.newConnection(transformed ? TOPIC_BOXED_SHORT_TRANSFORMED : TOPIC_BOXED_SHORT, bytes -> {
       result.numberOfBoxedShortValues += 1;
-      result.lastBoxedShortValue = TestUtils.DefaultMappings.BytesToShort(bytes);
+      result.lastBoxedShortValue = DefaultMappings.BytesToShort(bytes);
     });
     receiver.newConnection(transformed ? TOPIC_BOXED_LONG_TRANSFORMED : TOPIC_BOXED_LONG, bytes -> {
       result.numberOfBoxedLongValues += 1;
-      result.lastBoxedLongValue = TestUtils.DefaultMappings.BytesToLong(bytes);
+      result.lastBoxedLongValue = DefaultMappings.BytesToLong(bytes);
     });
     receiver.newConnection(transformed ? TOPIC_BOXED_FLOAT_TRANSFORMED : TOPIC_BOXED_FLOAT, bytes -> {
       result.numberOfBoxedFloatValues += 1;
-      result.lastBoxedFloatValue = TestUtils.DefaultMappings.BytesToFloat(bytes);
+      result.lastBoxedFloatValue = DefaultMappings.BytesToFloat(bytes);
     });
     receiver.newConnection(transformed ? TOPIC_BOXED_DOUBLE_TRANSFORMED : TOPIC_BOXED_DOUBLE, bytes -> {
       result.numberOfBoxedDoubleValues += 1;
-      result.lastBoxedDoubleValue = TestUtils.DefaultMappings.BytesToDouble(bytes);
+      result.lastBoxedDoubleValue = DefaultMappings.BytesToDouble(bytes);
     });
     receiver.newConnection(transformed ? TOPIC_BOXED_CHARACTER_TRANSFORMED : TOPIC_BOXED_CHARACTER, bytes -> {
       result.numberOfBoxedCharValues += 1;
-      result.lastBoxedCharValue = TestUtils.DefaultMappings.BytesToChar(bytes);
+      result.lastBoxedCharValue = DefaultMappings.BytesToChar(bytes);
     });
     return result;
   }
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Errors.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ErrorsTest.java
similarity index 59%
rename from ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Errors.java
rename to ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ErrorsTest.java
index 4d32adff23abd12f2052f320466b79dc0f8673f5..4853b6a7e62f6e71878b37700a72fe2515655200 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Errors.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ErrorsTest.java
@@ -1,8 +1,6 @@
 package org.jastadd.ragconnect.tests;
 
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-import org.junit.jupiter.api.Assertions;
+import org.jastadd.ragconnect.tests.utils.TestUtils;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 
@@ -10,20 +8,21 @@ 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.assertj.core.api.Assertions.assertThat;
-import static org.jastadd.ragconnect.tests.TestUtils.readFile;
+import static org.jastadd.ragconnect.tests.utils.TestUtils.readFile;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
-public class Errors {
+/**
+ * Test error messages.
+ *
+ * @author rschoene - Initial contribution
+ */
+public class ErrorsTest extends RagConnectTest {
 
-  private static final Logger logger = LogManager.getLogger(Errors.class);
-  private static final String FILENAME_PATTERN = "$FILENAME";
   private static final String ERROR_DIRECTORY = "errors/";
   private static final String OUTPUT_DIRECTORY = TestUtils.OUTPUT_DIRECTORY_PREFIX + ERROR_DIRECTORY;
 
@@ -37,12 +36,12 @@ public class Errors {
 
   @Test
   void testStandardErrors() throws IOException {
-    test("Standard", "A","Standard");
+    test("Standard", "A", "Standard");
   }
 
   @Test
   void testTwoPartsErrors() throws IOException {
-    test("Part", "A","Part1", "Part2");
+    test("Part", "A", "Part1", "Part2");
   }
 
   @SuppressWarnings("SameParameterValue")
@@ -53,23 +52,12 @@ public class Errors {
         .collect(Collectors.toList());
     Path outPath = TestUtils.runCompiler(grammarFile, connectFiles, rootNode, ERROR_DIRECTORY, 1);
 
-    final String startOfErrorsPattern = "SEVERE: Errors:";
+    final String startOfErrorsPattern = "Errors:\n";
     String out = readFile(outPath, Charset.defaultCharset());
     assertThat(out).contains(startOfErrorsPattern);
-    out = out.substring(out.indexOf(startOfErrorsPattern) + 16);
+    out = out.substring(out.indexOf(startOfErrorsPattern) + startOfErrorsPattern.length());
 
-    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"))
-        .sorted()
-        .filter(s -> !s.isEmpty() && !s.startsWith("//"))
-        .collect(Collectors.toList());
-
-    Assertions.assertLinesMatch(expectedList, outList);
+    TestUtils.assertLinesMatch(ERROR_DIRECTORY, expectedName, 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 5ad824bcd8de42d998d7825c6930892ecc57e37d..cc9facae3c261e82600ff15779406e5196c30403 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
@@ -3,6 +3,8 @@ package org.jastadd.ragconnect.tests;
 import com.google.protobuf.InvalidProtocolBufferException;
 import config.Config.RobotConfig;
 import example.ast.*;
+import org.jastadd.ragconnect.tests.utils.TestUtils;
+import org.jastadd.ragconnect.tests.utils.TestChecker;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import robot.RobotStateOuterClass.RobotState;
@@ -10,8 +12,10 @@ import robot.RobotStateOuterClass.RobotState;
 import java.io.IOException;
 import java.util.concurrent.TimeUnit;
 
-import static org.jastadd.ragconnect.tests.TestUtils.mqttUri;
-import static org.junit.jupiter.api.Assertions.*;
+import static org.jastadd.ragconnect.tests.utils.TestUtils.mqttUri;
+import static org.jastadd.ragconnect.tests.utils.TestUtils.waitForMqtt;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 /**
  * Test case "example".
@@ -29,6 +33,7 @@ public class ExampleTest extends AbstractMqttTest {
   private Link link1;
   private Link link2;
   private MqttHandler handler;
+  private TestChecker checker;
   private ReceiverData data;
 
   @BeforeEach
@@ -74,7 +79,7 @@ public class ExampleTest extends AbstractMqttTest {
   }
 
   @Override
-  protected void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException {
+  protected void setupReceiverAndConnect() throws IOException {
     model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS);
 
     handler = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost());
@@ -87,6 +92,12 @@ public class ExampleTest extends AbstractMqttTest {
 
     data = new ReceiverData();
 
+    checker = new TestChecker();
+    checker.setActualNumberOfValues(() -> data.numberOfConfigs)
+            .setCheckForObject(TOPIC_JOINT1, (name, expected) -> assertEquals(expected, link1.getCurrentPosition(), name))
+            .setCheckForObject(TOPIC_JOINT2, (name, expected) -> assertEquals(expected, link2.getCurrentPosition(), name))
+    ;
+
     handler.newConnection(TOPIC_CONFIG, bytes -> {
       data.numberOfConfigs += 1;
       try {
@@ -97,7 +108,7 @@ public class ExampleTest extends AbstractMqttTest {
       }
     });
 
-    assertTrue(robotArm.connectAppropriateSpeed(mqttUri(TOPIC_CONFIG), writeCurrentValue));
+    assertTrue(robotArm.connectAppropriateSpeed(mqttUri(TOPIC_CONFIG), isWriteCurrentValue()));
     assertTrue(link1.connectCurrentPosition(mqttUri(TOPIC_JOINT1)));
     assertTrue(link2.connectCurrentPosition(mqttUri(TOPIC_JOINT2)));
   }
@@ -105,135 +116,72 @@ public class ExampleTest extends AbstractMqttTest {
   @Override
   protected void communicateSendInitialValue() throws InterruptedException {
     // joint is currently within the safety zone, so speed should be low
-    TestUtils.waitForMqtt();
-    assertEquals(0, TestCounter.INSTANCE.numberParseLinkState);
-    assertEquals(0, TestCounter.INSTANCE.numberLinkStateToIntPosition);
-    assertEquals(1, TestCounter.INSTANCE.numberInSafetyZone);
-    assertEquals(1, TestCounter.INSTANCE.numberCreateSpeedMessage);
-    assertEquals(1, TestCounter.INSTANCE.numberSerializeRobotConfig);
-    assertEquals(1, data.numberOfConfigs);
-    assertFalse(data.failedLastConversion);
-    assertEquals(robotArm.speedLow(), data.lastConfig.getSpeed(), TestUtils.DELTA);
-
-    // change position of the first joint out of the safety zone, second still in
-    sendData(TOPIC_JOINT1, 0.2f, 0.2f, 0.2f);
-
-    // still in safety zone, so no update should have been sent
-    TestUtils.waitForMqtt();
-    assertEquals(makePosition(2, 2, 2), link1.getCurrentPosition());
-    assertEquals(1, TestCounter.INSTANCE.numberParseLinkState);
-    assertEquals(1, TestCounter.INSTANCE.numberLinkStateToIntPosition);
-    assertEquals(2, TestCounter.INSTANCE.numberInSafetyZone);
-    assertEquals(2, TestCounter.INSTANCE.numberCreateSpeedMessage);
-    assertEquals(2, TestCounter.INSTANCE.numberSerializeRobotConfig);
-    assertEquals(1, data.numberOfConfigs);
-    assertFalse(data.failedLastConversion);
+    checker.incNumberOfValues()
+            .put(TOPIC_JOINT1, makePosition(0, 0, 0))
+            .put(TOPIC_JOINT2, makePosition(-1, 0, 0));
+    checkData(0, 0, 1, 1, 1, false);
     assertEquals(robotArm.speedLow(), data.lastConfig.getSpeed(), TestUtils.DELTA);
 
-    // change position of second joint also out of the safety zone, now speed must be high
-    sendData(TOPIC_JOINT2, 0.3f, 0.4f, 0.5f);
-
-    TestUtils.waitForMqtt();
-    assertEquals(makePosition(3, 4, 5), link2.getCurrentPosition());
-    assertEquals(2, TestCounter.INSTANCE.numberParseLinkState);
-    assertEquals(2, TestCounter.INSTANCE.numberLinkStateToIntPosition);
-    assertEquals(3, TestCounter.INSTANCE.numberInSafetyZone);
-    assertEquals(3, TestCounter.INSTANCE.numberCreateSpeedMessage);
-    assertEquals(3, TestCounter.INSTANCE.numberSerializeRobotConfig);
-    assertEquals(2, data.numberOfConfigs);
-    assertFalse(data.failedLastConversion);
-    assertEquals(robotArm.speedHigh(), data.lastConfig.getSpeed(), TestUtils.DELTA);
-
-    // change position of second joint, no change after mapping
-    sendData(TOPIC_JOINT2, 0.33f, 0.42f, 0.51f);
-
-    TestUtils.waitForMqtt();
-    assertEquals(makePosition(3, 4, 5), link2.getCurrentPosition());
-    assertEquals(3, TestCounter.INSTANCE.numberParseLinkState);
-    assertEquals(3, TestCounter.INSTANCE.numberLinkStateToIntPosition);
-    assertEquals(3, TestCounter.INSTANCE.numberInSafetyZone);
-    assertEquals(3, TestCounter.INSTANCE.numberCreateSpeedMessage);
-    assertEquals(3, TestCounter.INSTANCE.numberSerializeRobotConfig);
-    assertEquals(2, data.numberOfConfigs);
-    assertFalse(data.failedLastConversion);
-
-    // change position of second joint, still out of the safety zone, no update should be sent
-    sendData(TOPIC_JOINT2, 1.3f, 2.4f, 3.5f);
-
-    TestUtils.waitForMqtt();
-    assertEquals(makePosition(13, 24, 35), link2.getCurrentPosition());
-    assertEquals(4, TestCounter.INSTANCE.numberParseLinkState);
-    assertEquals(4, TestCounter.INSTANCE.numberLinkStateToIntPosition);
-    assertEquals(4, TestCounter.INSTANCE.numberInSafetyZone);
-    assertEquals(4, TestCounter.INSTANCE.numberCreateSpeedMessage);
-    assertEquals(4, TestCounter.INSTANCE.numberSerializeRobotConfig);
-    assertEquals(2, data.numberOfConfigs);
-    assertFalse(data.failedLastConversion);
+    communicateBoth(false);
   }
 
   @Override
   protected void communicateOnlyUpdatedValue() throws InterruptedException {
 // no value should have been sent
-    TestUtils.waitForMqtt();
-    assertEquals(0, TestCounter.INSTANCE.numberParseLinkState);
-    assertEquals(0, TestCounter.INSTANCE.numberLinkStateToIntPosition);
-    assertEquals(1, TestCounter.INSTANCE.numberInSafetyZone);
-    assertEquals(1, TestCounter.INSTANCE.numberCreateSpeedMessage);
-    assertEquals(1, TestCounter.INSTANCE.numberSerializeRobotConfig);
-    assertEquals(0, data.numberOfConfigs);
+    checker.put(TOPIC_JOINT1, makePosition(0, 0, 0))
+            .put(TOPIC_JOINT2, makePosition(-1, 0, 0));
+    checkData(0, 0, 1, 1, 1, true);
 
+    communicateBoth(true);
+  }
+
+  private void communicateBoth(boolean failFirstConversion) {
     // change position of the first joint out of the safety zone, second still in
     sendData(TOPIC_JOINT1, 0.2f, 0.2f, 0.2f);
 
     // still in safety zone, hence, no value should have been sent
-    TestUtils.waitForMqtt();
-    assertEquals(makePosition(2, 2, 2), link1.getCurrentPosition());
-    assertEquals(1, TestCounter.INSTANCE.numberParseLinkState);
-    assertEquals(1, TestCounter.INSTANCE.numberLinkStateToIntPosition);
-    assertEquals(2, TestCounter.INSTANCE.numberInSafetyZone);
-    assertEquals(2, TestCounter.INSTANCE.numberCreateSpeedMessage);
-    assertEquals(2, TestCounter.INSTANCE.numberSerializeRobotConfig);
-    assertEquals(0, data.numberOfConfigs);
+    checker.put(TOPIC_JOINT1, makePosition(2, 2, 2));
+    checkData(1, 1, 2, 2, 2, failFirstConversion);
 
     // change position of second joint also out of the safety zone, now speed must be high
     sendData(TOPIC_JOINT2, 0.3f, 0.4f, 0.5f);
 
-    TestUtils.waitForMqtt();
-    assertEquals(makePosition(3, 4, 5), link2.getCurrentPosition());
-    assertEquals(2, TestCounter.INSTANCE.numberParseLinkState);
-    assertEquals(2, TestCounter.INSTANCE.numberLinkStateToIntPosition);
-    assertEquals(3, TestCounter.INSTANCE.numberInSafetyZone);
-    assertEquals(3, TestCounter.INSTANCE.numberCreateSpeedMessage);
-    assertEquals(3, TestCounter.INSTANCE.numberSerializeRobotConfig);
-    assertEquals(1, data.numberOfConfigs);
-    assertFalse(data.failedLastConversion);
+    checker.incNumberOfValues();
+    checker.put(TOPIC_JOINT2, makePosition(3, 4, 5));
+    checkData(2, 2, 3, 3, 3, false);
     assertEquals(robotArm.speedHigh(), data.lastConfig.getSpeed(), TestUtils.DELTA);
 
     // change position of second joint, no change after mapping
     sendData(TOPIC_JOINT2, 0.33f, 0.42f, 0.51f);
 
-    TestUtils.waitForMqtt();
-    assertEquals(makePosition(3, 4, 5), link2.getCurrentPosition());
-    assertEquals(3, TestCounter.INSTANCE.numberParseLinkState);
-    assertEquals(3, TestCounter.INSTANCE.numberLinkStateToIntPosition);
-    assertEquals(3, TestCounter.INSTANCE.numberInSafetyZone);
-    assertEquals(3, TestCounter.INSTANCE.numberCreateSpeedMessage);
-    assertEquals(3, TestCounter.INSTANCE.numberSerializeRobotConfig);
-    assertEquals(1, data.numberOfConfigs);
-    assertFalse(data.failedLastConversion);
+    checker.put(TOPIC_JOINT2, makePosition(3, 4, 5));
+    checkData(3, 3, 3, 3, 3, false);
 
     // change position of second joint, still out of the safety zone, no update should be sent
     sendData(TOPIC_JOINT2, 1.3f, 2.4f, 3.5f);
 
-    TestUtils.waitForMqtt();
-    assertEquals(makePosition(13, 24, 35), link2.getCurrentPosition());
-    assertEquals(4, TestCounter.INSTANCE.numberParseLinkState);
-    assertEquals(4, TestCounter.INSTANCE.numberLinkStateToIntPosition);
-    assertEquals(4, TestCounter.INSTANCE.numberInSafetyZone);
-    assertEquals(4, TestCounter.INSTANCE.numberCreateSpeedMessage);
-    assertEquals(4, TestCounter.INSTANCE.numberSerializeRobotConfig);
-    assertEquals(1, data.numberOfConfigs);
-    assertFalse(data.failedLastConversion);
+    checker.put(TOPIC_JOINT2, makePosition(13, 24, 35));
+    checkData(4, 4, 4, 4, 4, false);
+  }
+
+  private void checkData(int expectedNumberParseLinkState,
+                         int expectedNumberLinkStateToIntPosition,
+                         int expectedNumberInSafetyZone,
+                         int expectedNumberCreateSpeedMessage,
+                         int expectedNumberSerializeRobotConfig,
+                         boolean failedLastConversion) {
+    checker.check();
+    try {
+      waitForMqtt();
+    } catch (InterruptedException e) {
+      e.printStackTrace();
+    }
+    assertEquals(expectedNumberParseLinkState, TestCounter.INSTANCE.numberParseLinkState);
+    assertEquals(expectedNumberLinkStateToIntPosition, TestCounter.INSTANCE.numberLinkStateToIntPosition);
+    assertEquals(expectedNumberInSafetyZone, TestCounter.INSTANCE.numberInSafetyZone);
+    assertEquals(expectedNumberCreateSpeedMessage, TestCounter.INSTANCE.numberCreateSpeedMessage);
+    assertEquals(expectedNumberSerializeRobotConfig, TestCounter.INSTANCE.numberSerializeRobotConfig);
+    assertEquals(failedLastConversion, data.failedLastConversion);
   }
 
   @Override
@@ -249,10 +197,11 @@ public class ExampleTest extends AbstractMqttTest {
   @Test
   public void testFailedConversion() throws IOException {
     createModel();
-    setupReceiverAndConnect(false);
+    setupReceiverAndConnect();
+    int numberOfPreviousConfigs = data.numberOfConfigs;
 
     publisher.publish(TOPIC_JOINT1, "not-a-pandaLinkState".getBytes());
-    assertEquals(0, data.numberOfConfigs);
+    assertEquals(numberOfPreviousConfigs, data.numberOfConfigs);
     assertTrue(data.failedLastConversion);
   }
 
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ForwardingTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ForwardingTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..3efa2e6ee34a711d14aecfaa34aa163f7efc0944
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ForwardingTest.java
@@ -0,0 +1,328 @@
+package org.jastadd.ragconnect.tests;
+
+import forwardingInc.ast.*;
+import org.jastadd.ragconnect.tests.utils.TestChecker;
+import org.jastadd.ragconnect.tests.utils.TestUtils;
+import org.junit.jupiter.api.Tag;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.groups.Tuple.tuple;
+import static org.jastadd.ragconnect.tests.utils.TestUtils.*;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Test case "forwarding".
+ *
+ * @author rschoene - Initial contribution
+ */
+@Tag("Incremental")
+public class ForwardingTest extends AbstractMqttTest {
+
+  private static final String TOPIC_WILDCARD = "forwarding/#";
+  private static final String TOPIC_A_SINGLE = "forwarding/a-single";
+  private static final String TOPIC_A_MANY = "forwarding/a-many";
+  private static final String TOPIC_B_SINGLE = "forwarding/b-single";
+  private static final String TOPIC_C_SINGLE = "forwarding/c-single";
+  private static final String TOPIC_C_MANY = "forwarding/c-many";
+  private static final String TOPIC_D_SINGLE = "forwarding/d-single";
+
+  private static final Random rand = new Random();
+
+  /** Use initially created members as values for {@link #communicateOnlyUpdatedValue()} method */
+  private static final String INITIAL_VALUE = "initial" + rand.nextInt(100);
+
+  private Root model;
+  private SenderRoot senderRoot;
+  private ReceiverRoot receiverRoot;
+  private MqttHandler handler;
+
+  private ReceiverData data;
+
+  private TestChecker checker;
+
+  @Override
+  protected void createModel() {
+    model = new Root();
+//    model.trace().setReceiver(TestUtils::logEvent);
+    senderRoot = new SenderRoot();
+    model.setSenderRoot(senderRoot);
+
+    senderRoot.setA(new A().setValue("as1"));
+    senderRoot.setSingleA(new A().setValue("as1"));
+    senderRoot.setMaybeA(new A().setValue("as1"));
+    senderRoot.addMultipleA(new A().setValue("am1"));
+    senderRoot.addMultipleA(new A().setValue("am2"));
+
+    senderRoot.setB(new B().setValue("bs1"));
+    senderRoot.setSingleB(new B().setValue("bs1"));
+    senderRoot.setMaybeB(new B().setValue("bs1"));
+    senderRoot.addMultipleB(new B().setValue("bs1"));
+    senderRoot.addMultipleB(new B().setValue("bs2"));
+
+    senderRoot.setC(new C().setValue("cs1"));
+    senderRoot.setSingleC(new C().setValue("cs1"));
+    senderRoot.setMaybeC(new C().setValue("cs1"));
+    senderRoot.addMultipleC(new C().setValue("cm1"));
+    senderRoot.addMultipleC(new C().setValue("cm2"));
+
+    senderRoot.setD(new D().setValue("ds1"));
+    senderRoot.setSingleD(new D().setValue("ds1"));
+    senderRoot.setMaybeD(new D().setValue("ds1"));
+    senderRoot.addMultipleD(new D().setValue("ds1"));
+    senderRoot.addMultipleD(new D().setValue("ds2"));
+
+    receiverRoot = new ReceiverRoot();
+    model.setReceiverRoot(receiverRoot);
+
+    receiverRoot.setA(new A().setValue(INITIAL_VALUE));
+    receiverRoot.setB(new B().setValue(INITIAL_VALUE));
+    receiverRoot.setC(new C().setValue(INITIAL_VALUE));
+    receiverRoot.setD(new D().setValue(INITIAL_VALUE));
+
+    data = new ReceiverData();
+  }
+
+  @Override
+  protected void setupReceiverAndConnect() throws IOException, InterruptedException {
+    model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS);
+
+    handler = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost());
+    assertTrue(handler.waitUntilReady(2, TimeUnit.SECONDS));
+
+    handler.publish("test", Boolean.toString(isWriteCurrentValue()).getBytes(StandardCharsets.UTF_8));
+
+    handler.newConnection(TOPIC_WILDCARD, (topic, bytes) -> {
+      data.valuesSent += 1;
+      data.valueSentSinceLastCheck.set(true);
+    });
+
+    checker = new TestChecker();
+    checker.setActualNumberOfValues(() -> data.valuesSent)
+            .setCheckForString(TOPIC_A_SINGLE,
+                    (name, expected) -> assertThat(receiverRoot.getA().getValue()).as(name).isEqualTo(expected))
+            .setCheckForTuple(TOPIC_A_MANY,
+                    (name, expected) -> assertThat(receiverRoot.getManyAList()).extracting("Value").as(name)
+                    .containsExactlyElementsOf(expected.toList()))
+            .setCheckForString(TOPIC_B_SINGLE,
+                    (name, expected) -> assertThat(receiverRoot.getB().getValue()).as(name).isEqualTo(expected))
+            .setCheckForString(TOPIC_C_SINGLE,
+                    (name, expected) -> assertThat(receiverRoot.getC().getValue()).as(name).isEqualTo(expected))
+            .setCheckForTuple(TOPIC_C_MANY,
+                    (name, expected) -> assertThat(receiverRoot.getManyCList()).extracting("Value").as(name)
+                    .containsExactlyElementsOf(expected.toList()))
+            .setCheckForString(TOPIC_D_SINGLE,
+                    (name, expected) -> assertThat(receiverRoot.getD().getValue()).as(name).isEqualTo(expected))
+    ;
+
+    // connect receive
+    assertTrue(receiverRoot.connectA(mqttUri(TOPIC_A_SINGLE)));
+    assertTrue(receiverRoot.connectManyAList(mqttUri(TOPIC_A_MANY)));
+    assertTrue(receiverRoot.connectB(mqttUri(TOPIC_B_SINGLE)));
+    assertTrue(receiverRoot.connectC(mqttUri(TOPIC_C_SINGLE)));
+    assertTrue(receiverRoot.connectManyCList(mqttUri(TOPIC_C_MANY)));
+    assertTrue(receiverRoot.connectD(mqttUri(TOPIC_D_SINGLE)));
+
+    // connect send
+    assertTrueAndWaitForValue(senderRoot.connectA(mqttUri(TOPIC_A_SINGLE), isWriteCurrentValue()));
+    assertTrueAndWaitForValue(senderRoot.connectSingleA(mqttUri(TOPIC_A_SINGLE), isWriteCurrentValue()));
+    assertTrueAndWaitForValue(senderRoot.connectMaybeA(mqttUri(TOPIC_A_SINGLE), isWriteCurrentValue()));
+    assertTrueAndWaitForValue(senderRoot.connectMultipleAList(mqttUri(TOPIC_A_MANY), isWriteCurrentValue()));
+
+    assertTrueAndWaitForValue(senderRoot.getB().connect(mqttUri(TOPIC_B_SINGLE), isWriteCurrentValue()));
+    assertTrueAndWaitForValue(senderRoot.getSingleB().connect(mqttUri(TOPIC_B_SINGLE), isWriteCurrentValue()));
+    assertTrueAndWaitForValue(senderRoot.getMaybeB().connect(mqttUri(TOPIC_B_SINGLE), isWriteCurrentValue()));
+    assertTrueAndWaitForValue(senderRoot.getMultipleB(0).connect(mqttUri(TOPIC_B_SINGLE), isWriteCurrentValue()));
+    assertTrueAndWaitForValue(senderRoot.getMultipleB(1).connect(mqttUri(TOPIC_B_SINGLE), isWriteCurrentValue()));
+
+    assertTrueAndWaitForValue(senderRoot.connectC(mqttUri(TOPIC_C_SINGLE), isWriteCurrentValue()));
+    assertTrueAndWaitForValue(senderRoot.connectSingleC(mqttUri(TOPIC_C_SINGLE), isWriteCurrentValue()));
+    assertTrueAndWaitForValue(senderRoot.connectMaybeC(mqttUri(TOPIC_C_SINGLE), isWriteCurrentValue()));
+    assertTrueAndWaitForValue(senderRoot.connectMultipleCList(mqttUri(TOPIC_C_MANY), isWriteCurrentValue()));
+
+    assertTrueAndWaitForValue(senderRoot.getD().connect(mqttUri(TOPIC_D_SINGLE), isWriteCurrentValue()));
+    assertTrueAndWaitForValue(senderRoot.getSingleD().connect(mqttUri(TOPIC_D_SINGLE), isWriteCurrentValue()));
+    assertTrueAndWaitForValue(senderRoot.getMaybeD().connect(mqttUri(TOPIC_D_SINGLE), isWriteCurrentValue()));
+    assertTrueAndWaitForValue(senderRoot.getMultipleD(0).connect(mqttUri(TOPIC_D_SINGLE), isWriteCurrentValue()));
+    assertTrueAndWaitForValue(senderRoot.getMultipleD(1).connect(mqttUri(TOPIC_D_SINGLE), isWriteCurrentValue()));
+  }
+
+  private void assertTrueAndWaitForValue(boolean actual) {
+    assertTrue(actual);
+    waitForValue();
+  }
+
+  private void waitForValue() {
+    if (isWriteCurrentValue()) {
+      awaitMqtt().until(() -> data.valueSentSinceLastCheck.getAndSet(false));
+    }
+  }
+
+  @Override
+  protected void communicateSendInitialValue() throws IOException, InterruptedException {
+    // Sink.A     <-- Root.A, Root.SingleA, Root.MaybeA (and C)
+    // Sink.ManyA <-- Root.MultipleA (and C)
+    // Sink.B     <-- Root.B, Root.SingleB, Root.MaybeB, indexed Root.MultipleB (and D)
+    // MultipleB += "-other", all C = "pre-" + value, almost all D += "-post", MultipleD += "-other"
+    checker.addToNumberOfValues(18)
+            .put(TOPIC_A_SINGLE, "as1")
+            .put(TOPIC_A_MANY, tuple("am1", "am2"))
+            .put(TOPIC_B_SINGLE, "bs2-other")
+            .put(TOPIC_C_SINGLE, "pre-cs1")
+            .put(TOPIC_C_MANY, tuple("pre-cm1", "pre-cm2"))
+            .put(TOPIC_D_SINGLE, "ds2-other");
+
+    communicateBoth();
+  }
+
+  @Override
+  protected void communicateOnlyUpdatedValue() throws IOException, InterruptedException {
+    // Sink.A     <-- Root.A, Root.SingleA, Root.MaybeA (and C)
+    // Sink.ManyA <-- Root.MultipleA (and C)
+    // Sink.B     <-- Root.B, Root.SingleB, Root.MaybeB, indexed Root.MultipleB (and D)
+    // MultipleB += "-other", all C = "pre-" + value, almost all D += "-post", MultipleD += "-other"
+
+    checker.put(TOPIC_A_SINGLE, INITIAL_VALUE)
+            .put(TOPIC_A_MANY, tuple())
+            .put(TOPIC_B_SINGLE, INITIAL_VALUE)
+            .put(TOPIC_C_SINGLE, INITIAL_VALUE)
+            .put(TOPIC_C_MANY, tuple())
+            .put(TOPIC_D_SINGLE, INITIAL_VALUE);
+
+    communicateBoth();
+  }
+
+  private void communicateBoth() throws IOException {
+    checker.check();
+
+    // --- A ---
+    senderRoot.getA().setValue("test-3");
+    checker.incNumberOfValues().put(TOPIC_A_SINGLE, "test-3").check();
+
+    senderRoot.getSingleA().setValue("test-4");
+    checker.incNumberOfValues().put(TOPIC_A_SINGLE, "test-4").check();
+
+    senderRoot.getMaybeA().setValue("test-5");
+    checker.incNumberOfValues().put(TOPIC_A_SINGLE, "test-5").check();
+
+    // whole list is updated after change of one element
+    senderRoot.getMultipleA(0).setValue("test-6");
+    checker.incNumberOfValues().put(TOPIC_A_MANY, tuple("test-6", "am2")).check();
+
+    senderRoot.addMultipleA(new A().setValue("test-7"));
+    checker.incNumberOfValues().put(TOPIC_A_MANY, tuple("test-6", "am2", "test-7")).check();
+
+    // --- B ---
+    senderRoot.getB().setValue("test-8");
+    checker.incNumberOfValues().put(TOPIC_B_SINGLE, "test-8").check();
+
+    senderRoot.getSingleB().setValue("test-9");
+    checker.incNumberOfValues().put(TOPIC_B_SINGLE, "test-9").check();
+
+    senderRoot.getMaybeB().setValue("test-10");
+    checker.incNumberOfValues().put(TOPIC_B_SINGLE, "test-10").check();
+
+    senderRoot.getMultipleB(0).setValue("test-11");
+    checker.incNumberOfValues().put(TOPIC_B_SINGLE, "test-11-other").check();
+
+    // not connected, so no value is sent (manually wait)
+    senderRoot.addMultipleB(new B().setValue("test-12-ignored"));
+    checker.check();
+
+    // connected, but current value is not sent (manually wait)
+    assertTrue(senderRoot.getMultipleB(2).connect(mqttUri(TOPIC_B_SINGLE), false));
+    checker.check();
+
+    senderRoot.getMultipleB(2).setValue("test-12");
+    checker.incNumberOfValues().put(TOPIC_B_SINGLE, "test-12-other").check();
+
+    // --- C ---
+    senderRoot.getC().setValue("test-13");
+    checker.incNumberOfValues().put(TOPIC_C_SINGLE, "pre-test-13").check();
+
+    senderRoot.getSingleC().setValue("test-14");
+    checker.incNumberOfValues().put(TOPIC_C_SINGLE, "pre-test-14").check();
+
+    senderRoot.getMaybeC().setValue("test-15");
+    checker.incNumberOfValues().put(TOPIC_C_SINGLE, "pre-test-15").check();
+
+    senderRoot.getMultipleC(1).setValue("test-16");
+    checker.incNumberOfValues().put(TOPIC_C_MANY, tuple("pre-cm1", "pre-test-16")).check();
+
+    senderRoot.addMultipleC(new C().setValue("test-17"));
+    checker.incNumberOfValues().put(TOPIC_C_MANY, tuple("pre-cm1", "pre-test-16", "pre-test-17")).check();
+
+    // --- D ---
+    senderRoot.getD().setValue("test-18");
+    checker.incNumberOfValues().put(TOPIC_D_SINGLE, "test-18-post").check();
+
+    senderRoot.getSingleD().setValue("test-19");
+    checker.incNumberOfValues().put(TOPIC_D_SINGLE, "test-19-post").check();
+
+    senderRoot.getMaybeD().setValue("test-20");
+    checker.incNumberOfValues().put(TOPIC_D_SINGLE, "test-20-post").check();
+
+    senderRoot.getMultipleD(0).setValue("test-21");
+    checker.incNumberOfValues().put(TOPIC_D_SINGLE, "test-21-other").check();
+
+    // not connected, so no value is sent (manually wait)
+    senderRoot.addMultipleD(new D().setValue("test-22-ignored"));
+    checker.check();
+
+    // connected, but current value is not sent (manually wait)
+    assertTrue(senderRoot.getMultipleD(2).connect(mqttUri(TOPIC_D_SINGLE), false));
+    checker.check();
+
+    senderRoot.getMultipleD(2).setValue("test-22");
+    checker.incNumberOfValues().put(TOPIC_D_SINGLE, "test-22-other").check();
+
+
+    assertTrue(senderRoot.getMultipleB(1).disconnectSend(mqttUri(TOPIC_B_SINGLE)));
+    senderRoot.getMultipleB(1).setValue("test-23-ignored");
+
+    // disconnect affects complete list
+    senderRoot.getMultipleB(0).setValue("test-24-ignored");
+    senderRoot.getMultipleB(2).setValue("test-25-ignored");
+
+    assertTrue(senderRoot.getMaybeB().disconnectSend(mqttUri(TOPIC_B_SINGLE)));
+    senderRoot.getMaybeB().setValue("test-26-ignored");
+
+    assertTrue(senderRoot.getB().disconnectSend(mqttUri(TOPIC_B_SINGLE)));
+    senderRoot.getB().setValue("test-27-ignored");
+
+    checker.check();
+
+    // same for A
+    assertTrue(senderRoot.disconnectMultipleAList(mqttUri(TOPIC_A_MANY)));
+    senderRoot.getMultipleA(0).setValue("test-28-ignored");
+    senderRoot.getMultipleA(2).setValue("test-29-ignored");
+
+    assertTrue(senderRoot.disconnectMaybeA(mqttUri(TOPIC_A_SINGLE)));
+    senderRoot.getMaybeA().setValue("test-30-ignored");
+
+    assertTrue(senderRoot.disconnectA(mqttUri(TOPIC_A_SINGLE)));
+    senderRoot.getA().setValue("test-31-ignored");
+
+    checker.check();
+  }
+
+  @Override
+  protected void closeConnections() {
+    if (handler != null) {
+      handler.close();
+    }
+    if (model != null) {
+      model.ragconnectCloseConnections();
+    }
+  }
+
+  static class ReceiverData {
+    int valuesSent = 0;
+    AtomicBoolean valueSentSinceLastCheck = new AtomicBoolean(false);
+  }
+}
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 132317701c73cfa9a06fa136b985393c065823ba..2d441addf370f962e9ff40d71b963d00e77e7bbe 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,12 +3,14 @@ package org.jastadd.ragconnect.tests;
 import incremental.ast.A;
 import incremental.ast.B;
 import incremental.ast.MqttHandler;
+import org.jastadd.ragconnect.tests.utils.DefaultMappings;
+import org.jastadd.ragconnect.tests.utils.TestUtils;
 import org.junit.jupiter.api.Tag;
 
 import java.io.IOException;
 import java.util.concurrent.TimeUnit;
 
-import static org.jastadd.ragconnect.tests.TestUtils.mqttUri;
+import static org.jastadd.ragconnect.tests.utils.TestUtils.mqttUri;
 import static org.junit.jupiter.api.Assertions.*;
 
 /**
@@ -45,7 +47,7 @@ public class IncrementalDependencyTest extends AbstractMqttTest {
   }
 
   @Override
-  protected void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException {
+  protected void setupReceiverAndConnect() throws IOException {
     model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS);
 
     handler = new MqttHandler("TestHandler")
@@ -61,21 +63,21 @@ public class IncrementalDependencyTest extends AbstractMqttTest {
 
     handler.newConnection(TOPIC_OUT_A, bytes -> {
       dataA.numberOfStringValues += 1;
-      dataA.lastStringValue = TestUtils.DefaultMappings.BytesToString(bytes);
+      dataA.lastStringValue = DefaultMappings.BytesToString(bytes);
     });
     handler.newConnection(TOPIC_OUT_B1, bytes -> {
       dataB1.numberOfStringValues += 1;
-      dataB1.lastStringValue = TestUtils.DefaultMappings.BytesToString(bytes);
+      dataB1.lastStringValue = DefaultMappings.BytesToString(bytes);
     });
     handler.newConnection(TOPIC_OUT_B2, bytes -> {
       dataB2.numberOfStringValues += 1;
-      dataB2.lastStringValue = TestUtils.DefaultMappings.BytesToString(bytes);
+      dataB2.lastStringValue = DefaultMappings.BytesToString(bytes);
     });
 
     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));
+    assertTrue(model.connectOutputOnA(mqttUri(TOPIC_OUT_A), isWriteCurrentValue()));
+    assertTrue(b1.connectOutputOnB(mqttUri(TOPIC_OUT_B1), isWriteCurrentValue()));
+    assertTrue(b2.connectOutputOnB(mqttUri(TOPIC_OUT_B2), isWriteCurrentValue()));
   }
 
   @Override
@@ -86,47 +88,7 @@ public class IncrementalDependencyTest extends AbstractMqttTest {
         "bStartPostfix",
         "bStartPostfix");
 
-    // send and check new value
-    sendData("101");
-    checkData(2, "a101",
-        "b101Postfix",
-        "b101Postfix");
-
-    // send and check same value
-    sendData("101");
-    checkData(2, "a101",
-        "b101Postfix",
-        "b101Postfix");
-
-    // send and check new value
-    sendData("201");
-    checkData(3, "a201",
-        "b201Postfix",
-        "b201Postfix");
-
-    // send and check new value (b1 should not change)
-    assertTrue(b1.disconnectOutputOnB(mqttUri(TOPIC_OUT_B1)));
-    sendData("301");
-    checkData(4, "a301",
-        3, "b201Postfix",
-        4, "b301Postfix");
-
-    // disconnecting again should yield false
-    assertFalse(b1.disconnectOutputOnB(mqttUri(TOPIC_OUT_B1)));
-
-    // send and check new value (b1 and b2 should not change)
-    assertTrue(b2.disconnectOutputOnB(mqttUri(TOPIC_OUT_B2)));
-    sendData("401");
-    checkData(5, "a401",
-        3, "b201Postfix",
-        4, "b301Postfix");
-
-    // send and check new value (nothing should not change)
-    assertTrue(model.disconnectOutputOnA(mqttUri(TOPIC_OUT_A)));
-    sendData("501");
-    checkData(5, "a401",
-        3, "b201Postfix",
-        4, "b301Postfix");
+    communicateBoth(1);
   }
 
   @Override
@@ -137,30 +99,34 @@ public class IncrementalDependencyTest extends AbstractMqttTest {
         null,
         null);
 
+    communicateBoth(0);
+  }
+
+  private void communicateBoth(int initialNumberOfValues) throws InterruptedException, IOException {
     // send and check new value
     sendData("102");
-    checkData(1, "a102",
+    checkData(initialNumberOfValues + 1, "a102",
         "b102Postfix",
         "b102Postfix");
 
     // send and check same value
     sendData("102");
-    checkData(1, "a102",
+    checkData(initialNumberOfValues + 1, "a102",
         "b102Postfix",
         "b102Postfix");
 
     // send and check new value
     sendData("202");
-    checkData(2, "a202",
+    checkData(initialNumberOfValues + 2, "a202",
         "b202Postfix",
         "b202Postfix");
 
     // send and check new value (b1 should not change)
     assertTrue(b1.disconnectOutputOnB(mqttUri(TOPIC_OUT_B1)));
     sendData("302");
-    checkData(3, "a302",
-        2, "b202Postfix",
-        3, "b302Postfix");
+    checkData(initialNumberOfValues + 3, "a302",
+            initialNumberOfValues + 2, "b202Postfix",
+            initialNumberOfValues + 3, "b302Postfix");
 
     // disconnecting again should yield false
     assertFalse(b1.disconnectOutputOnB(mqttUri(TOPIC_OUT_B1)));
@@ -168,16 +134,16 @@ public class IncrementalDependencyTest extends AbstractMqttTest {
     // send and check new value (b1 and b2 should not change)
     assertTrue(b2.disconnectOutputOnB(mqttUri(TOPIC_OUT_B2)));
     sendData("402");
-    checkData(4, "a402",
-        2, "b202Postfix",
-        3, "b302Postfix");
+    checkData(initialNumberOfValues + 4, "a402",
+            initialNumberOfValues + 2, "b202Postfix",
+            initialNumberOfValues + 3, "b302Postfix");
 
     // send and check new value (nothing should not change)
     assertTrue(model.disconnectOutputOnA(mqttUri(TOPIC_OUT_A)));
     sendData("502");
-    checkData(4, "a402",
-        2, "b202Postfix",
-        3, "b302Postfix");
+    checkData(initialNumberOfValues + 4, "a402",
+            initialNumberOfValues + 2, "b202Postfix",
+            initialNumberOfValues + 3, "b302Postfix");
   }
 
   @Override
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/IndexedSendTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/IndexedSendTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..7ea7ed89fd6f6f57951082f433e4ef530e965ef5
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/IndexedSendTest.java
@@ -0,0 +1,271 @@
+package org.jastadd.ragconnect.tests;
+
+import indexedSendInc.ast.*;
+import io.github.artsok.RepeatedIfExceptionsTest;
+import org.assertj.core.api.Assertions;
+import org.assertj.core.groups.Tuple;
+import org.jastadd.ragconnect.tests.utils.TestChecker;
+import org.jastadd.ragconnect.tests.utils.TestUtils;
+import org.junit.jupiter.api.Tag;
+
+import java.io.IOException;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
+import static org.assertj.core.groups.Tuple.tuple;
+import static org.jastadd.ragconnect.tests.utils.TestUtils.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Test case "indexedSend (Incremental)".
+ *
+ * @author rschoene - Initial contribution
+ */
+@Tag("Incremental")
+public class IndexedSendTest extends AbstractMqttTest {
+
+  private static final String TOPIC_A_MANY_NORMAL_WILDCARD = "a-many/#";
+  private static final String TOPIC_A_MANY_NORMAL_0 = "a-many/0";
+  private static final String TOPIC_A_MANY_NORMAL_1 = "a-many/1";
+  private static final String TOPIC_A_MANY_SUFFIX_WILDCARD = "a-many-suffix/#";
+  private static final String TOPIC_A_MANY_SUFFIX_0 = "a-many-suffix/0";
+  private static final String TOPIC_A_MANY_SUFFIX_1 = "a-many-suffix/1";
+  private static final String TOPIC_A_MANY_SUFFIX_2 = "a-many-suffix/2";
+
+  private static final String CHECK_MANY_A = "many-a";
+  private static final String CHECK_WITH_SUFFIX = "many-a-with-suffix";
+
+  private boolean connectNTAsInstead;
+
+  private MqttHandler handler;
+  private ReceiverData data;
+  private TestChecker checker;
+
+  private Root model;
+  private SenderRoot senderRoot;
+  private ReceiverRoot receiverRoot;
+
+  private A listA0;
+  private A listA1;
+  private A listA0InSuffix;
+  private A listA1InSuffix;
+
+  @Override
+  protected void createModel() {
+    model = new Root();
+    // model.trace().setReceiver(TestUtils::logEvent);
+    senderRoot = new SenderRoot();
+    receiverRoot = new ReceiverRoot();
+    model.setSenderRoot(senderRoot);
+    model.setReceiverRoot(receiverRoot);
+
+    listA0 = createA("am0");
+    listA1 = createA("am1");
+    listA0InSuffix = createA("am0");
+    listA1InSuffix = createA("am1");
+
+    senderRoot.addMultipleA(listA0);
+    senderRoot.addMultipleA(listA1);
+    senderRoot.addMultipleAWithSuffix(listA0InSuffix);
+    senderRoot.addMultipleAWithSuffix(listA1InSuffix);
+  }
+
+  private A createA(String value) {
+    return new A().setValue(value).setInner(new Inner("inner" + value));
+  }
+
+  @Override
+  protected void setupReceiverAndConnect() throws IOException, InterruptedException {
+    model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS);
+    handler = new MqttHandler().setHost(TestUtils.getMqttHost()).dontSendWelcomeMessage();
+    assertTrue(handler.waitUntilReady(2, TimeUnit.SECONDS));
+
+    data = new ReceiverData();
+    assertTrue(handler.newConnection(TOPIC_A_MANY_NORMAL_WILDCARD, bytes -> data.numberOfValues += 1));
+    assertTrue(handler.newConnection(TOPIC_A_MANY_SUFFIX_WILDCARD, bytes -> data.numberOfValues += 1));
+
+    checker = new TestChecker();
+    checker.setActualNumberOfValues(() -> data.numberOfValues)
+            .setCheckForTuple(CHECK_MANY_A, (name, expected) ->
+                    checkList(name, expected, receiverRoot::getManyAList))
+            .setCheckForTuple(CHECK_WITH_SUFFIX, (name, expected) ->
+                    checkList(name, expected, receiverRoot::getManyAWithSuffixList));
+
+    // connect receive
+    assertTrue(receiverRoot.connectManyA(mqttUri(TOPIC_A_MANY_NORMAL_WILDCARD)));
+    assertTrue(receiverRoot.connectManyAWithSuffix(mqttUri(TOPIC_A_MANY_SUFFIX_WILDCARD)));
+
+    // connect send, and wait to receive (if writeCurrentValue is set)
+    assertConnectAOrMultipleA(TOPIC_A_MANY_NORMAL_0, 0);
+    waitForValue(receiverRoot::getNumManyA, 1);
+
+    assertConnectAOrMultipleA(TOPIC_A_MANY_NORMAL_1, 1);
+    waitForValue(receiverRoot::getNumManyA, 2);
+
+    assertConnectComputedAOrMultipleAWithSuffix(TOPIC_A_MANY_SUFFIX_0, 0);
+    waitForValue(receiverRoot::getNumManyAWithSuffix, 1);
+
+    assertConnectComputedAOrMultipleAWithSuffix(TOPIC_A_MANY_SUFFIX_1, 1);
+    waitForValue(receiverRoot::getNumManyAWithSuffix, 2);
+  }
+
+  private void assertConnectAOrMultipleA(String topic, int index) throws IOException {
+    assertTrue(connectNTAsInstead ?
+            senderRoot.connectA(mqttUri(topic), index, isWriteCurrentValue()) :
+            senderRoot.connectMultipleA(mqttUri(topic), index, isWriteCurrentValue()));
+  }
+
+  private void assertConnectComputedAOrMultipleAWithSuffix(String topic, int index) throws IOException {
+    assertTrue(connectNTAsInstead ?
+            senderRoot.connectComputedA(mqttUri(topic), index, isWriteCurrentValue()) :
+            senderRoot.connectMultipleAWithSuffix(mqttUri(topic), index, isWriteCurrentValue()));
+  }
+
+  private void assertDisconnectComputedAOrMultipleAWithSuffix(String topic) throws IOException {
+    assertTrue(connectNTAsInstead ?
+            senderRoot.disconnectComputedA(mqttUri(topic)) :
+            senderRoot.disconnectMultipleAWithSuffix(mqttUri(topic)));
+  }
+
+  private void checkList(String name, Tuple expected, Supplier<JastAddList<A>> actual) {
+    Assertions.assertThat(actual.get()).extracting("Value")
+            .as(name)
+            .containsExactlyElementsOf(expected.toList());
+  }
+
+  private void waitForValue(Callable<Integer> callable, int expectedValue) {
+    if (isWriteCurrentValue()) {
+      awaitMqtt().until(callable, Predicate.isEqual(expectedValue));
+    }
+  }
+
+  @Tag("mqtt")
+  @RepeatedIfExceptionsTest(repeats = TEST_REPETITIONS)
+  public void testCommunicateSendInitialValueWithNTAs() throws IOException, InterruptedException {
+    this.writeCurrentValue = true;
+    this.connectNTAsInstead = true;
+
+    try {
+      createModel();
+      setupReceiverAndConnect();
+
+      logger.info("Calling communicateSendInitialValue");
+      communicateSendInitialValue();
+    } finally {
+      this.connectNTAsInstead = false;
+    }
+  }
+
+  @Tag("mqtt")
+  @RepeatedIfExceptionsTest(repeats = TEST_REPETITIONS)
+  public void testCommunicateOnlyUpdatedValueWithNTAs() throws IOException, InterruptedException {
+    this.writeCurrentValue = false;
+    this.connectNTAsInstead = true;
+
+    try {
+      createModel();
+      setupReceiverAndConnect();
+
+      logger.info("Calling communicateOnlyUpdatedValue");
+      communicateOnlyUpdatedValue();
+    } finally {
+      this.connectNTAsInstead = false;
+    }
+  }
+
+  @Override
+  protected void communicateSendInitialValue() throws IOException, InterruptedException {
+    checker.addToNumberOfValues(4)
+            .put(CHECK_MANY_A, tuple("am0", "am1"))
+            .put(CHECK_WITH_SUFFIX, tuple("am0post", "am1post"));
+
+    communicateBoth("am1", "am0post");
+  }
+
+  @Override
+  protected void communicateOnlyUpdatedValue() throws IOException, InterruptedException {
+    checker.put(CHECK_MANY_A, tuple())
+            .put(CHECK_WITH_SUFFIX, tuple());
+
+    communicateBoth(null, null);
+  }
+
+  private void communicateBoth(String manyAtIndex1, String suffixAtIndex0) throws IOException {
+    // Sink.ManyA           <-- Root.MultipleA
+    // Sink.ManyAWithSuffix <-- Root.MultipleAWithSuffix
+    checker.check();
+
+    assertEquals(listA0.getValue(), senderRoot._ragconnect_MultipleA(0).getValue());
+    listA0.setValue("changedValue");
+    assertEquals(listA0.getValue(), senderRoot._ragconnect_MultipleA(0).getValue());
+
+    checker.incNumberOfValues()
+            .put(CHECK_MANY_A, manyAtIndex1 != null ? tuple("changedValue", manyAtIndex1) : tuple("changedValue"))
+            .check();
+
+    // setting same value must not change data, and must not trigger a new sent message
+    listA0.setValue("changedValue");
+    checker.check();
+
+    listA1.setValue("");
+    checker.incNumberOfValues().put(CHECK_MANY_A, tuple("changedValue", "")).check();
+
+    // first element in suffix-list
+    listA1InSuffix.setValue("re");
+    checker.incNumberOfValues()
+            .put(CHECK_WITH_SUFFIX, suffixAtIndex0 != null ? tuple(suffixAtIndex0, "repost") : tuple("repost"))
+            .check();
+
+    // adding a new element does not automatically send it
+    A listA2InSuffix = createA("out");
+    senderRoot.addMultipleAWithSuffix(listA2InSuffix);
+    checker.check();
+
+    // only after connecting it, the element gets sent (for SendInitialValue case)
+    assertConnectComputedAOrMultipleAWithSuffix(TOPIC_A_MANY_SUFFIX_2, 2);
+    if (isWriteCurrentValue()) {
+      checker.incNumberOfValues()
+              .put(CHECK_WITH_SUFFIX, suffixAtIndex0 != null ? tuple(suffixAtIndex0, "repost", "outpost") : tuple("repost", "outpost"));
+    }
+    checker.check();
+
+    // changing the value of the newly added element will send it
+    listA2InSuffix.setValue("goal");
+    checker.incNumberOfValues()
+            .put(CHECK_WITH_SUFFIX, suffixAtIndex0 != null ? tuple(suffixAtIndex0, "repost", "goalpost") : tuple("repost", "goalpost"));
+    checker.check();
+
+    // after successful disconnect for index 0, no messages will be sent
+    assertDisconnectComputedAOrMultipleAWithSuffix(TOPIC_A_MANY_SUFFIX_0);
+    listA0InSuffix.setValue("willBeIgnored");
+    checker.check();
+
+    // for index 1 (not disconnected), messages will be sent still
+    listA1InSuffix.setValue("sign");
+    checker.incNumberOfValues()
+            .put(CHECK_WITH_SUFFIX, suffixAtIndex0 != null ? tuple(suffixAtIndex0, "signpost", "goalpost") : tuple("signpost", "goalpost"))
+            .check();
+
+    // after successful disconnect for index 1, no messages will be sent anymore
+    assertDisconnectComputedAOrMultipleAWithSuffix(TOPIC_A_MANY_SUFFIX_1);
+    listA1InSuffix.setValue("willBeIgnored");
+    checker.check();
+  }
+
+  @Override
+  protected void closeConnections() {
+    if (handler != null) {
+      handler.close();
+    }
+    if (model != null) {
+      model.ragconnectCloseConnections();
+    }
+  }
+
+  private static class ReceiverData {
+    int numberOfValues = 0;
+  }
+}
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/JavaTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/JavaTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..81d2817c5a1453bb82728edb496d1b41042d31ba
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/JavaTest.java
@@ -0,0 +1,243 @@
+package org.jastadd.ragconnect.tests;
+
+import javaInc.ast.*;
+import org.assertj.core.groups.Tuple;
+import org.jastadd.ragconnect.tests.utils.TestChecker;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.tuple;
+import static org.jastadd.ragconnect.tests.utils.TestUtils.javaUri;
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Testing the Java handler.
+ *
+ * @author rschoene - Initial contribution
+ */
+public class JavaTest extends RagConnectTest {
+
+  private static final String TOPIC_RECEIVE_TOKEN = "receiveToken";
+  private static final String TOPIC_RECEIVE_NODE_PLAIN = "receiveNode/plain";
+  private static final String TOPIC_RECEIVE_NODE_MAPPED = "receiveNode/mapped";
+  private static final String TOPIC_RECEIVE_MANY = "receiveMany";
+  private static final String TOPIC_RECEIVE_NTA = "receiveNTA";
+
+  private static final String TOPIC_SEND_TOKEN = "sendToken";
+  private static final String TOPIC_SEND_NODE = "sendNode";
+  private static final String TOPIC_SEND_MANY = "sendMany";
+  private static final String TOPIC_SEND_NTA = "sendNTA";
+
+  private Root model;
+  private SenderRoot senderRoot;
+  private ReceiverRoot receiverRoot;
+
+  private String lastValueToken;
+  private A lastValueNode;
+  private JastAddList<A> lastValueMany;
+  private A lastValueNTA;
+  private TestChecker checker;
+
+  void createModel() {
+    model = new Root();
+    senderRoot = new SenderRoot().setInput("1").setSendNode(createA("1"));
+    receiverRoot = new ReceiverRoot();
+    model.addSenderRoot(senderRoot);
+    model.setReceiverRoot(receiverRoot);
+  }
+
+  private static A createA(String value) {
+    return new A().setValue(value).setInner(new Inner("inner"));
+  }
+
+  private void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException {
+    // checker
+    checker = new TestChecker();
+    checker.setActualString(TOPIC_SEND_TOKEN, () -> lastValueToken)
+            .setCheckForString(TOPIC_SEND_NODE, (name, expected) -> checkA(expected, lastValueNode, name))
+            .setCheckForTuple(TOPIC_SEND_MANY, (name, expected) -> checkAList(expected, lastValueMany, name))
+            .setCheckForString(TOPIC_SEND_NTA, (name, expected) -> checkA(expected, lastValueNTA, name))
+            .setActualString(TOPIC_RECEIVE_TOKEN, () -> receiverRoot.getSomeToken())
+            .setCheckForString(TOPIC_RECEIVE_NODE_PLAIN, (name, expected) ->
+                    checkA(expected, receiverRoot.getSomeNode(), name))
+            .setCheckForString(TOPIC_RECEIVE_NODE_MAPPED, (name, expected) ->
+                    checkA(expected, receiverRoot.getSomeNodeWithMapping(), name))
+            .setCheckForTuple(TOPIC_RECEIVE_MANY, (name, expected) ->
+                    checkAList(expected, receiverRoot.getManyNodeList(), name))
+            .setCheckForString(TOPIC_RECEIVE_NTA, (name, expected) ->
+                    checkA(expected, receiverRoot.getNTA(), name))
+            .put(TOPIC_RECEIVE_TOKEN, "")
+            .put(TOPIC_RECEIVE_MANY, tuple())
+            .setActualNumberOfValues(() -> 0)
+            .disableManualWait();
+
+    // callbacks
+    model.ragconnectJavaRegisterConsumer(TOPIC_SEND_TOKEN, bytes -> lastValueToken = new String(bytes));
+    model.ragconnectJavaRegisterConsumer(TOPIC_SEND_NODE, bytes -> lastValueNode = ExposingASTNode.INSTANCE.bytesToA(bytes));
+    model.ragconnectJavaRegisterConsumer(TOPIC_SEND_MANY, bytes -> lastValueMany = ExposingASTNode.INSTANCE.bytesToList(bytes));
+    model.ragconnectJavaRegisterConsumer(TOPIC_SEND_NTA, bytes -> lastValueNTA = ExposingASTNode.INSTANCE.bytesToA(bytes));
+
+    // receive
+    receiverRoot.connectSomeToken(javaUri(TOPIC_RECEIVE_TOKEN));
+    receiverRoot.connectSomeNode(javaUri(TOPIC_RECEIVE_NODE_PLAIN));
+    receiverRoot.connectSomeNodeWithMapping(javaUri(TOPIC_RECEIVE_NODE_MAPPED));
+    receiverRoot.connectManyNodeList(javaUri(TOPIC_RECEIVE_MANY));
+    receiverRoot.connectNTA(javaUri(TOPIC_RECEIVE_NTA));
+
+    // send
+    senderRoot.connectSendToken(javaUri(TOPIC_SEND_TOKEN), writeCurrentValue);
+    senderRoot.connectSendNode(javaUri(TOPIC_SEND_NODE), writeCurrentValue);
+    senderRoot.connectSendManyNodeList(javaUri(TOPIC_SEND_MANY), writeCurrentValue);
+    senderRoot.connectSendNTA(javaUri(TOPIC_SEND_NTA), writeCurrentValue);
+  }
+
+  @Test
+  public void testCommunicateSendInitialValue() throws IOException {
+    createModel();
+    setupReceiverAndConnect(true);
+    checker.put(TOPIC_SEND_TOKEN, "")
+            .put(TOPIC_SEND_NODE, "1")
+            .put(TOPIC_SEND_MANY, tuple())
+            .put(TOPIC_SEND_NTA, "1|1")
+    ;
+
+    communicateBoth();
+  }
+
+  @Test
+  public void testCommunicateOnlyUpdatedValue() throws IOException {
+    createModel();
+    setupReceiverAndConnect(false);
+
+    checker.put(TOPIC_SEND_TOKEN, (String) null)
+            .put(TOPIC_SEND_NODE, (String) null)
+            .put(TOPIC_SEND_MANY, (Tuple) null)
+            .put(TOPIC_SEND_NTA, (String) null)
+    ;
+
+    communicateBoth();
+  }
+
+  private void communicateBoth() {
+    checker.check();
+
+    senderRoot.setInput("2");
+    checker.put(TOPIC_SEND_NTA, "2|1").check();
+
+    senderRoot.getSendNode().setValue("3");
+    checker.put(TOPIC_SEND_NODE, "3").check();
+
+    senderRoot.setSendToken("test-4");
+    checker.put(TOPIC_SEND_TOKEN, "test-4").check();
+
+    senderRoot.addSendManyNode(createA("5"));
+    checker.put(TOPIC_SEND_MANY, tuple("5")).check();
+
+    model.ragconnectJavaPush(TOPIC_RECEIVE_TOKEN, ExposingASTNode.INSTANCE.stringToBytes("7"));
+    checker.put(TOPIC_RECEIVE_TOKEN, "7").check();
+
+    model.ragconnectJavaPush(TOPIC_RECEIVE_NODE_PLAIN, ExposingASTNode.INSTANCE.aToBytes(createA("8")));
+    checker.put(TOPIC_RECEIVE_NODE_PLAIN, "8").check();
+
+    model.ragconnectJavaPush(TOPIC_RECEIVE_NODE_MAPPED, ExposingASTNode.INSTANCE.aToBytes(createA("9")));
+    checker.put(TOPIC_RECEIVE_NODE_MAPPED, "9-post|inner-post").check();
+
+    model.ragconnectJavaPush(TOPIC_RECEIVE_MANY, ExposingASTNode.INSTANCE.listToBytes(new JastAddList<>(createA("10"), createA("11"))));
+    checker.put(TOPIC_RECEIVE_MANY, tuple("10", "11")).check();
+
+    model.ragconnectJavaPush(TOPIC_RECEIVE_NTA, ExposingASTNode.INSTANCE.aToBytes(createA("12")));
+    checker.put(TOPIC_RECEIVE_NTA, "12").check();
+
+    System.out.println(model.ragconnectEvaluationCounterSummary());
+  }
+
+  private void checkA(String expectedValue, A actual, String alias) {
+    if (expectedValue == null) {
+      assertNull(actual, alias);
+    } else {
+      final String expectedValueInA, expectedInnerValue;
+      if (expectedValue.contains("|")) {
+        String[] tokens = expectedValue.split("\\|");
+        expectedValueInA = tokens[0];
+        expectedInnerValue = tokens[1];
+      } else {
+        expectedValueInA = expectedValue;
+        expectedInnerValue = "inner";
+      }
+      assertNotNull(actual, alias);
+      assertEquals(expectedValueInA, actual.getValue(), alias);
+      assertNotNull(actual.getInner(), alias + ".inner");
+      assertEquals(expectedInnerValue, actual.getInner().getInnerValue(), alias + ".inner");
+    }
+  }
+
+  private void checkAList(Tuple expectedTuple, JastAddList<A> actual, String alias) {
+    if (expectedTuple == null) {
+      assertNull(actual, alias);
+      return;
+    }
+    List<Object> expected = expectedTuple.toList();
+    assertEquals(expected.size(), actual.getNumChild(), alias + ".size");
+    for (int i = 0, expectedSize = expected.size(); i < expectedSize; i++) {
+      String s = (String) expected.get(i);
+      checkA(s, actual.getChild(i), alias + "[" + i + "]");
+    }
+  }
+
+  @AfterEach
+  public void alwaysCloseConnections() {
+    logger.debug("Closing connections");
+    if (model != null) {
+      model.ragconnectCloseConnections();
+    }
+  }
+
+  @SuppressWarnings({"rawtypes" , "unchecked"})
+  static class ExposingASTNode extends ASTNode {
+    static ExposingASTNode INSTANCE = new ExposingASTNode();
+
+    public A bytesToA(byte[] bytes) {
+      try {
+        return _ragconnect__apply__TreeDefaultBytesToAMapping(bytes);
+      } catch (Exception e) {
+        return null;
+      }
+    }
+
+    public JastAddList<A> bytesToList(byte[] input) {
+      try {
+        return _ragconnect__apply__TreeDefaultBytesToJastAddListAListMapping(input);
+      } catch (Exception e) {
+        return null;
+      }
+    }
+
+    public byte[] aToBytes(A input) {
+      try {
+        return _ragconnect__apply__TreeDefaultAToBytesMapping(input);
+      } catch (Exception e) {
+        return null;
+      }
+    }
+
+    public byte[] listToBytes(JastAddList<A> input) {
+      try {
+        return _ragconnect__apply__TreeDefaultJastAddListToBytesMapping(input);
+      } catch (Exception e) {
+        return null;
+      }
+    }
+
+    public byte[] stringToBytes(String input) {
+      try {
+        return _ragconnect__apply__DefaultStringToBytesMapping(input);
+      } catch (Exception e) {
+        return null;
+      }
+    }
+  }
+
+}
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 52d750223efb102f73e5f63d83aaf8c85ae6847f..519b802bbc7a6b7c3c7719fc13debde86006d2c5 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
@@ -4,11 +4,13 @@ import mapping.ast.A;
 import mapping.ast.BoxedTypes;
 import mapping.ast.MqttHandler;
 import mapping.ast.NativeTypes;
+import org.jastadd.ragconnect.tests.utils.DefaultMappings;
+import org.jastadd.ragconnect.tests.utils.TestUtils;
 
 import java.io.IOException;
 import java.util.concurrent.TimeUnit;
 
-import static org.jastadd.ragconnect.tests.TestUtils.mqttUri;
+import static org.jastadd.ragconnect.tests.utils.TestUtils.mqttUri;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
@@ -45,7 +47,7 @@ public class MappingTest extends AbstractMqttTest {
   }
 
   @Override
-  protected void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException {
+  protected void setupReceiverAndConnect() throws IOException {
     model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS);
 
     handler = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost());
@@ -62,40 +64,40 @@ public class MappingTest extends AbstractMqttTest {
     data = new ReceiverData();
     handler.newConnection(TOPIC_WRITE_NATIVE_INT, bytes -> {
       data.numberOfNativeIntValues += 1;
-      data.lastNativeIntValue = TestUtils.DefaultMappings.BytesToInt(bytes);
+      data.lastNativeIntValue = DefaultMappings.BytesToInt(bytes);
     });
     handler.newConnection(TOPIC_WRITE_NATIVE_SHORT, bytes -> {
       data.numberOfNativeShortValues += 1;
-      data.lastNativeShortValue = TestUtils.DefaultMappings.BytesToShort(bytes);
+      data.lastNativeShortValue = DefaultMappings.BytesToShort(bytes);
     });
     handler.newConnection(TOPIC_WRITE_NATIVE_LONG, bytes -> {
       data.numberOfNativeLongValues += 1;
-      data.lastNativeLongValue = TestUtils.DefaultMappings.BytesToLong(bytes);
+      data.lastNativeLongValue = DefaultMappings.BytesToLong(bytes);
     });
     handler.newConnection(TOPIC_WRITE_NATIVE_FLOAT, bytes -> {
       data.numberOfNativeFloatValues += 1;
-      data.lastNativeFloatValue = TestUtils.DefaultMappings.BytesToFloat(bytes);
+      data.lastNativeFloatValue = DefaultMappings.BytesToFloat(bytes);
     });
     handler.newConnection(TOPIC_WRITE_NATIVE_DOUBLE, bytes -> {
       data.numberOfNativeDoubleValues += 1;
-      data.lastNativeDoubleValue = TestUtils.DefaultMappings.BytesToDouble(bytes);
+      data.lastNativeDoubleValue = DefaultMappings.BytesToDouble(bytes);
     });
     handler.newConnection(TOPIC_WRITE_NATIVE_CHAR, bytes -> {
       data.numberOfNativeCharValues += 1;
-      data.lastNativeCharValue = TestUtils.DefaultMappings.BytesToChar(bytes);
+      data.lastNativeCharValue = DefaultMappings.BytesToChar(bytes);
     });
     handler.newConnection(TOPIC_WRITE_NATIVE_BOOLEAN, bytes -> {
       data.numberOfNativeBooleanValues += 1;
-      data.lastNativeBooleanValue = TestUtils.DefaultMappings.BytesToBool(bytes);
+      data.lastNativeBooleanValue = DefaultMappings.BytesToBool(bytes);
     });
 
-    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.connectWriteIntValue(mqttUri(TOPIC_WRITE_NATIVE_INT), isWriteCurrentValue()));
+    assertTrue(natives.connectWriteShortValue(mqttUri(TOPIC_WRITE_NATIVE_SHORT), isWriteCurrentValue()));
+    assertTrue(natives.connectWriteLongValue(mqttUri(TOPIC_WRITE_NATIVE_LONG), isWriteCurrentValue()));
+    assertTrue(natives.connectWriteFloatValue(mqttUri(TOPIC_WRITE_NATIVE_FLOAT), isWriteCurrentValue()));
+    assertTrue(natives.connectWriteDoubleValue(mqttUri(TOPIC_WRITE_NATIVE_DOUBLE), isWriteCurrentValue()));
+    assertTrue(natives.connectWriteCharValue(mqttUri(TOPIC_WRITE_NATIVE_CHAR), isWriteCurrentValue()));
+    assertTrue(natives.connectWriteBooleanValue(mqttUri(TOPIC_WRITE_NATIVE_BOOLEAN), isWriteCurrentValue()));
 
     assertTrue(natives.connectIntValue(mqttUri(TOPIC_INPUT)));
     assertTrue(natives.connectShortValue(mqttUri(TOPIC_INPUT)));
@@ -119,20 +121,7 @@ public class MappingTest extends AbstractMqttTest {
     checkSendData(1, 1, (short) 1, 1, 1.01f, 1.01d, (char) 1, 1, false);
     // no check for initial received data (no input set yet)
 
-    // send first value
-    sendAndSetData("21", "31");
-    checkSendData(2, 21, (short) 21, 21, 21.01f, 21.01d, (char) 21, 1, false);
-    checkReceiveData(31, (short) 31, 31, 31.01f, 31.01d, (char) 31, true);
-
-    // send same value
-    sendAndSetData("21", "31");
-    checkSendData(2, 21, (short) 21, 21, 21.01f, 21.01d, (char) 21, 1, false);
-    checkReceiveData(31, (short) 31, 31, 31.01f, 31.01d, (char) 31, true);
-
-    // send new value
-    sendAndSetData("22", "32");
-    checkSendData(3, 22, (short) 22, 22, 22.01f, 22.01d, (char) 22, 2, true);
-    checkReceiveData(32, (short) 32, 32, 32.01f, 32.01d, (char) 32, true);
+    communicateBoth(1);
   }
 
   @Override
@@ -140,20 +129,24 @@ public class MappingTest extends AbstractMqttTest {
     checkSendData(0, 0, (short) 0, 0, 0f, 0d, (char) 0, 0, false);
     // no check for initial received data (no input set yet)
 
+    communicateBoth(0);
+  }
+
+  private void communicateBoth(int initialNumberOfValues) throws InterruptedException {
     // send first value
     sendAndSetData("41", "51");
-    checkSendData(1, 41, (short) 41, 41, 41.01f, 41.01d, (char) 41, 1, true);
-    checkReceiveData(51, (short) 51, 51, 51.01f, 51.01d, (char) 51, true);
+    checkSendData(initialNumberOfValues + 1, 41, (short) 41, 41, 41.01f, 41.01d, (char) 41, initialNumberOfValues + 1, true);
+    checkReceiveData(51, (short) 51, 51, 51.01f, 51.01d, (char) 51);
 
     // send same value
     sendAndSetData("41", "51");
-    checkSendData(1, 41, (short) 41, 41, 41.01f, 41.01d, (char) 41, 1, true);
-    checkReceiveData(51, (short) 51, 51, 51.01f, 51.01d, (char) 51, true);
+    checkSendData(initialNumberOfValues + 1, 41, (short) 41, 41, 41.01f, 41.01d, (char) 41, initialNumberOfValues + 1, true);
+    checkReceiveData(51, (short) 51, 51, 51.01f, 51.01d, (char) 51);
 
     // send new value
     sendAndSetData("42", "52");
-    checkSendData(2, 42, (short) 42, 42, 42.01f, 42.01d, (char) 42, 1, true);
-    checkReceiveData(52, (short) 52, 52, 52.01f, 52.01d, (char) 52, true);
+    checkSendData(initialNumberOfValues + 2, 42, (short) 42, 42, 42.01f, 42.01d, (char) 42, initialNumberOfValues + 1, true);
+    checkReceiveData(52, (short) 52, 52, 52.01f, 52.01d, (char) 52);
   }
 
   @Override
@@ -193,14 +186,14 @@ public class MappingTest extends AbstractMqttTest {
     assertEquals(expectedBoolean, data.lastNativeBooleanValue);
   }
 
-  private void checkReceiveData(int expectedInt, short expectedShort, long expectedLong, float expectedFloat, double expectedDouble, char expectedChar, boolean expectedBoolean) {
+  private void checkReceiveData(int expectedInt, short expectedShort, long expectedLong, float expectedFloat, double expectedDouble, char expectedChar) {
     assertEquals(expectedInt, natives.getIntValue());
     assertEquals(expectedShort, natives.getShortValue());
     assertEquals(expectedLong, natives.getLongValue());
     assertEquals(expectedFloat, natives.getFloatValue(), TestUtils.DELTA);
     assertEquals(expectedDouble, natives.getDoubleValue(), TestUtils.DELTA);
     assertEquals(expectedChar, natives.getCharValue());
-    assertEquals(expectedBoolean, natives.getBooleanValue());
+    assertTrue(natives.getBooleanValue());
 
     assertEquals(expectedInt, boxes.getIntValue());
     assertEquals(expectedShort, boxes.getShortValue());
@@ -208,7 +201,7 @@ public class MappingTest extends AbstractMqttTest {
     assertEquals(expectedFloat, boxes.getFloatValue(), TestUtils.DELTA);
     assertEquals(expectedDouble, boxes.getDoubleValue(), TestUtils.DELTA);
     assertEquals(expectedChar, boxes.getCharValue());
-    assertEquals(expectedBoolean, boxes.getBooleanValue());
+    assertEquals(true, boxes.getBooleanValue());
   }
 
   private static class ReceiverData {
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
index 091990a64a31c01a6f2c59b934cfe15b6fb76ece..f8b127ea58ede616ede4c2c44def9d0086877c68 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/MqttHandlerTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/MqttHandlerTest.java
@@ -1,6 +1,7 @@
 package org.jastadd.ragconnect.tests;
 
 import example.ast.MqttHandler;
+import org.jastadd.ragconnect.tests.utils.TestUtils;
 import org.junit.jupiter.api.Tag;
 import org.junit.jupiter.api.Test;
 
@@ -20,7 +21,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
  * @author rschoene - Initial contribution
  */
 @Tag("mqtt")
-public class MqttHandlerTest {
+public class MqttHandlerTest extends RagConnectTest {
 
   @Test
   public void defaultBehaviour() {
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/RagConnectTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/RagConnectTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..e2da1577980ad00b644f3f95c25d7e66ca11be79
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/RagConnectTest.java
@@ -0,0 +1,26 @@
+package org.jastadd.ragconnect.tests;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.TestInfo;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Base for all RagConnect tests.
+ *
+ * @author rschoene - Initial contribution
+ */
+public class RagConnectTest {
+  protected Logger logger = LoggerFactory.getLogger(getClass());
+
+  @BeforeEach
+  public void logStart(TestInfo testInfo) {
+    logger.info("Starting {}.{}", getClass().getSimpleName(), testInfo.getDisplayName());
+  }
+
+  @AfterEach
+  public void logEnd(TestInfo testInfo) {
+    logger.info("Finished {}.{}", getClass().getSimpleName(), testInfo.getDisplayName());
+  }
+}
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 76eac958448c69e0ac1158074a4ddac12f4027f9..155109f8a4010e1bde66c37ad34696b3a1b7964b 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
@@ -1,11 +1,13 @@
 package org.jastadd.ragconnect.tests;
 
+import org.jastadd.ragconnect.tests.utils.DefaultMappings;
+import org.jastadd.ragconnect.tests.utils.TestUtils;
 import read1write2.ast.*;
 
 import java.io.IOException;
 import java.util.concurrent.TimeUnit;
 
-import static org.jastadd.ragconnect.tests.TestUtils.mqttUri;
+import static org.jastadd.ragconnect.tests.utils.TestUtils.mqttUri;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
@@ -56,7 +58,7 @@ public class Read1Write2Test extends AbstractMqttTest {
   }
 
   @Override
-  protected void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException {
+  protected void setupReceiverAndConnect() throws IOException {
     model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS);
 
     handler = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost());
@@ -75,40 +77,40 @@ public class Read1Write2Test extends AbstractMqttTest {
 
     handler.newConnection(TOPIC_SAME_WRITE_INT, bytes -> {
       dataSame.numberOfIntValues += 1;
-      dataSame.lastIntValue = TestUtils.DefaultMappings.BytesToInt(bytes);
+      dataSame.lastIntValue = DefaultMappings.BytesToInt(bytes);
     });
     handler.newConnection(TOPIC_SAME_WRITE_STRING, bytes -> {
       dataSame.numberOfStringValues += 1;
-      dataSame.lastStringValue = TestUtils.DefaultMappings.BytesToString(bytes);
+      dataSame.lastStringValue = DefaultMappings.BytesToString(bytes);
     });
 
     handler.newConnection(TOPIC_DIFFERENT_WRITE1_INT, bytes -> {
       dataOther1.numberOfIntValues += 1;
-      dataOther1.lastIntValue = TestUtils.DefaultMappings.BytesToInt(bytes);
+      dataOther1.lastIntValue = DefaultMappings.BytesToInt(bytes);
     });
     handler.newConnection(TOPIC_DIFFERENT_WRITE1_STRING, bytes -> {
       dataOther1.numberOfStringValues += 1;
-      dataOther1.lastStringValue = TestUtils.DefaultMappings.BytesToString(bytes);
+      dataOther1.lastStringValue = DefaultMappings.BytesToString(bytes);
     });
 
     handler.newConnection(TOPIC_DIFFERENT_WRITE2_INT, bytes -> {
       dataOther2.numberOfIntValues += 1;
-      dataOther2.lastIntValue = TestUtils.DefaultMappings.BytesToInt(bytes);
+      dataOther2.lastIntValue = DefaultMappings.BytesToInt(bytes);
     });
     handler.newConnection(TOPIC_DIFFERENT_WRITE2_STRING, bytes -> {
       dataOther2.numberOfStringValues += 1;
-      dataOther2.lastStringValue = TestUtils.DefaultMappings.BytesToString(bytes);
+      dataOther2.lastStringValue = DefaultMappings.BytesToString(bytes);
     });
 
     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(onSameNonterminal.connectOutInteger(mqttUri(TOPIC_SAME_WRITE_INT), isWriteCurrentValue()));
+    assertTrue(onSameNonterminal.connectOutString(mqttUri(TOPIC_SAME_WRITE_STRING), isWriteCurrentValue()));
 
     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));
+    assertTrue(other1.connectOutInteger(mqttUri(TOPIC_DIFFERENT_WRITE1_INT), isWriteCurrentValue()));
+    assertTrue(other1.connectOutString(mqttUri(TOPIC_DIFFERENT_WRITE1_STRING), isWriteCurrentValue()));
+    assertTrue(other2.connectOutInteger(mqttUri(TOPIC_DIFFERENT_WRITE2_INT), isWriteCurrentValue()));
+    assertTrue(other2.connectOutString(mqttUri(TOPIC_DIFFERENT_WRITE2_STRING), isWriteCurrentValue()));
   }
 
   @Override
@@ -116,23 +118,7 @@ public class Read1Write2Test extends AbstractMqttTest {
     // check initial value
     checkData(1, Integer.parseInt(INITIAL_VALUE), prefixed(INITIAL_VALUE), 1, Integer.parseInt(INITIAL_VALUE), prefixed(INITIAL_VALUE));
 
-    // set new value
-    sendData("2", "3");
-
-    // check new value
-    checkData(2, 2, prefixed("2"), 2, 3, prefixed("3"));
-
-    // set new value
-    sendData("4", "4");
-
-    // check new value
-    checkData(3, 4, prefixed("4"), 3, 4, prefixed("4"));
-
-    // set new value only for same
-    setDataOnlySame("77");
-
-    // check new value
-    checkData(4, 77, prefixed("77"), 3, 4, prefixed("4"));
+    communicateBoth(1);
   }
 
   @Override
@@ -140,23 +126,27 @@ public class Read1Write2Test extends AbstractMqttTest {
 // check initial value
     checkData(0, null, null, 0, null, null);
 
+    communicateBoth(0);
+  }
+
+  private void communicateBoth(int initialNumberOfValues) throws InterruptedException {
     // set new value
     sendData("2", "3");
 
     // check new value
-    checkData(1, 2, prefixed("2"), 1, 3, prefixed("3"));
+    checkData(initialNumberOfValues + 1, 2, prefixed("2"), initialNumberOfValues + 1, 3, prefixed("3"));
 
     // set new value
     sendData("4", "4");
 
     // check new value
-    checkData(2, 4, prefixed("4"), 2, 4, prefixed("4"));
+    checkData(initialNumberOfValues + 2, 4, prefixed("4"), initialNumberOfValues + 2, 4, prefixed("4"));
 
     // set new value only for same
-    setDataOnlySame("78");
+    handler.publish(TOPIC_SAME_READ, "78".getBytes());
 
     // check new value
-    checkData(3, 78, prefixed("78"), 2, 4, prefixed("4"));
+    checkData(initialNumberOfValues + 3, 78, prefixed("78"), initialNumberOfValues + 2, 4, prefixed("4"));
   }
 
   @Override
@@ -178,10 +168,6 @@ public class Read1Write2Test extends AbstractMqttTest {
     publisher.publish(TOPIC_DIFFERENT_READ, inputDifferent.getBytes());
   }
 
-  private void setDataOnlySame(String inputSame) {
-    handler.publish(TOPIC_SAME_READ, inputSame.getBytes());
-  }
-
   private void checkData(int numberOfSameValues, Integer lastSameIntValue, String lastSameStringValue,
                          int numberOfDifferentValues, Integer lastDifferentIntValue,
                          String lastDifferentStringValue) throws InterruptedException {
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 f6a0d33a4b70689a1c883f866d33442defd2b786..0bd22195a4fc2e2fab77b690a65b65e6f8080514 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
@@ -1,16 +1,18 @@
 package org.jastadd.ragconnect.tests;
 
+import org.jastadd.ragconnect.tests.utils.DefaultMappings;
+import org.jastadd.ragconnect.tests.utils.TestUtils;
 import read2write1.ast.*;
 
 import java.io.IOException;
 import java.util.concurrent.TimeUnit;
 
-import static org.jastadd.ragconnect.tests.TestUtils.mqttUri;
+import static org.jastadd.ragconnect.tests.utils.TestUtils.mqttUri;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 /**
- * Test case "read-1-write-2".
+ * Test case "read-2-write-1".
  *
  * @author rschoene - Initial contribution
  */
@@ -57,7 +59,7 @@ public class Read2Write1Test extends AbstractMqttTest {
   }
 
   @Override
-  protected void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException {
+  protected void setupReceiverAndConnect() throws IOException {
     model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS);
 
     handler = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost());
@@ -76,27 +78,27 @@ public class Read2Write1Test extends AbstractMqttTest {
 
     handler.newConnection(TOPIC_SAME_WRITE_INT, bytes -> {
       dataSame.numberOfIntValues += 1;
-      dataSame.lastIntValue = TestUtils.DefaultMappings.BytesToInt(bytes);
+      dataSame.lastIntValue = DefaultMappings.BytesToInt(bytes);
     });
 
     handler.newConnection(TOPIC_DIFFERENT_WRITE1_INT, bytes -> {
       dataOther1.numberOfIntValues += 1;
-      dataOther1.lastIntValue = TestUtils.DefaultMappings.BytesToInt(bytes);
+      dataOther1.lastIntValue = DefaultMappings.BytesToInt(bytes);
     });
 
     handler.newConnection(TOPIC_DIFFERENT_WRITE2_INT, bytes -> {
       dataOther2.numberOfIntValues += 1;
-      dataOther2.lastIntValue = TestUtils.DefaultMappings.BytesToInt(bytes);
+      dataOther2.lastIntValue = DefaultMappings.BytesToInt(bytes);
     });
 
     assertTrue(onSameNonterminal.connectInput1(mqttUri(TOPIC_SAME_READ1)));
     assertTrue(onSameNonterminal.connectInput2(mqttUri(TOPIC_SAME_READ2)));
-    assertTrue(onSameNonterminal.connectOutInteger(mqttUri(TOPIC_SAME_WRITE_INT), writeCurrentValue));
+    assertTrue(onSameNonterminal.connectOutInteger(mqttUri(TOPIC_SAME_WRITE_INT), isWriteCurrentValue()));
 
     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));
+    assertTrue(other1.connectOutInteger(mqttUri(TOPIC_DIFFERENT_WRITE1_INT), isWriteCurrentValue()));
+    assertTrue(other2.connectOutInteger(mqttUri(TOPIC_DIFFERENT_WRITE2_INT), isWriteCurrentValue()));
   }
 
   @Override
@@ -105,54 +107,39 @@ public class Read2Write1Test extends AbstractMqttTest {
     checkData(1, Integer.parseInt(INITIAL_VALUE + INITIAL_VALUE),
         1, Integer.parseInt(INITIAL_VALUE + INITIAL_VALUE));
 
-    // set new value
-    sendData(true, "2", true, "3");
-
-    // check new value. same: 2, 0. different: 3, 0.
-    checkData(2, 20,
-        2, 30);
-
-    // set new value
-    sendData(false, "4", false, "4");
-
-    // check new value. same: 2, 4. different: 3, 4.
-    checkData(3, 24,
-        3, 34);
-
-    // set new value only for same
-    setDataOnlySame("77");
-
-    // check new value. same: 77, 4. different: 3, 4.
-    checkData(4, 774,
-        3, 34);
+    communicateBoth(1);
   }
 
   @Override
   protected void communicateOnlyUpdatedValue() throws InterruptedException {
     // check initial value
     checkData(0, null,
-        0, null);
+            0, null);
 
+    communicateBoth(0);
+  }
+
+  private void communicateBoth(int initialNumberOfValues) throws InterruptedException {
     // set new value
     sendData(true, "2", true, "3");
 
     // check new value. same: 2, 0. different: 3, 0.
-    checkData(1, 20,
-        1, 30);
+    checkData(initialNumberOfValues + 1, 20,
+            initialNumberOfValues + 1, 30);
 
     // set new value
     sendData(false, "4", false, "4");
 
     // check new value. same: 2, 4. different: 3, 4.
-    checkData(2, 24,
-        2, 34);
+    checkData(initialNumberOfValues + 2, 24,
+            initialNumberOfValues + 2, 34);
 
     // set new value only for same
-    setDataOnlySame("78");
+    publisher.publish(TOPIC_SAME_READ1, "78".getBytes());
 
     // check new value. same: 78, 4. different: 3, 4.
-    checkData(3, 784,
-        2, 34);
+    checkData(initialNumberOfValues + 3, 784,
+            initialNumberOfValues + 2, 34);
   }
 
   @Override
@@ -173,10 +160,6 @@ public class Read2Write1Test extends AbstractMqttTest {
         inputDifferent.getBytes());
   }
 
-  private void setDataOnlySame(String inputSame) {
-    publisher.publish(TOPIC_SAME_READ1, inputSame.getBytes());
-  }
-
   private void checkData(int numberOfSameValues, Integer lastSameIntValue,
                          int numberOfDifferentValues, Integer lastDifferentIntValue)
       throws InterruptedException {
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
index 67c82177d96fe34c6379b9d62537f785292827df..92a2c4b978ed6d930bdc9b104db1d6043512fe78 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/RegressionTests.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/RegressionTests.java
@@ -1,5 +1,6 @@
 package org.jastadd.ragconnect.tests;
 
+import org.jastadd.ragconnect.tests.utils.TestUtils;
 import org.junit.jupiter.api.Test;
 import via.ast.A;
 
@@ -17,7 +18,7 @@ import static org.junit.jupiter.api.Assertions.*;
  *
  * @author rschoene - Initial contribution
  */
-public class RegressionTests {
+public class RegressionTests extends RagConnectTest {
 
   private static final String REGRESSION_TEST_OUTPUT_DIRECTORY = "regression-test/";
 
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
deleted file mode 100644
index bb557f6f88783c00f3bb2115758223bae0dc6f9c..0000000000000000000000000000000000000000
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TestUtils.java
+++ /dev/null
@@ -1,347 +0,0 @@
-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 java.util.stream.Collectors;
-
-import static java.lang.Math.abs;
-import static java.util.Collections.addAll;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.util.Lists.newArrayList;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.junit.jupiter.api.Assertions.fail;
-
-/**
- * Utility methods for tests.
- *
- * @author rschoene - Initial contribution
- */
-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) {
-      // we are in the CI, so use "mqtt" as host
-      return "mqtt";
-    } {
-      // else assume a locally running mqtt broker
-      return "localhost";
-    }
-  }
-
-  public static String mqttUri(String path) {
-    return "mqtt://" + getMqttHost() + "/" + path;
-  }
-
-  public static String restUri(String path, int port) {
-    return "rest://localhost:" + port + "/" + path;
-  }
-
-  public static int getMqttDefaultPort() {
-    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");
-    String javaBin = javaHome + File.separator + "bin" + File.separator + "java";
-    String classpath = System.getProperty("java.class.path");
-    String className = klass.getName();
-
-    String[] newArgs = new String[args.length + 4];
-    newArgs[0] = javaBin;
-    newArgs[1] = "-cp";
-    newArgs[2] = classpath;
-    newArgs[3] = className;
-    System.arraycopy(args, 0, newArgs, 4, args.length);
-
-    ProcessBuilder builder = new ProcessBuilder(newArgs);
-//    builder.redirectOutput(err);
-    builder.redirectError(err);
-
-    Process process = builder.start();
-    process.waitFor();
-    return process.exitValue();
-  }
-
-  public static void testJaddContainReferenceToJackson(Path path, boolean shouldContain) {
-    try {
-      String content = Files.readString(path);
-      boolean actualContain = content.contains("com.fasterxml.jackson.databind.ObjectMapper");
-      if (actualContain && !shouldContain) {
-        fail(path + " should not depend on jackson library, but does");
-      }
-      if (!actualContain && shouldContain) {
-        fail(path + " does not depend on jackson library");
-      }
-    } catch (IOException e) {
-      fail(e);
-    }
-  }
-
-  public static String readFile(Path path, Charset encoding)
-      throws IOException {
-    byte[] encoded = Files.readAllBytes(path);
-    return new String(encoded, encoding);
-  }
-
-  public static void waitForMqtt() throws InterruptedException {
-    TimeUnit.MILLISECONDS.sleep(1500);
-  }
-
-  public static class IntList {
-    private final List<Integer> integers = newArrayList();
-    public IntList(Integer... values) {
-      addAll(integers, values);
-    }
-
-    public List<Integer> toList() {
-      return integers;
-    }
-
-    public List<Integer> toAbsList() {
-      return integers.stream().map(Math::abs).collect(Collectors.toList());
-    }
-
-    public static IntList list(Integer... values) {
-      return new IntList(values);
-    }
-  }
-
-  @SuppressWarnings({"unused", "rawtypes"})
-  public static class DefaultMappings {
-    static class ReadNode extends defaultOnlyRead.ast.ASTNode {
-      public static boolean _apply__DefaultBytesToBooleanMapping(byte[] input) throws Exception {
-        return defaultOnlyRead.ast.ASTNode._apply__DefaultBytesToBooleanMapping(input);
-      }
-      public static int _apply__DefaultBytesToIntMapping(byte[] input) throws Exception {
-        return defaultOnlyRead.ast.ASTNode._apply__DefaultBytesToIntMapping(input);
-      }
-      public static short _apply__DefaultBytesToShortMapping(byte[] input) throws Exception {
-        return defaultOnlyRead.ast.ASTNode._apply__DefaultBytesToShortMapping(input);
-      }
-      public static long _apply__DefaultBytesToLongMapping(byte[] input) throws Exception {
-        return defaultOnlyRead.ast.ASTNode._apply__DefaultBytesToLongMapping(input);
-      }
-      public static float _apply__DefaultBytesToFloatMapping(byte[] input) throws Exception {
-        return defaultOnlyRead.ast.ASTNode._apply__DefaultBytesToFloatMapping(input);
-      }
-      public static double _apply__DefaultBytesToDoubleMapping(byte[] input) throws Exception {
-        return defaultOnlyRead.ast.ASTNode._apply__DefaultBytesToDoubleMapping(input);
-      }
-      public static char _apply__DefaultBytesToCharMapping(byte[] input) throws Exception {
-        return defaultOnlyRead.ast.ASTNode._apply__DefaultBytesToCharMapping(input);
-      }
-      public static String _apply__DefaultBytesToStringMapping(byte[] input) throws Exception {
-        return defaultOnlyRead.ast.ASTNode._apply__DefaultBytesToStringMapping(input);
-      }
-    }
-
-    static class WriteNode extends defaultOnlyWrite.ast.ASTNode {
-      public static byte[] _apply__DefaultBooleanToBytesMapping(boolean input) throws Exception {
-        return defaultOnlyWrite.ast.ASTNode._apply__DefaultBooleanToBytesMapping(input);
-      }
-      public static byte[] _apply__DefaultIntToBytesMapping(int input) throws Exception {
-        return defaultOnlyWrite.ast.ASTNode._apply__DefaultIntToBytesMapping(input);
-      }
-      public static byte[] _apply__DefaultShortToBytesMapping(short input) throws Exception {
-        return defaultOnlyWrite.ast.ASTNode._apply__DefaultShortToBytesMapping(input);
-      }
-      public static byte[] _apply__DefaultLongToBytesMapping(long input) throws Exception {
-        return defaultOnlyWrite.ast.ASTNode._apply__DefaultLongToBytesMapping(input);
-      }
-      public static byte[] _apply__DefaultFloatToBytesMapping(float input) throws Exception {
-        return defaultOnlyWrite.ast.ASTNode._apply__DefaultFloatToBytesMapping(input);
-      }
-      public static byte[] _apply__DefaultDoubleToBytesMapping(double input) throws Exception {
-        return defaultOnlyWrite.ast.ASTNode._apply__DefaultDoubleToBytesMapping(input);
-      }
-      public static byte[] _apply__DefaultCharToBytesMapping(char input) throws Exception {
-        return defaultOnlyWrite.ast.ASTNode._apply__DefaultCharToBytesMapping(input);
-      }
-      public static byte[] _apply__DefaultStringToBytesMapping(String input) throws Exception {
-        return defaultOnlyWrite.ast.ASTNode._apply__DefaultStringToBytesMapping(input);
-      }
-    }
-
-    public static boolean BytesToBool(byte[] input) {
-      try {
-        return ReadNode._apply__DefaultBytesToBooleanMapping(input);
-      } catch (Exception e) {
-        e.printStackTrace();
-        return false;
-      }
-    }
-    public static int BytesToInt(byte[] input) {
-      try {
-        return ReadNode._apply__DefaultBytesToIntMapping(input);
-      } catch (Exception e) {
-        e.printStackTrace();
-        return 0;
-      }
-    }
-    public static short BytesToShort(byte[] input) {
-      try {
-        return ReadNode._apply__DefaultBytesToShortMapping(input);
-      } catch (Exception e) {
-        e.printStackTrace();
-        return 0;
-      }
-    }
-    public static long BytesToLong(byte[] input) {
-      try {
-        return ReadNode._apply__DefaultBytesToLongMapping(input);
-      } catch (Exception e) {
-        e.printStackTrace();
-        return 0;
-      }
-    }
-    public static float BytesToFloat(byte[] input) {
-      try {
-        return ReadNode._apply__DefaultBytesToFloatMapping(input);
-      } catch (Exception e) {
-        e.printStackTrace();
-        return 0;
-      }
-    }
-    public static double BytesToDouble(byte[] input) {
-      try {
-        return ReadNode._apply__DefaultBytesToDoubleMapping(input);
-      } catch (Exception e) {
-        e.printStackTrace();
-        return 0;
-      }
-    }
-    public static char BytesToChar(byte[] input) {
-      try {
-        return ReadNode._apply__DefaultBytesToCharMapping(input);
-      } catch (Exception e) {
-        e.printStackTrace();
-        return 0;
-      }
-    }
-    public static String BytesToString(byte[] input) {
-      try {
-        return ReadNode._apply__DefaultBytesToStringMapping(input);
-      } catch (Exception e) {
-        e.printStackTrace();
-        return null;
-      }
-    }
-    public static byte[] BoolToBytes(boolean input) {
-      try {
-        return WriteNode._apply__DefaultBooleanToBytesMapping(input);
-      } catch (Exception e) {
-        e.printStackTrace();
-        return null;
-      }
-    }
-    public static byte[] IntToBytes(int input) {
-      try {
-        return WriteNode._apply__DefaultIntToBytesMapping(input);
-      } catch (Exception e) {
-        e.printStackTrace();
-        return null;
-      }
-    }
-    public static byte[] ShortToBytes(short input) {
-      try {
-        return WriteNode._apply__DefaultShortToBytesMapping(input);
-      } catch (Exception e) {
-        e.printStackTrace();
-        return null;
-      }
-    }
-    public static byte[] LongToBytes(long input) {
-      try {
-        return WriteNode._apply__DefaultLongToBytesMapping(input);
-      } catch (Exception e) {
-        e.printStackTrace();
-        return null;
-      }
-    }
-    public static byte[] FloatToBytes(float input) {
-      try {
-        return WriteNode._apply__DefaultFloatToBytesMapping(input);
-      } catch (Exception e) {
-        e.printStackTrace();
-        return null;
-      }
-    }
-    public static byte[] DoubleToBytes(double input) {
-      try {
-        return WriteNode._apply__DefaultDoubleToBytesMapping(input);
-      } catch (Exception e) {
-        e.printStackTrace();
-        return null;
-      }
-    }
-    public static byte[] CharToBytes(char input) {
-      try {
-        return WriteNode._apply__DefaultCharToBytesMapping(input);
-      } catch (Exception e) {
-        e.printStackTrace();
-        return null;
-      }
-    }
-    public static byte[] StringToBytes(String input) {
-      try {
-        return WriteNode._apply__DefaultStringToBytesMapping(input);
-      } catch (Exception e) {
-        e.printStackTrace();
-        return null;
-      }
-    }
-  }
-}
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
deleted file mode 100644
index da4d70308afd1efc12e10d4dbfa1a84c04852628..0000000000000000000000000000000000000000
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TokenValueSendTest.java
+++ /dev/null
@@ -1,297 +0,0 @@
-package org.jastadd.ragconnect.tests;
-
-import tokenValueSend.ast.*;
-
-import java.io.IOException;
-import java.util.concurrent.TimeUnit;
-
-import static org.jastadd.ragconnect.tests.TestUtils.mqttUri;
-import static org.junit.jupiter.api.Assertions.*;
-
-/**
- * Test case "tokenValueSend".
- *
- * @author rschoene - Initial contribution
- */
-public class TokenValueSendTest extends AbstractMqttTest {
-  // TODO split into incremental and manual test
-
-  private static final String TOPIC_SEND_ONE = "one/value/out";
-
-  private static final String TOPIC_RECEIVE_TWO = "two/value/in";
-  private static final String TOPIC_SEND_TWO = "two/value/out";
-
-  private static final String TOPIC_RECEIVE_THREE_VALUE = "three/value/in";
-  private static final String TOPIC_SEND_THREE_VALUE = "three/value/out";
-  private static final String TOPIC_SEND_THREE_OTHER = "three/other/out";
-
-  private static final String INITIAL_VALUE = "Start";
-
-  private MqttHandler handler;
-  private A model;
-  private OnlySend one;
-  private ReceiveAndSend two;
-  private ReceiveSendAndDepend three;
-
-  private ReceiverData dataOne;
-  private ReceiverData dataTwo;
-  private ReceiverData dataThree;
-  private ReceiverData dataThreeOther;
-
-  @Override
-  protected void createModel() {
-    // Setting value for Input without dependencies does not trigger any updates
-    model = new A();
-
-    one = new OnlySend();
-    one.setValue(INITIAL_VALUE);
-    model.setOnlySend(one);
-
-    two = new ReceiveAndSend();
-    two.setValue(INITIAL_VALUE);
-    model.setReceiveAndSend(two);
-
-    three = new ReceiveSendAndDepend();
-    three.setValue(INITIAL_VALUE);
-    model.setReceiveSendAndDepend(three);
-  }
-
-  protected void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException {
-    model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS);
-
-    handler = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost());
-    assertTrue(handler.waitUntilReady(2, TimeUnit.SECONDS));
-
-    three.addDependency1(three);
-
-    dataOne = new ReceiverData();
-    dataTwo = new ReceiverData();
-    dataThree = new ReceiverData();
-    dataThreeOther = new ReceiverData();
-
-    handler.newConnection(TOPIC_SEND_ONE, bytes -> {
-      dataOne.numberOfStringValues += 1;
-      dataOne.lastStringValue = TestUtils.DefaultMappings.BytesToString(bytes);
-    });
-    handler.newConnection(TOPIC_SEND_TWO, bytes -> {
-      dataTwo.numberOfStringValues += 1;
-      dataTwo.lastStringValue = TestUtils.DefaultMappings.BytesToString(bytes);
-    });
-
-    handler.newConnection(TOPIC_SEND_THREE_VALUE, bytes -> {
-      dataThree.numberOfStringValues += 1;
-      dataThree.lastStringValue = TestUtils.DefaultMappings.BytesToString(bytes);
-    });
-    handler.newConnection(TOPIC_SEND_THREE_OTHER, bytes -> {
-      dataThreeOther.numberOfStringValues += 1;
-      dataThreeOther.lastStringValue = TestUtils.DefaultMappings.BytesToString(bytes);
-    });
-
-    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
-  protected void communicateSendInitialValue() throws InterruptedException, IOException {
-    // check initial value
-    checkData(1, "Start-Post",
-        1, "Start-Post",
-        1, "Start-Post",
-        1, "Start-T-Post");
-
-    // send new value
-    sendData("200", "300");
-    checkData(1, "Start-Post",
-        2, "Pre-200-Post",
-        2, "Pre-300-Post",
-        2, "Pre-300-T-Post");
-
-    // set new value
-    setData("101", "201", "301");
-    checkData(2, "101-Post",
-        3, "201-Post",
-        3, "301-Post",
-        3, "301-T-Post");
-
-    // send the same values (will not be sent again)
-    setData("101", "201", "301");
-    checkData(2, "101-Post",
-        3, "201-Post",
-        3, "301-Post",
-        3, "301-T-Post");
-
-    // send values with prefixes imitating receiving
-    setData("102", "Pre-202", "Pre-302");
-    checkData(3, "102-Post",
-        4, "Pre-202-Post",
-        4, "Pre-302-Post",
-        4, "Pre-302-T-Post");
-
-    // send the same values (will not be sent again, because previously prefixed)
-    sendData("202", "302");
-    checkData(3, "102-Post",
-        4, "Pre-202-Post",
-        4, "Pre-302-Post",
-        4, "Pre-302-T-Post");
-
-    // new values for two and three, but two will not send updated value
-    assertTrue(two.disconnectSendValue(mqttUri(TOPIC_SEND_TWO)));
-    sendData("203", "303");
-    checkData(3, "102-Post",
-        4, "Pre-202-Post",
-        5, "Pre-303-Post",
-        5, "Pre-303-T-Post");
-    assertEquals("Pre-203", two.getValue());
-
-    // can not disconnect again, and also not for different topic
-    assertFalse(two.disconnectSendValue(mqttUri(TOPIC_SEND_TWO)));
-    assertFalse(two.disconnectSendValue(mqttUri(TOPIC_RECEIVE_TWO)));
-
-    // new values for two and three, but two will neither receive nor send updated value
-    assertTrue(two.disconnectReceiveValue(mqttUri(TOPIC_RECEIVE_TWO)));
-    sendData("204", "304");
-    checkData(3, "102-Post",
-        4, "Pre-202-Post",
-        6, "Pre-304-Post",
-        6, "Pre-304-T-Post");
-    assertEquals("Pre-203", two.getValue());
-
-    // new values for three, but it will not receive updated value, and, thus, not send it either
-    assertTrue(three.disconnectReceiveValue(mqttUri(TOPIC_RECEIVE_THREE_VALUE)));
-    sendData("204", "305");
-    checkData(3, "102-Post",
-        4, "Pre-202-Post",
-        6, "Pre-304-Post",
-        6, "Pre-304-T-Post");
-    assertEquals("Pre-203", two.getValue());
-
-    // disconnect send is possible
-    assertTrue(three.disconnectSendValue(mqttUri(TOPIC_SEND_THREE_VALUE)));
-  }
-
-  @Override
-  protected void communicateOnlyUpdatedValue() throws InterruptedException, IOException {
-    // check initial value
-    checkData(0, null,
-        0, null,
-        0, null,
-        0, null);
-
-    // send new value
-    sendData("210", "310");
-    checkData(0, null,
-        1, "Pre-210-Post",
-        1, "Pre-310-Post",
-        1, "Pre-310-T-Post");
-
-    // set new value
-    setData("111", "211", "311");
-    checkData(1, "111-Post",
-        2, "211-Post",
-        2, "311-Post",
-        2, "311-T-Post");
-
-    // send the same values (will not be sent again)
-    setData("111", "211", "311");
-    checkData(1, "111-Post",
-        2, "211-Post",
-        2, "311-Post",
-        2, "311-T-Post");
-
-    // send values with prefixes imitating receiving
-    setData("112", "Pre-212", "Pre-312");
-    checkData(2, "112-Post",
-        3, "Pre-212-Post",
-        3, "Pre-312-Post",
-        3, "Pre-312-T-Post");
-
-    // send the same values (will not be sent again, because previously prefixed)
-    sendData("212", "312");
-    checkData(2, "112-Post",
-        3, "Pre-212-Post",
-        3, "Pre-312-Post",
-        3, "Pre-312-T-Post");
-
-    // new values for two and three, but two will not send updated value
-    assertTrue(two.disconnectSendValue(mqttUri(TOPIC_SEND_TWO)));
-    sendData("213", "313");
-    checkData(2, "112-Post",
-        3, "Pre-212-Post",
-        4, "Pre-313-Post",
-        4, "Pre-313-T-Post");
-    assertEquals("Pre-213", two.getValue());
-
-    // can not disconnect again, and also not for different topic
-    assertFalse(two.disconnectSendValue(mqttUri(TOPIC_SEND_TWO)));
-    assertFalse(two.disconnectSendValue(mqttUri(TOPIC_RECEIVE_TWO)));
-
-    // new values for two and three, but two will neither receive nor send updated value
-    assertTrue(two.disconnectReceiveValue(mqttUri(TOPIC_RECEIVE_TWO)));
-    sendData("214", "314");
-    checkData(2, "112-Post",
-        3, "Pre-212-Post",
-        5, "Pre-314-Post",
-        5, "Pre-314-T-Post");
-    assertEquals("Pre-213", two.getValue());
-
-    // new values for three, but it will not receive updated value, and, thus, not send it either
-    assertTrue(three.disconnectReceiveValue(mqttUri(TOPIC_RECEIVE_THREE_VALUE)));
-    sendData("214", "315");
-    checkData(2, "112-Post",
-        3, "Pre-212-Post",
-        5, "Pre-314-Post",
-        5, "Pre-314-T-Post");
-    assertEquals("Pre-213", two.getValue());
-
-    // disconnect send is possible
-    assertTrue(three.disconnectSendValue(mqttUri(TOPIC_SEND_THREE_VALUE)));
-  }
-
-  @Override
-  public void closeConnections() {
-    if (handler != null) {
-      handler.close();
-    }
-    if (model != null) {
-      model.ragconnectCloseConnections();
-    }
-  }
-
-  private void sendData(String inputTwo, String inputThree) {
-    publisher.publish(TOPIC_RECEIVE_TWO, inputTwo.getBytes());
-    publisher.publish(TOPIC_RECEIVE_THREE_VALUE, inputThree.getBytes());
-  }
-
-  private void setData(String inputOne, String inputTwo, String inputThree) {
-    one.setValue(inputOne);
-    two.setValue(inputTwo);
-    three.setValue(inputThree);
-  }
-
-  private void checkData(int numberOfOneValues, String lastOneStringValue,
-                         int numberOfTwoValues, String lastTwoStringValue,
-                         int numberOfThreeValues, String lastThreeStringValue,
-                         int numberOfOtherValues, String lastOtherStringValue
-  ) throws InterruptedException {
-    TestUtils.waitForMqtt();
-    dataOne.assertEqualData(numberOfOneValues, lastOneStringValue);
-    dataTwo.assertEqualData(numberOfTwoValues, lastTwoStringValue);
-    dataThree.assertEqualData(numberOfThreeValues, lastThreeStringValue);
-    dataThreeOther.assertEqualData(numberOfOtherValues, lastOtherStringValue);
-  }
-
-  private static class ReceiverData {
-    String lastStringValue;
-    int numberOfStringValues = 0;
-
-    public void assertEqualData(int expectedNumberOfValues, String expectedLastValue) {
-      assertEquals(expectedNumberOfValues, this.numberOfStringValues);
-      assertEquals(expectedLastValue, this.lastStringValue);
-    }
-  }
-
-}
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 64ad5fbd147ba6c7e22d24ba7f92fe8d7114ced7..490f8c5a752290964fa67561b272c22984d1501a 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
@@ -5,7 +5,7 @@ import tutorial.ast.B;
 
 import java.io.IOException;
 
-import static org.jastadd.ragconnect.tests.TestUtils.mqttUri;
+import static org.jastadd.ragconnect.tests.utils.TestUtils.mqttUri;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 /**
@@ -31,7 +31,7 @@ public class TutorialTest extends AbstractMqttTest {
   }
 
   @Override
-  protected void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException {
+  protected void setupReceiverAndConnect() throws IOException {
     // a.OutputOnA -> a.Input
     a.addDependencyA(a);
     // b1.OutputOnB -> a.Input
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 ded2990b5e997a18ff834379258689c359e4a465..9401cf6180733c316560509e98af2fbc9922259f 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
@@ -1,5 +1,8 @@
 package org.jastadd.ragconnect.tests;
 
+import org.jastadd.ragconnect.tests.utils.DefaultMappings;
+import org.jastadd.ragconnect.tests.utils.TestUtils;
+import org.jastadd.ragconnect.tests.utils.TestChecker;
 import org.junit.jupiter.api.Tag;
 import via.ast.A;
 import via.ast.MqttHandler;
@@ -12,8 +15,8 @@ import javax.ws.rs.core.MediaType;
 import java.io.IOException;
 import java.util.concurrent.TimeUnit;
 
-import static org.jastadd.ragconnect.tests.TestUtils.mqttUri;
-import static org.jastadd.ragconnect.tests.TestUtils.restUri;
+import static org.jastadd.ragconnect.tests.utils.TestUtils.mqttUri;
+import static org.jastadd.ragconnect.tests.utils.TestUtils.restUri;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
@@ -46,6 +49,7 @@ public class ViaTest extends AbstractMqttTest {
   private static final String NOT_MAPPED = "<html><body><h2>404 Not found</h2></body></html>";
 
   private MqttHandler handler;
+  private TestChecker checker;
   private A model;
   private ReceiverData dataMqtt2Mqtt;
   private ReceiverData dataRest2Mqtt;
@@ -70,7 +74,7 @@ public class ViaTest extends AbstractMqttTest {
   }
 
   @Override
-  protected void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException {
+  protected void setupReceiverAndConnect() throws IOException {
     model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS);
 
     handler = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost());
@@ -89,17 +93,20 @@ public class ViaTest extends AbstractMqttTest {
 
     handler.newConnection(TOPIC_MQTT_2_MQTT_SEND, bytes -> {
       dataMqtt2Mqtt.numberOfStringValues += 1;
-      dataMqtt2Mqtt.lastStringValue = TestUtils.DefaultMappings.BytesToString(bytes);
+      dataMqtt2Mqtt.lastStringValue = DefaultMappings.BytesToString(bytes);
     });
     handler.newConnection(TOPIC_REST_2_MQTT_SEND, bytes -> {
       dataRest2Mqtt.numberOfStringValues += 1;
-      dataRest2Mqtt.lastStringValue = TestUtils.DefaultMappings.BytesToString(bytes);
+      dataRest2Mqtt.lastStringValue = DefaultMappings.BytesToString(bytes);
     });
     handler.newConnection(TOPIC_BOTH_2_MQTT_SEND, bytes -> {
       dataBoth2Mqtt.numberOfStringValues += 1;
-      dataBoth2Mqtt.lastStringValue = TestUtils.DefaultMappings.BytesToString(bytes);
+      dataBoth2Mqtt.lastStringValue = DefaultMappings.BytesToString(bytes);
     });
 
+    checker = new TestChecker();
+    checker.setActualNumberOfValues(() -> dataMqtt2Mqtt.numberOfStringValues);
+
     Client client = ClientBuilder.newClient();
     dataRest2Rest = client.target(REST_SERVER_BASE_URL + PATH_REST_2_REST_SEND);
     dataMqtt2Rest = client.target(REST_SERVER_BASE_URL + PATH_MQTT_2_REST_SEND);
@@ -109,22 +116,23 @@ public class ViaTest extends AbstractMqttTest {
     senderBoth2Rest = client.target(REST_SERVER_BASE_URL + PATH_BOTH_REST_RECEIVE);
 
     assertTrue(model.connectMqtt2MqttInput(mqttUri(TOPIC_MQTT_2_MQTT_RECEIVE)));
-    assertTrue(model.connectMqtt2MqttOutput(mqttUri(TOPIC_MQTT_2_MQTT_SEND), writeCurrentValue));
+    assertTrue(model.connectMqtt2MqttOutput(mqttUri(TOPIC_MQTT_2_MQTT_SEND), isWriteCurrentValue()));
     assertTrue(model.connectMqtt2RestInput(mqttUri(TOPIC_MQTT_2_REST_RECEIVE)));
-    assertTrue(model.connectMqtt2RestOutput(restUri(PATH_MQTT_2_REST_SEND, REST_PORT), writeCurrentValue));
+    assertTrue(model.connectMqtt2RestOutput(restUri(PATH_MQTT_2_REST_SEND, REST_PORT), isWriteCurrentValue()));
     assertTrue(model.connectRest2MqttInput(restUri(PATH_REST_2_MQTT_RECEIVE, REST_PORT)));
-    assertTrue(model.connectRest2MqttOutput(mqttUri(TOPIC_REST_2_MQTT_SEND), writeCurrentValue));
+    assertTrue(model.connectRest2MqttOutput(mqttUri(TOPIC_REST_2_MQTT_SEND), isWriteCurrentValue()));
     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.connectRest2RestOutput(restUri(PATH_REST_2_REST_SEND, REST_PORT), isWriteCurrentValue()));
     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));
+    assertTrue(model.connectBoth2MqttOutput(mqttUri(TOPIC_BOTH_2_MQTT_SEND), isWriteCurrentValue()));
+    assertTrue(model.connectBoth2RestOutput(restUri(PATH_BOTH_2_REST_SEND, REST_PORT), isWriteCurrentValue()));
   }
 
   @Override
   protected void communicateSendInitialValue() throws InterruptedException, IOException {
     // check initial value
+    checker.addToNumberOfValues(1);
     checkData(1, "100-M2M-ToMqtt",
         "200-R2R-ToRest",
         "300-M2R-ToRest",
@@ -132,195 +140,109 @@ public class ViaTest extends AbstractMqttTest {
         1, "500-B2M-ToMqtt",
         "500-B2R-ToRest");
 
-    sendData("101", "201", "301", "401");
-    sendDataForBoth("501", true);
-
-    // check new value
-    checkData(2, "FromMqtt-101-M2M-ToMqtt",
-        "FromRest-201-R2R-ToRest",
-        "FromMqtt-301-M2R-ToRest",
-        2, "FromRest-401-R2M-ToMqtt",
-        2, "501-B2M-ToMqtt",
-        "501-B2R-ToRest");
-
-    // send value only for bothInput via REST
-    sendDataForBoth("502", false);
-
-    // check this value
-    checkData(2, "FromMqtt-101-M2M-ToMqtt",
-        "FromRest-201-R2R-ToRest",
-        "FromMqtt-301-M2R-ToRest",
-        2, "FromRest-401-R2M-ToMqtt",
-        3, "502-B2M-ToMqtt",
-        "502-B2R-ToRest");
-
-    // send same value only for bothInput via MQTT
-    sendDataForBoth("502", true);
-
-    // check this value
-    checkData(2, "FromMqtt-101-M2M-ToMqtt",
-        "FromRest-201-R2R-ToRest",
-        "FromMqtt-301-M2R-ToRest",
-        2, "FromRest-401-R2M-ToMqtt",
-        3, "502-B2M-ToMqtt",
-        "502-B2R-ToRest");
-
-    // send values for other things
-    sendData("102", "202", "302", "402");
-
-    // check this value
-    checkData(3, "FromMqtt-102-M2M-ToMqtt",
-        "FromRest-202-R2R-ToRest",
-        "FromMqtt-302-M2R-ToRest",
-        3, "FromRest-402-R2M-ToMqtt",
-        3, "502-B2M-ToMqtt",
-        "502-B2R-ToRest");
-
-    // send same values again for other things
-    sendData("102", "202", "302", "402");
-
-    // check this value
-    checkData(3, "FromMqtt-102-M2M-ToMqtt",
-        "FromRest-202-R2R-ToRest",
-        "FromMqtt-302-M2R-ToRest",
-        3, "FromRest-402-R2M-ToMqtt",
-        3, "502-B2M-ToMqtt",
-        "502-B2R-ToRest");
-
-    // send 503 over mqtt while disconnected should not change anything
-    assertTrue(model.disconnectBoth2BothInput(mqttUri(TOPIC_BOTH_MQTT_RECEIVE)));
-    sendDataForBoth("503", true);
-    checkData(3, "FromMqtt-102-M2M-ToMqtt",
-        "FromRest-202-R2R-ToRest",
-        "FromMqtt-302-M2R-ToRest",
-        3, "FromRest-402-R2M-ToMqtt",
-        3, "502-B2M-ToMqtt",
-        "502-B2R-ToRest");
-
-    // send 504 over rest while still connected should update
-    sendDataForBoth("504", false);
-    checkData(3, "FromMqtt-102-M2M-ToMqtt",
-        "FromRest-202-R2R-ToRest",
-        "FromMqtt-302-M2R-ToRest",
-        3, "FromRest-402-R2M-ToMqtt",
-        4, "504-B2M-ToMqtt",
-        "504-B2R-ToRest");
-
-    // send 505 over rest while also disconnected should not change anything
-    assertTrue(model.disconnectBoth2BothInput(restUri(PATH_BOTH_REST_RECEIVE, REST_PORT)));
-    sendDataForBoth("505", false);
-    checkData(3, "FromMqtt-102-M2M-ToMqtt",
-        "FromRest-202-R2R-ToRest",
-        "FromMqtt-302-M2R-ToRest",
-        3, "FromRest-402-R2M-ToMqtt",
-        4, "504-B2M-ToMqtt",
-        "504-B2R-ToRest");
-
-    // send new values. value over rest while sender disconnected does not provide a value anymore
-    assertTrue(model.disconnectRest2RestOutput(restUri(PATH_REST_2_REST_SEND, REST_PORT)));
-    assertTrue(model.disconnectMqtt2RestOutput(restUri(PATH_MQTT_2_REST_SEND, REST_PORT)));
-    sendData("103", "203", "303", "403");
-    checkData(4, "FromMqtt-103-M2M-ToMqtt",
-        NOT_MAPPED,
-        NOT_MAPPED,
-        4, "FromRest-403-R2M-ToMqtt",
-        4, "504-B2M-ToMqtt",
-        "504-B2R-ToRest");
+    communicateBoth(1);
   }
 
   @Override
   protected void communicateOnlyUpdatedValue() throws InterruptedException, IOException {
     // check initial value
     checkData(0, null,
-        "200-R2R-ToRest",
-        "300-M2R-ToRest",
-        0, null,
-        0, null,
-        "500-B2R-ToRest");
+            "200-R2R-ToRest",
+            "300-M2R-ToRest",
+            0, null,
+            0, null,
+            "500-B2R-ToRest");
+
+    communicateBoth(0);
+  }
 
+  private void communicateBoth(int initialNumberOfValues) throws IOException {
     sendData("111", "211", "311", "411");
     sendDataForBoth("511", true);
-    checkData(1, "FromMqtt-111-M2M-ToMqtt",
+    checker.incNumberOfValues();
+    checkData(initialNumberOfValues + 1, "FromMqtt-111-M2M-ToMqtt",
         "FromRest-211-R2R-ToRest",
         "FromMqtt-311-M2R-ToRest",
-        1, "FromRest-411-R2M-ToMqtt",
-        1, "511-B2M-ToMqtt",
+        initialNumberOfValues + 1, "FromRest-411-R2M-ToMqtt",
+        initialNumberOfValues + 1, "511-B2M-ToMqtt",
         "511-B2R-ToRest");
 
     // send value only for bothInput via REST
     sendDataForBoth("512", false);
-    checkData(1, "FromMqtt-111-M2M-ToMqtt",
+    checkData(initialNumberOfValues + 1, "FromMqtt-111-M2M-ToMqtt",
         "FromRest-211-R2R-ToRest",
         "FromMqtt-311-M2R-ToRest",
-        1, "FromRest-411-R2M-ToMqtt",
-        2, "512-B2M-ToMqtt",
+        initialNumberOfValues + 1, "FromRest-411-R2M-ToMqtt",
+        initialNumberOfValues + 2, "512-B2M-ToMqtt",
         "512-B2R-ToRest");
 
     // send same value only for bothInput via MQTT
     sendDataForBoth("512", true);
-    checkData(1, "FromMqtt-111-M2M-ToMqtt",
+    checkData(initialNumberOfValues + 1, "FromMqtt-111-M2M-ToMqtt",
         "FromRest-211-R2R-ToRest",
         "FromMqtt-311-M2R-ToRest",
-        1, "FromRest-411-R2M-ToMqtt",
-        2, "512-B2M-ToMqtt",
+        initialNumberOfValues + 1, "FromRest-411-R2M-ToMqtt",
+        initialNumberOfValues + 2, "512-B2M-ToMqtt",
         "512-B2R-ToRest");
 
     // send values for other things
     sendData("112", "212", "312", "412");
-    checkData(2, "FromMqtt-112-M2M-ToMqtt",
+    checker.incNumberOfValues();
+    checkData(initialNumberOfValues + 2, "FromMqtt-112-M2M-ToMqtt",
         "FromRest-212-R2R-ToRest",
         "FromMqtt-312-M2R-ToRest",
-        2, "FromRest-412-R2M-ToMqtt",
-        2, "512-B2M-ToMqtt",
+        initialNumberOfValues + 2, "FromRest-412-R2M-ToMqtt",
+        initialNumberOfValues + 2, "512-B2M-ToMqtt",
         "512-B2R-ToRest");
 
     // send same values again for other things
     sendData("112", "212", "312", "412");
-    checkData(2, "FromMqtt-112-M2M-ToMqtt",
+    checkData(initialNumberOfValues + 2, "FromMqtt-112-M2M-ToMqtt",
         "FromRest-212-R2R-ToRest",
         "FromMqtt-312-M2R-ToRest",
-        2, "FromRest-412-R2M-ToMqtt",
-        2, "512-B2M-ToMqtt",
+        initialNumberOfValues + 2, "FromRest-412-R2M-ToMqtt",
+        initialNumberOfValues + 2, "512-B2M-ToMqtt",
         "512-B2R-ToRest");
 
     // send 503 over mqtt while disconnected should not change anything
     assertTrue(model.disconnectBoth2BothInput(mqttUri(TOPIC_BOTH_MQTT_RECEIVE)));
     sendDataForBoth("513", true);
-    checkData(2, "FromMqtt-112-M2M-ToMqtt",
+    checkData(initialNumberOfValues + 2, "FromMqtt-112-M2M-ToMqtt",
         "FromRest-212-R2R-ToRest",
         "FromMqtt-312-M2R-ToRest",
-        2, "FromRest-412-R2M-ToMqtt",
-        2, "512-B2M-ToMqtt",
+        initialNumberOfValues + 2, "FromRest-412-R2M-ToMqtt",
+        initialNumberOfValues + 2, "512-B2M-ToMqtt",
         "512-B2R-ToRest");
 
     // send 514 over rest while still connected should update
     sendDataForBoth("514", false);
-    checkData(2, "FromMqtt-112-M2M-ToMqtt",
+    checkData(initialNumberOfValues + 2, "FromMqtt-112-M2M-ToMqtt",
         "FromRest-212-R2R-ToRest",
         "FromMqtt-312-M2R-ToRest",
-        2, "FromRest-412-R2M-ToMqtt",
-        3, "514-B2M-ToMqtt",
+        initialNumberOfValues + 2, "FromRest-412-R2M-ToMqtt",
+        initialNumberOfValues + 3, "514-B2M-ToMqtt",
         "514-B2R-ToRest");
 
     // send 515 over rest while also disconnected should not change anything
     assertTrue(model.disconnectBoth2BothInput(restUri(PATH_BOTH_REST_RECEIVE, REST_PORT)));
     sendDataForBoth("515", false);
-    checkData(2, "FromMqtt-112-M2M-ToMqtt",
+    checkData(initialNumberOfValues + 2, "FromMqtt-112-M2M-ToMqtt",
         "FromRest-212-R2R-ToRest",
         "FromMqtt-312-M2R-ToRest",
-        2, "FromRest-412-R2M-ToMqtt",
-        3, "514-B2M-ToMqtt",
+        initialNumberOfValues + 2, "FromRest-412-R2M-ToMqtt",
+        initialNumberOfValues + 3, "514-B2M-ToMqtt",
         "514-B2R-ToRest");
 
     // send new values. value over rest while sender disconnected does not provide a value anymore
     assertTrue(model.disconnectRest2RestOutput(restUri(PATH_REST_2_REST_SEND, REST_PORT)));
     assertTrue(model.disconnectMqtt2RestOutput(restUri(PATH_MQTT_2_REST_SEND, REST_PORT)));
     sendData("113", "213", "313", "413");
-    checkData(3, "FromMqtt-113-M2M-ToMqtt",
+    checker.incNumberOfValues();
+    checkData(initialNumberOfValues + 3, "FromMqtt-113-M2M-ToMqtt",
         NOT_MAPPED,
         NOT_MAPPED,
-        3, "FromRest-413-R2M-ToMqtt",
-        3, "514-B2M-ToMqtt",
+        initialNumberOfValues + 3, "FromRest-413-R2M-ToMqtt",
+        initialNumberOfValues + 3, "514-B2M-ToMqtt",
         "514-B2R-ToRest");
   }
 
@@ -349,8 +271,8 @@ public class ViaTest extends AbstractMqttTest {
     }
   }
 
-  private void checkData(int numberOfMqtt2MqttValues, String mqtt2MqttValue, String rest2RestValue, String mqtt2RestValue, int numberOfRest2MqttValues, String rest2MqttValue, int numberOfBoth2MqttValues, String both2MqttValue, String both2RestValue) throws InterruptedException {
-    TestUtils.waitForMqtt();
+  private void checkData(int numberOfMqtt2MqttValues, String mqtt2MqttValue, String rest2RestValue, String mqtt2RestValue, int numberOfRest2MqttValues, String rest2MqttValue, int numberOfBoth2MqttValues, String both2MqttValue, String both2RestValue) {
+    checker.check();
     dataMqtt2Mqtt.assertEqualData(numberOfMqtt2MqttValues, mqtt2MqttValue);
     dataRest2Mqtt.assertEqualData(numberOfRest2MqttValues, rest2MqttValue);
     dataBoth2Mqtt.assertEqualData(numberOfBoth2MqttValues, both2MqttValue);
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/WarningsTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/WarningsTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..50dc46d2bff45651d46b473c7bf6a8268717385b
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/WarningsTest.java
@@ -0,0 +1,92 @@
+package org.jastadd.ragconnect.tests;
+
+import org.jastadd.ragconnect.tests.utils.TestUtils;
+import org.junit.jupiter.api.BeforeAll;
+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.util.Collections;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.jastadd.ragconnect.tests.utils.TestUtils.readFile;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Test warning messages.
+ *
+ * @author rschoene - Initial contribution
+ */
+public class WarningsTest extends RagConnectTest {
+
+  private static final String WARNING_DIRECTORY = "warnings/";
+  private static final String OUTPUT_DIRECTORY = TestUtils.OUTPUT_DIRECTORY_PREFIX + WARNING_DIRECTORY;
+
+  private static final String DEFAULT_GRAMMAR_NAME = "Test";
+
+  private static final String OPTION_INCREMENTAL_PARAM = "--incremental=param";
+  private static final String OPTION_TRACE_FLUSH = "--tracing=flush";
+  private static final String OPTION_CACHE_ALL = "--cache=all";
+
+  @BeforeAll
+  public static void createOutputDirectory() {
+    File outputDirectory = new File(OUTPUT_DIRECTORY);
+    assertTrue((outputDirectory.exists() && outputDirectory.isDirectory()) || outputDirectory.mkdir());
+  }
+
+  @Test
+  public void testNoDependenciesAndInc() throws IOException {
+    // pass "null" as expectedName means that no warnings are expected
+    test(null, "TestNoDependencies", OPTION_INCREMENTAL_PARAM, OPTION_TRACE_FLUSH);
+  }
+
+  @Test
+  public void testSomeDependenciesAndInc() throws IOException {
+    test("SomeDependenciesAndInc", "TestSomeDependencies",
+            OPTION_INCREMENTAL_PARAM, OPTION_TRACE_FLUSH);
+  }
+
+  @Test
+  public void testNoDependenciesAndNoInc() throws IOException {
+    test("NoDependenciesAndNoInc", "TestNoDependencies");
+  }
+
+  @Test
+  public void testNoIncAndCacheAll() throws IOException {
+    test("NoDependenciesAndNoIncAndCacheAll", "TestNoDependencies", OPTION_CACHE_ALL);
+    test("SomeDependenciesAndNoIncAndCacheAll", "TestSomeDependencies", OPTION_CACHE_ALL);
+  }
+
+  private void test(String expectedName, String connectName, String... additionalArguments) throws IOException {
+    String grammarFile = WARNING_DIRECTORY + DEFAULT_GRAMMAR_NAME + ".relast";
+    List<String> connectFiles = Collections.singletonList(WARNING_DIRECTORY + connectName + ".connect");
+    Path outPath = TestUtils.runCompiler(
+            grammarFile, connectFiles, "Root", WARNING_DIRECTORY, 0, additionalArguments
+    );
+
+    String out = readFile(outPath, Charset.defaultCharset());
+    final boolean expectWarnings = expectedName != null;
+    final String startOfWarningsPattern = "Warnings:\n";
+    if (expectWarnings) {
+      assertThat(out).contains(startOfWarningsPattern);
+    } else {
+      assertThat(out).doesNotContain(startOfWarningsPattern);
+    }
+
+    final String startOfErrorsPattern = "Errors:\n";
+    assertThat(out).doesNotContain(startOfErrorsPattern);
+
+    if (!expectWarnings) {
+      return;
+    }
+
+    out = out.substring(out.indexOf(startOfWarningsPattern) + startOfWarningsPattern.length());
+
+    logger.debug("ragconnect for " + expectedName + " returned:\n{}", out);
+
+    TestUtils.assertLinesMatch(WARNING_DIRECTORY, expectedName, out);
+  }
+}
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/list/AbstractListTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/list/AbstractListTest.java
index 5e26ba01360aaad05681c6ee3c045be0a6ca835d..d4a432f93e0dc6b1b930a2d2feb05d206fcec15d 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/list/AbstractListTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/list/AbstractListTest.java
@@ -1,8 +1,8 @@
 package org.jastadd.ragconnect.tests.list;
 
 import org.jastadd.ragconnect.tests.AbstractMqttTest;
-import org.jastadd.ragconnect.tests.TestUtils;
-import org.jastadd.ragconnect.tests.TestUtils.IntList;
+import org.jastadd.ragconnect.tests.utils.IntList;
+import org.jastadd.ragconnect.tests.utils.TestChecker;
 import org.junit.jupiter.api.Tag;
 import org.junit.jupiter.api.Test;
 
@@ -11,8 +11,8 @@ import java.nio.file.Paths;
 import java.util.List;
 import java.util.function.Function;
 
-import static org.jastadd.ragconnect.tests.TestUtils.IntList.list;
-import static org.jastadd.ragconnect.tests.TestUtils.testJaddContainReferenceToJackson;
+import static org.jastadd.ragconnect.tests.utils.IntList.list;
+import static org.jastadd.ragconnect.tests.utils.TestUtils.testJaddContainReferenceToJackson;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
 /**
@@ -55,12 +55,9 @@ public abstract class AbstractListTest extends AbstractMqttTest {
     int getID();
   }
 
-  AbstractListTest(String shortName) {
-    this.shortName = shortName;
-  }
-
   protected static final String TOPIC_A = "a";
   protected static final String TOPIC_SINGLE_A = "single-a";
+  protected TestChecker checker;
 
   protected TestWrapperReceiverRoot receiverRoot;
   protected ReceiverData data;
@@ -68,6 +65,12 @@ public abstract class AbstractListTest extends AbstractMqttTest {
 
   private final String shortName;
 
+  AbstractListTest(String shortName) {
+    this.shortName = shortName;
+    checker = new TestChecker();
+    this.checker.setActualNumberOfValues(() -> data.numberOfElements);
+  }
+
   @Test
   public void checkJacksonReference() {
     testJaddContainReferenceToJackson(
@@ -76,53 +79,50 @@ public abstract class AbstractListTest extends AbstractMqttTest {
   }
 
   @Override
-  protected void communicateSendInitialValue() throws InterruptedException, IOException {
+  protected void communicateSendInitialValue() throws IOException {
+    checker.incNumberOfValues();
     checkTree(1, list(), list(0), list(), list(0));
 
-    setInput(1);
-    checkTree(2, list(1), list(1), list(1), list(0, 1));
-
-    setInput(1);
-    checkTree(2, list(1), list(1), list(1), list(0, 1));
-
-    setInput(2);
-    checkTree(3, list(1, 2), list(2), list(1, 1, 2), list(0, 1, 2));
-
-    setInput(3);
-    checkTree(4, list(1, 2, 3), list(3), list(1, 1, 2, 1, 2, 3), list(0, 1, 2, 3));
-
-    disconnectReceive();
-    setInput(4);
-    checkTree(5, list(1, 2, 3), list(3), list(1, 1, 2, 1, 2, 3), list(0, 1, 2, 3));
-
-    disconnectSend();
-    setInput(5);
-    checkTree(5, list(1, 2, 3), list(3), list(1, 1, 2, 1, 2, 3), list(0, 1, 2, 3));
+    communicateBoth(1, 0);
   }
 
   @Override
-  protected void communicateOnlyUpdatedValue() throws InterruptedException, IOException {
+  protected void communicateOnlyUpdatedValue() throws IOException {
     checkTree(0, list(), list(), list(), list());
 
+    communicateBoth(0, null);
+  }
+
+  private void communicateBoth(int initialNumberOfValues, Integer firstElementOfWithAddFromSingleA) throws IOException {
     setInput(1);
-    checkTree(1, list(1), list(1), list(1), list(1));
+    checker.incNumberOfValues();
+    checkTree(initialNumberOfValues + 1, list(1), list(1), list(1),
+            list(firstElementOfWithAddFromSingleA, 1));
 
     setInput(1);
-    checkTree(1, list(1), list(1), list(1), list(1));
+    checkTree(initialNumberOfValues + 1, list(1), list(1), list(1),
+            list(firstElementOfWithAddFromSingleA, 1));
 
     setInput(2);
-    checkTree(2, list(1, 2), list(2), list(1, 1, 2), list(1, 2));
+    checker.incNumberOfValues();
+    checkTree(initialNumberOfValues + 2, list(1, 2), list(2), list(1, 1, 2),
+            list(firstElementOfWithAddFromSingleA, 1, 2));
 
     setInput(3);
-    checkTree(3, list(1, 2, 3), list(3), list(1, 1, 2, 1, 2, 3), list(1, 2, 3));
+    checker.incNumberOfValues();
+    checkTree(initialNumberOfValues + 3, list(1, 2, 3), list(3), list(1, 1, 2, 1, 2, 3),
+            list(firstElementOfWithAddFromSingleA, 1, 2, 3));
 
     disconnectReceive();
     setInput(4);
-    checkTree(4, list(1, 2, 3), list(3), list(1, 1, 2, 1, 2, 3), list(1, 2, 3));
+    checker.incNumberOfValues();
+    checkTree(initialNumberOfValues + 4, list(1, 2, 3), list(3), list(1, 1, 2, 1, 2, 3),
+            list(firstElementOfWithAddFromSingleA, 1, 2, 3));
 
     disconnectSend();
     setInput(5);
-    checkTree(4, list(1, 2, 3), list(3), list(1, 1, 2, 1, 2, 3), list(1, 2, 3));
+    checkTree(initialNumberOfValues + 4, list(1, 2, 3), list(3), list(1, 1, 2, 1, 2, 3),
+            list(firstElementOfWithAddFromSingleA, 1, 2, 3));
   }
 
   protected abstract void disconnectReceive() throws IOException;
@@ -131,13 +131,13 @@ public abstract class AbstractListTest extends AbstractMqttTest {
 
   protected abstract void setInput(int input);
 
-  private void checkTree(int expectedTransmissions, IntList normalA, IntList fromSingleA, IntList withAddA, IntList withAddFromSingleA) throws InterruptedException {
-    TestUtils.waitForMqtt();
+  private void checkTree(int expectedTransmissions, IntList normalA, IntList fromSingleA, IntList withAddA, IntList withAddFromSingleA) {
+    checker.check();
 
     assertEquals(expectedTransmissions, data.numberOfElements, "transmissions for normal");
     assertEquals(expectedTransmissions, dataSingle.numberOfElements, "transmissions for single");
 
-    checkList(normalA.toList(), receiverRoot.getNumA(), receiverRoot::getA, true);
+    checkList(normalA.toList(), receiverRoot.getNumA(), receiverRoot::getA);
     checkList(normalA.toList(), receiverRoot.getAList(), true);
     checkList(normalA.toList(), receiverRoot.getAs(), true);
 
@@ -151,15 +151,13 @@ public abstract class AbstractListTest extends AbstractMqttTest {
     checkList(withAddFromSingleA.toList(), receiverRoot.getWithAddFromSingleAs(), false);
   }
 
-  private void checkList(List<Integer> expectedList, int numChildren, Function<Integer, TestWrapperA> getA, boolean expectB) {
+  private void checkList(List<Integer> expectedList, int numChildren, Function<Integer, TestWrapperA> getA) {
     assertEquals(expectedList.size(), numChildren, "same list size");
     for (int index = 0; index < expectedList.size(); index++) {
       TestWrapperA a = getA.apply(index);
       assertEquals(expectedList.get(index), a.getID(), "correct ID for A");
-      if (expectB) {
-        assertEquals(1, a.getNumB(), "one B child");
-        assertEquals(expectedList.get(index) + 1, a.getB(0).getID(), "correct ID for B child");
-      }
+      assertEquals(1, a.getNumB(), "one B child");
+      assertEquals(expectedList.get(index) + 1, a.getB(0).getID(), "correct ID for B child");
     }
   }
 
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/list/ListIncrementalTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/list/ListIncrementalTest.java
index ac8b9415523c2a1e9121b5647e1d886fd2efba09..1aabdc43ed94a5f3efaf0e01184beeaba3b56c23 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/list/ListIncrementalTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/list/ListIncrementalTest.java
@@ -1,13 +1,13 @@
 package org.jastadd.ragconnect.tests.list;
 
 import listInc.ast.*;
-import org.jastadd.ragconnect.tests.TestUtils;
+import org.jastadd.ragconnect.tests.utils.TestUtils;
 import org.junit.jupiter.api.Tag;
 
 import java.io.IOException;
 import java.util.concurrent.TimeUnit;
 
-import static org.jastadd.ragconnect.tests.TestUtils.mqttUri;
+import static org.jastadd.ragconnect.tests.utils.TestUtils.mqttUri;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
@@ -39,7 +39,7 @@ public class ListIncrementalTest extends AbstractListTest {
   }
 
   @Override
-  protected void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException {
+  protected void setupReceiverAndConnect() throws IOException {
     model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS);
 
     handler = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost());
@@ -58,8 +58,8 @@ public class ListIncrementalTest extends AbstractListTest {
     assertTrue(receiverRoot.connectWithAddFromAList(mqttUri(TOPIC_A)));
     assertTrue(receiverRoot.connectWithAddFromSingleAList(mqttUri(TOPIC_SINGLE_A)));
 
-    assertTrue(senderRoot.connectAList(mqttUri(TOPIC_A), writeCurrentValue));
-    assertTrue(senderRoot.connectSingleAList(mqttUri(TOPIC_SINGLE_A), writeCurrentValue));
+    assertTrue(senderRoot.connectAList(mqttUri(TOPIC_A), isWriteCurrentValue()));
+    assertTrue(senderRoot.connectSingleAList(mqttUri(TOPIC_SINGLE_A), isWriteCurrentValue()));
   }
 
   @Override
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/list/ListManualTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/list/ListManualTest.java
index 978359f772ba91187cae829ea0952807e1892ce2..1b9aadcb3d25a69d1405798e4646061fa659f3fe 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/list/ListManualTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/list/ListManualTest.java
@@ -4,12 +4,12 @@ import list.ast.MqttHandler;
 import list.ast.ReceiverRoot;
 import list.ast.Root;
 import list.ast.SenderRoot;
-import org.jastadd.ragconnect.tests.TestUtils;
+import org.jastadd.ragconnect.tests.utils.TestUtils;
 
 import java.io.IOException;
 import java.util.concurrent.TimeUnit;
 
-import static org.jastadd.ragconnect.tests.TestUtils.mqttUri;
+import static org.jastadd.ragconnect.tests.utils.TestUtils.mqttUri;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
@@ -40,7 +40,7 @@ public class ListManualTest extends AbstractListTest {
   }
 
   @Override
-  protected void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException {
+  protected void setupReceiverAndConnect() throws IOException {
     model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS);
 
     handler = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost());
@@ -61,8 +61,8 @@ public class ListManualTest extends AbstractListTest {
     assertTrue(receiverRoot.connectWithAddFromAList(mqttUri(TOPIC_A)));
     assertTrue(receiverRoot.connectWithAddFromSingleAList(mqttUri(TOPIC_SINGLE_A)));
 
-    assertTrue(senderRoot.connectAList(mqttUri(TOPIC_A), writeCurrentValue));
-    assertTrue(senderRoot.connectSingleAList(mqttUri(TOPIC_SINGLE_A), writeCurrentValue));
+    assertTrue(senderRoot.connectAList(mqttUri(TOPIC_A), isWriteCurrentValue()));
+    assertTrue(senderRoot.connectSingleAList(mqttUri(TOPIC_SINGLE_A), isWriteCurrentValue()));
   }
 
   @Override
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/relation/RelationTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/relation/RelationTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..f4ebfc5c10bbbed01ab133445ad65a3728b92a69
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/relation/RelationTest.java
@@ -0,0 +1,692 @@
+package org.jastadd.ragconnect.tests.relation;
+
+import org.jastadd.ragconnect.tests.AbstractMqttTest;
+import org.jastadd.ragconnect.tests.utils.TestUtils;
+import org.junit.jupiter.api.Tag;
+import relationInc.ast.*;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.groups.Tuple.tuple;
+
+import org.jastadd.ragconnect.tests.utils.TestChecker;
+import static org.jastadd.ragconnect.tests.utils.TestUtils.mqttUri;
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Test case "relation".
+ *
+ * @author rschoene - Initial contribution
+ */
+@Tag("Incremental")
+public class RelationTest extends AbstractMqttTest {
+
+  private static final String TOPIC_WILDCARD = "rel/#";
+  private static final String TOPIC_MY_A = "rel/my_a";
+  private static final String TOPIC_OPTIONAL_A = "rel/optional_a";
+  private static final String TOPIC_MANY_A = "rel/many_a";
+  private static final String TOPIC_BI_MY_A = "rel/bi_my_a";
+  private static final String TOPIC_BI_OPTIONAL_A = "rel/bi_optional_a";
+  private static final String TOPIC_BI_MANY_A = "rel/bi_many_a";
+  private static final String TOPIC_MY_B = "rel/my_b";
+  private static final String TOPIC_OPTIONAL_B = "rel/optional_b";
+  private static final String TOPIC_MANY_B = "rel/many_b";
+  private static final String TOPIC_BI_MY_B = "rel/bi_my_b";
+  private static final String TOPIC_BI_OPTIONAL_B = "rel/bi_optional_b";
+  private static final String TOPIC_BI_MANY_B = "rel/bi_many_b";
+
+  private MqttHandler handler;
+  private ReceiverData data;
+  private TestChecker checker;
+
+  private Root model;
+  private SenderRoot senderUni;
+  private SenderRoot senderBi;
+  private ReceiverRoot receiverRoot;
+
+  @Override
+  protected void createModel() {
+    model = new Root();
+//    model.trace().setReceiver(TestUtils::logEvent);
+    senderUni = createSenderRoot("uni");
+    senderUni.setMyA(uniA(1));
+    senderUni.setMyB(uniB(1));
+
+    senderBi = createSenderRoot("bi");
+    senderBi.setBiMyA(biA(1));
+    senderBi.setBiMyB(biB(1));
+
+    receiverRoot = new ReceiverRoot();
+    model.addSenderRoot(senderUni);
+    model.addSenderRoot(senderBi);
+    model.setReceiverRoot(receiverRoot);
+  }
+
+  private SenderRoot createSenderRoot(String name) {
+    SenderRoot result = new SenderRoot();
+    A dummyA = createA(name + "-dummyA");
+    result.addA(dummyA);
+    result.addA(createA(name + "-a1"));
+    result.addA(createA(name + "-a2"));
+    result.addA(createA(name + "-a3"));
+
+    B dummyB = createB(name + "-dummyB");
+    result.addB(dummyB);
+    result.addB(createB(name + "-b1"));
+    result.addB(createB(name + "-b2"));
+    result.addB(createB(name + "-b3"));
+
+    result.setMyA(dummyA);
+    result.setBiMyA(dummyA);
+    result.setMyB(dummyB);
+    result.setBiMyB(dummyB);
+
+    return result;
+  }
+
+  private A createA(String value) {
+    return new A().setValue(value)
+            .setInner(new Inner().setInnerValue("inner-" + value));
+  }
+
+  private B createB(String value) {
+    return new B().setValue(value)
+            .setInner(new Inner().setInnerValue("inner-" + value));
+  }
+
+  private A uniA(int index) {
+    return senderUni.getA(index);
+  }
+
+  private B uniB(int index) {
+    return senderUni.getB(index);
+  }
+
+  private A biA(int index) {
+    return senderBi.getA(index);
+  }
+
+  private B biB(int index) {
+    return senderBi.getB(index);
+  }
+
+  @Override
+  protected void setupReceiverAndConnect() throws IOException, InterruptedException {
+    model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS);
+    handler = new MqttHandler().setHost(TestUtils.getMqttHost()).dontSendWelcomeMessage();
+    assertTrue(handler.waitUntilReady(2, TimeUnit.SECONDS));
+
+    data = new ReceiverData();
+    assertTrue(handler.newConnection(TOPIC_WILDCARD, bytes -> data.numberOfValues += 1));
+
+    checker = new TestChecker();
+    checker.setActualNumberOfValues(() -> data.numberOfValues)
+            .setCheckForString(TOPIC_MY_A,
+                    (name, expected) -> assertNullOrA(expected, receiverRoot.getFromMyA(), name))
+            .setCheckForString(TOPIC_OPTIONAL_A,
+                    (name, expected) -> assertNullOrA(expected, receiverRoot.getFromOptionalA(), name))
+            .setCheckForTuple(TOPIC_MANY_A,
+                    (name, expected) -> assertListEqualsForA(expected.toList(), receiverRoot.getFromManyAList(), name))
+            .setCheckForString(TOPIC_BI_MY_A,
+                    (name, expected) -> assertNullOrA(expected, receiverRoot.getFromBiMyA(), name))
+            .setCheckForString(TOPIC_BI_OPTIONAL_A,
+                    (name, expected) -> assertNullOrA(expected, receiverRoot.getFromBiOptionalA(), name))
+            .setCheckForTuple(TOPIC_BI_MANY_A,
+                    (name, expected) -> assertListEqualsForA(expected.toList(), receiverRoot.getFromBiManyAList(), name))
+            .setCheckForString(TOPIC_MY_B,
+                    (name, expected) -> assertNullOrB(expected, receiverRoot.getFromMyB(), name))
+            .setCheckForString(TOPIC_OPTIONAL_B,
+                    (name, expected) -> assertNullOrB(expected, receiverRoot.getFromOptionalB(), name))
+            .setCheckForTuple(TOPIC_MANY_B,
+                    (name, expected) -> assertListEqualsForB(expected.toList(), receiverRoot.getFromManyB(), name))
+            .setCheckForString(TOPIC_BI_MY_B,
+                    (name, expected) -> assertNullOrB(expected, receiverRoot.getFromBiMyB(), name))
+            .setCheckForString(TOPIC_BI_OPTIONAL_B,
+                    (name, expected) -> assertNullOrB(expected, receiverRoot.getFromBiOptionalB(), name))
+            .setCheckForTuple(TOPIC_BI_MANY_B,
+                    (name, expected) -> assertListEqualsForB(expected.toList(), receiverRoot.getFromBiManyB(), name));
+
+    // connect receive
+    assertTrue(receiverRoot.connectFromMyA(mqttUri(TOPIC_MY_A)));
+    assertTrue(receiverRoot.connectFromOptionalA(mqttUri(TOPIC_OPTIONAL_A)));
+    assertTrue(receiverRoot.connectFromManyAList(mqttUri(TOPIC_MANY_A)));
+    assertTrue(receiverRoot.connectFromBiMyA(mqttUri(TOPIC_BI_MY_A)));
+    assertTrue(receiverRoot.connectFromBiOptionalA(mqttUri(TOPIC_BI_OPTIONAL_A)));
+    assertTrue(receiverRoot.connectFromBiManyAList(mqttUri(TOPIC_BI_MANY_A)));
+    assertTrue(receiverRoot.connectFromMyB(mqttUri(TOPIC_MY_B)));
+    assertTrue(receiverRoot.connectFromOptionalB(mqttUri(TOPIC_OPTIONAL_B)));
+    assertTrue(receiverRoot.connectFromManyB(mqttUri(TOPIC_MANY_B)));
+    assertTrue(receiverRoot.connectFromBiMyB(mqttUri(TOPIC_BI_MY_B)));
+    assertTrue(receiverRoot.connectFromBiOptionalB(mqttUri(TOPIC_BI_OPTIONAL_B)));
+    assertTrue(receiverRoot.connectFromBiManyB(mqttUri(TOPIC_BI_MANY_B)));
+
+    // connect send, and wait to receive (if writeCurrentValue is set)
+    assertTrue(senderUni.connectMyA(mqttUri(TOPIC_MY_A), isWriteCurrentValue()));
+    assertTrue(senderUni.connectOptionalA(mqttUri(TOPIC_OPTIONAL_A), isWriteCurrentValue()));
+    assertTrue(senderUni.connectManyA(mqttUri(TOPIC_MANY_A), isWriteCurrentValue()));
+
+    assertTrue(senderBi.connectBiMyA(mqttUri(TOPIC_BI_MY_A), isWriteCurrentValue()));
+    assertTrue(senderBi.connectBiOptionalA(mqttUri(TOPIC_BI_OPTIONAL_A), isWriteCurrentValue()));
+    assertTrue(senderBi.connectBiManyA(mqttUri(TOPIC_BI_MANY_A), isWriteCurrentValue()));
+
+    assertTrue(senderUni.connectMyB(mqttUri(TOPIC_MY_B), isWriteCurrentValue()));
+    assertTrue(senderUni.connectOptionalB(mqttUri(TOPIC_OPTIONAL_B), isWriteCurrentValue()));
+    assertTrue(senderUni.connectManyB(mqttUri(TOPIC_MANY_B), isWriteCurrentValue()));
+
+    assertTrue(senderBi.connectBiMyB(mqttUri(TOPIC_BI_MY_B), isWriteCurrentValue()));
+    assertTrue(senderBi.connectBiOptionalB(mqttUri(TOPIC_BI_OPTIONAL_B), isWriteCurrentValue()));
+    assertTrue(senderBi.connectBiManyB(mqttUri(TOPIC_BI_MANY_B), isWriteCurrentValue()));
+  }
+
+  @Override
+  protected void communicateSendInitialValue() throws IOException {
+    checker.addToNumberOfValues(8)
+            .put(TOPIC_MY_A, "uni-a1")
+            .put(TOPIC_OPTIONAL_A, (String) null)
+            .put(TOPIC_MANY_A, tuple())
+            .put(TOPIC_BI_MY_A, "bi-a1")
+            .put(TOPIC_BI_OPTIONAL_A, (String) null)
+            .put(TOPIC_BI_MANY_A, tuple())
+            .put(TOPIC_MY_B, "uni-b1")
+            .put(TOPIC_OPTIONAL_B, (String) null)
+            .put(TOPIC_MANY_B, tuple())
+            .put(TOPIC_BI_MY_B, "bi-b1")
+            .put(TOPIC_BI_OPTIONAL_B, (String) null)
+            .put(TOPIC_BI_MANY_B, tuple());
+
+    communicateBoth();
+  }
+
+  @Override
+  protected void communicateOnlyUpdatedValue() throws IOException, InterruptedException {
+    checker.put(TOPIC_MY_A, (String) null)
+            .put(TOPIC_OPTIONAL_A, (String) null)
+            .put(TOPIC_MANY_A, tuple())
+            .put(TOPIC_BI_MY_A, (String) null)
+            .put(TOPIC_BI_OPTIONAL_A, (String) null)
+            .put(TOPIC_BI_MANY_A, tuple())
+            .put(TOPIC_MY_B, (String) null)
+            .put(TOPIC_OPTIONAL_B, (String) null)
+            .put(TOPIC_MANY_B, tuple())
+            .put(TOPIC_BI_MY_B, (String) null)
+            .put(TOPIC_BI_OPTIONAL_B, (String) null)
+            .put(TOPIC_BI_MANY_B, tuple());
+
+    communicateBoth();
+  }
+
+  protected void communicateBoth() throws IOException {
+    checker.check();
+
+    // myA -> uni-a1, myB -> uni-b1
+    // --- testing unmapped unidirectional normal role --- //
+
+    uniA(1).setValue("test-1");
+    checker.incNumberOfValues().put(TOPIC_MY_A, "test-1:inner-uni-a1").check();
+
+    senderUni.setMyA(uniA(2));
+    checker.incNumberOfValues().put(TOPIC_MY_A, "uni-a2").check();
+
+    // changing something that was previously the relation target must not trigger a message
+    uniA(1).setValue("test-2-ignored");
+    checker.check();
+
+    uniA(2).setValue("test-3");
+    checker.incNumberOfValues().put(TOPIC_MY_A, "test-3:inner-uni-a2").check();
+
+    uniA(2).getInner().setInnerValue("test-4");
+    checker.incNumberOfValues().put(TOPIC_MY_A, "test-3:test-4").check();
+
+    // setting a new relation target resulting in the same serialization must not trigger a message
+    uniA(1).setValue("test-3");
+    uniA(1).getInner().setInnerValue("test-4");
+    senderUni.setMyA(uniA(1));
+    checker.check();
+
+    // --- testing unmapped unidirectional optional role --- //
+
+    // reset a2
+    uniA(2).setValue("uni-a2");
+    uniA(2).getInner().setInnerValue("inner-uni-a2");
+
+    senderUni.setOptionalA(uniA(2));
+    checker.incNumberOfValues().put(TOPIC_OPTIONAL_A, "uni-a2").check();
+
+    uniA(2).setValue("test-5");
+    checker.incNumberOfValues().put(TOPIC_OPTIONAL_A, "test-5:inner-uni-a2").check();
+
+    senderUni.setOptionalA(uniA(1));
+    checker.incNumberOfValues().put(TOPIC_OPTIONAL_A, "test-3:test-4").check();
+
+    // change a nonterminal target of two relations must trigger two messages
+    uniA(1).getInner().setInnerValue("test-6");
+    checker.addToNumberOfValues(2)
+            .put(TOPIC_MY_A, "test-3:test-6")
+            .put(TOPIC_OPTIONAL_A, "test-3:test-6")
+            .check();
+
+    // setting an optional relation to null is allowed, but must not trigger a message
+    senderUni.setOptionalA(null);
+    checker.check();
+
+    // setting the previous nonterminal as relation target again won't trigger a message
+    senderUni.setOptionalA(uniA(1));
+    checker.check();
+
+    // --- testing unmapped unidirectional list role --- //
+
+    senderUni.addManyA(uniA(3));
+    checker.incNumberOfValues().put(TOPIC_MANY_A, tuple("uni-a3")).check();
+
+    uniA(3).setValue("test-7");
+    checker.incNumberOfValues().put(TOPIC_MANY_A, tuple("test-7:inner-uni-a3")).check();
+
+    senderUni.addManyA(uniA(2));
+    checker.incNumberOfValues().put(TOPIC_MANY_A, tuple("test-7:inner-uni-a3", "test-5:inner-uni-a2")).check();
+
+    senderUni.addManyA(uniA(1));
+    checker.incNumberOfValues().put(TOPIC_MANY_A, tuple("test-7:inner-uni-a3", "test-5:inner-uni-a2", "test-3:test-6")).check();
+
+    uniA(2).getInner().setInnerValue("test-8");
+    checker.incNumberOfValues().put(TOPIC_MANY_A, tuple("test-7:inner-uni-a3", "test-5:test-8", "test-3:test-6")).check();
+
+    senderUni.removeManyA(uniA(2));
+    checker.incNumberOfValues().put(TOPIC_MANY_A, tuple("test-7:inner-uni-a3", "test-3:test-6")).check();
+
+    // disconnect my-a, optional-a, many-a - resetting afterwards must not trigger a message
+    senderUni.disconnectMyA(mqttUri(TOPIC_MY_A));
+    senderUni.disconnectOptionalA(mqttUri(TOPIC_OPTIONAL_A));
+    senderUni.disconnectManyA(mqttUri(TOPIC_MANY_A));
+    uniA(1).setValue("a1");
+    uniA(1).getInner().setInnerValue("inner-a1");
+    uniA(2).setValue("a2");
+    uniA(2).getInner().setInnerValue("inner-a2");
+    uniA(3).setValue("a3");
+    uniA(3).getInner().setInnerValue("inner-a3");
+    checker.check();
+
+    // "reset" values in receiver-root to make check method call shorted
+    receiverRoot.setFromMyA(createA("uni-a1"));
+    receiverRoot.setFromOptionalA(null);
+    receiverRoot.setFromManyAList(new JastAddList<>());
+    checker.put(TOPIC_MY_A, "uni-a1")
+            .put(TOPIC_OPTIONAL_A, (String) null)
+            .put(TOPIC_MANY_A, tuple());
+    checker.check();
+
+    // biMyA -> bi-a1, biMyB -> bi-b1
+    // --- testing unmapped bidirectional normal role --- //
+    biA(1).setValue("test-9");
+    checker.incNumberOfValues().put(TOPIC_BI_MY_A, "test-9:inner-bi-a1").check();
+
+    // set opposite role of relation must trigger message
+    biA(2).setToMyA(senderBi);
+    checker.incNumberOfValues().put(TOPIC_BI_MY_A, "bi-a2").check();
+
+    // changing something that was previously the relation target must not trigger a message
+    biA(1).setValue("test-9-ignored");
+    checker.check();
+
+    biA(2).setValue("test-10");
+    checker.incNumberOfValues().put(TOPIC_BI_MY_A, "test-10:inner-bi-a2").check();
+
+    biA(2).getInner().setInnerValue("test-11");
+    checker.incNumberOfValues().put(TOPIC_BI_MY_A, "test-10:test-11").check();
+
+    // setting a new relation target resulting in the same serialization must not trigger a message
+    biA(1).setValue("test-10");
+    biA(1).getInner().setInnerValue("test-11");
+    biA(1).setToMyA(senderBi);
+    checker.check();
+
+    // --- testing unmapped bidirectional optional role --- //
+    // reset a2
+    biA(2).setValue("bi-a2");
+    biA(2).getInner().setInnerValue("inner-bi-a2");
+
+    senderBi.setBiOptionalA(biA(2));
+    checker.incNumberOfValues().put(TOPIC_BI_OPTIONAL_A, "bi-a2").check();
+
+    biA(2).setValue("test-12");
+    checker.incNumberOfValues().put(TOPIC_BI_OPTIONAL_A, "test-12:inner-bi-a2").check();
+
+    // set opposite role of relation must trigger message
+    biA(1).setToOptionalA(senderBi);
+    checker.incNumberOfValues().put(TOPIC_BI_OPTIONAL_A, "test-10:test-11").check();
+
+    // change a nonterminal target of two relations must trigger two messages
+    biA(1).getInner().setInnerValue("test-13");
+    checker.addToNumberOfValues(2)
+            .put(TOPIC_BI_MY_A, "test-10:test-13")
+            .put(TOPIC_BI_OPTIONAL_A, "test-10:test-13").check();
+
+    // setting an optional relation to null is allowed, but must not trigger a message
+    senderBi.setBiOptionalA(null);
+    checker.check();
+
+    // setting the previous nonterminal as relation target again won't trigger a message
+    biA(1).setToOptionalA(senderBi);
+    checker.check();
+
+    // --- testing unmapped bidirectional list role --- //
+    biA(3).setToManyA(senderBi);
+    checker.incNumberOfValues().put(TOPIC_BI_MANY_A, tuple("bi-a3")).check();
+
+    biA(3).setValue("test-14");
+    checker.incNumberOfValues().put(TOPIC_BI_MANY_A, tuple("test-14:inner-bi-a3")).check();
+
+    senderBi.addBiManyA(biA(2));
+    checker.incNumberOfValues().put(TOPIC_BI_MANY_A, tuple("test-14:inner-bi-a3", "test-12:inner-bi-a2")).check();
+
+    biA(1).setToManyA(senderBi);
+    checker.incNumberOfValues().put(TOPIC_BI_MANY_A, tuple("test-14:inner-bi-a3", "test-12:inner-bi-a2", "test-10:test-13")).check();
+
+    biA(2).getInner().setInnerValue("test-15");
+    // currently, an additional message is sent at bi_optional_a for biA1 as the serialization includes relations from A to SenderRoot and the relation to ToManyA was added for biA1.
+    // this appears to be a bug in either jastadd or ragconnect
+    // numberOfValues should actually be only 37 here.
+    checker.addToNumberOfValues(2).put(TOPIC_BI_MANY_A, tuple("test-14:inner-bi-a3", "test-12:test-15", "test-10:test-13")).check();
+
+    senderBi.removeBiManyA(biA(2));
+    // the bug from above does not occur here, although the situation is similar
+    // so, only one message is sent
+    checker.incNumberOfValues().put(TOPIC_BI_MANY_A, tuple("test-14:inner-bi-a3", "test-10:test-13")).check();
+
+    biA(3).setToManyA(null);
+    checker.incNumberOfValues().put(TOPIC_BI_MANY_A, tuple("test-10:test-13")).check();
+
+    // change a nonterminal target of three relations must trigger three messages
+    biA(1).setValue("test-16");
+    checker.addToNumberOfValues(3)
+            .put(TOPIC_BI_MY_A, "test-16:test-13")
+            .put(TOPIC_BI_OPTIONAL_A, "test-16:test-13")
+            .put(TOPIC_BI_MANY_A, tuple("test-16:test-13"))
+            .check();
+
+    // disconnect bi-my-a, bi-optional-a, bi-many-a - resetting afterwards must not trigger a message
+    senderBi.disconnectBiMyA(mqttUri(TOPIC_BI_MY_A));
+    senderBi.disconnectBiOptionalA(mqttUri(TOPIC_BI_OPTIONAL_A));
+    senderBi.disconnectBiManyA(mqttUri(TOPIC_BI_MANY_A));
+    biA(1).setValue("bi-a1");
+    biA(1).getInner().setInnerValue("inner-bi-a1");
+    biA(2).setValue("bi-a2");
+    biA(2).getInner().setInnerValue("inner-bi-a2");
+    biA(3).setValue("bi-a3");
+    biA(3).getInner().setInnerValue("inner-bi-a3");
+    checker.check();
+
+    // "reset" values in receiver-root to make check method call shorted
+    receiverRoot.setFromBiMyA(createA("bi-a1"));
+    receiverRoot.setFromBiOptionalA(null);
+    receiverRoot.setFromBiManyAList(new JastAddList<>());
+    checker.put(TOPIC_BI_MY_A, "bi-a1")
+            .put(TOPIC_BI_OPTIONAL_A, (String) null)
+            .put(TOPIC_BI_MANY_A, tuple())
+            .check();
+
+    // --- testing transformed unidirectional normal role --- //
+    uniB(1).setValue("test-17");
+    checker.incNumberOfValues().put(TOPIC_MY_B, "test-17:inner-uni-b1").check();
+
+    senderUni.setMyB(uniB(2));
+    checker.incNumberOfValues().put(TOPIC_MY_B, "uni-b2").check();
+
+    // changing something that was previously the relation target must not trigger a message
+    uniB(1).setValue("test-17-ignored");
+    checker.check();
+
+    uniB(2).setValue("test-18");
+    checker.incNumberOfValues().put(TOPIC_MY_B, "test-18:inner-uni-b2").check();
+
+    uniB(2).getInner().setInnerValue("test-19");
+    checker.incNumberOfValues().put(TOPIC_MY_B, "test-18:test-19").check();
+
+    // setting a new relation target resulting in the same serialization must not trigger a message
+    uniB(1).setValue("test-18");
+    uniB(1).getInner().setInnerValue("test-19");
+    senderUni.setMyB(uniB(1));
+    checker.check();
+
+    // --- testing transformed unidirectional optional role --- //
+
+    // reset a2
+    uniB(2).setValue("uni-b2");
+    uniB(2).getInner().setInnerValue("inner-uni-b2");
+
+    senderUni.setOptionalB(uniB(2));
+    checker.incNumberOfValues().put(TOPIC_OPTIONAL_B, "uni-b2").check();
+
+    uniB(2).setValue("test-20");
+    checker.incNumberOfValues().put(TOPIC_OPTIONAL_B, "test-20:inner-uni-b2").check();
+
+    senderUni.setOptionalB(uniB(1));
+    checker.incNumberOfValues().put(TOPIC_OPTIONAL_B, "test-18:test-19").check();
+
+    // change a nonterminal target of two relations must trigger two messages
+    uniB(1).getInner().setInnerValue("test-21");
+    checker.addToNumberOfValues(2)
+            .put(TOPIC_MY_B, "test-18:test-21")
+            .put(TOPIC_OPTIONAL_B, "test-18:test-21")
+            .check();
+
+    // setting an optional relation to null is allowed, but must not trigger a message
+    senderUni.setOptionalB(null);
+    checker.check();
+
+    // setting the previous nonterminal as relation target again won't trigger a message
+    senderUni.setOptionalB(uniB(1));
+    checker.check();
+
+    // --- testing transformed unidirectional list role --- //
+    senderUni.addManyB(uniB(3));
+    checker.incNumberOfValues().put(TOPIC_MANY_B, tuple("uni-b3")).check();
+
+    uniB(3).setValue("test-22");
+    checker.incNumberOfValues().put(TOPIC_MANY_B, tuple("test-22:inner-uni-b3")).check();
+
+    senderUni.addManyB(uniB(2));
+    checker.incNumberOfValues().put(TOPIC_MANY_B, tuple("test-22:inner-uni-b3", "test-20:inner-uni-b2")).check();
+
+    senderUni.addManyB(uniB(1));
+    checker.incNumberOfValues().put(TOPIC_MANY_B, tuple("test-22:inner-uni-b3", "test-20:inner-uni-b2", "test-18:test-21")).check();
+
+    uniB(2).getInner().setInnerValue("test-23");
+    checker.incNumberOfValues().put(TOPIC_MANY_B, tuple("test-22:inner-uni-b3", "test-20:test-23", "test-18:test-21")).check();
+
+    senderUni.removeManyB(uniB(2));
+    checker.incNumberOfValues().put(TOPIC_MANY_B, tuple("test-22:inner-uni-b3", "test-18:test-21")).check();
+
+    // disconnect my-b, optional-b, many-b - resetting afterwards must not trigger a message
+    senderUni.disconnectMyB(mqttUri(TOPIC_MY_B));
+    senderUni.disconnectOptionalB(mqttUri(TOPIC_OPTIONAL_B));
+    senderUni.disconnectManyB(mqttUri(TOPIC_MANY_B));
+    uniB(1).setValue("b1");
+    uniB(1).getInner().setInnerValue("inner-b1");
+    uniB(2).setValue("b2");
+    uniB(2).getInner().setInnerValue("inner-b2");
+    uniB(3).setValue("b3");
+    uniB(3).getInner().setInnerValue("inner-b3");
+
+    // "reset" values in receiver-root to make check method call shorted
+    receiverRoot.setFromMyB("uni-b1+inner-uni-b1");
+    receiverRoot.setFromOptionalB(null);
+    receiverRoot.setFromManyB(null);
+    checker.put(TOPIC_MY_B, "uni-b1")
+            .put(TOPIC_OPTIONAL_B, (String) null)
+            .put(TOPIC_MANY_B, tuple())
+            .check();
+
+    // --- testing transformed bidirectional normal role --- //
+    biB(1).setValue("test-24");
+    checker.incNumberOfValues().put(TOPIC_BI_MY_B, "test-24:inner-bi-b1").check();
+
+    // set opposite role of relation must trigger message
+    biB(2).setToMyB(senderBi);
+    checker.incNumberOfValues().put(TOPIC_BI_MY_B, "bi-b2").check();
+
+    // changing something that was previously the relation target must not trigger a message
+    biB(1).setValue("test-24-ignored");
+    checker.check();
+
+    biB(2).setValue("test-25");
+    checker.incNumberOfValues().put(TOPIC_BI_MY_B, "test-25:inner-bi-b2").check();
+
+    biB(2).getInner().setInnerValue("test-26");
+    checker.incNumberOfValues().put(TOPIC_BI_MY_B, "test-25:test-26").check();
+
+    // setting a new relation target resulting in the same serialization must not trigger a message
+    biB(1).setValue("test-25");
+    biB(1).getInner().setInnerValue("test-26");
+    biB(1).setToMyB(senderBi);
+    checker.check();
+
+    // --- testing transformed bidirectional optional role --- //
+    // reset b2
+    biB(2).setValue("bi-b2");
+    biB(2).getInner().setInnerValue("inner-bi-b2");
+
+    senderBi.setBiOptionalB(biB(2));
+    checker.incNumberOfValues().put(TOPIC_BI_OPTIONAL_B, "bi-b2").check();
+
+    biB(2).setValue("test-27");
+    checker.incNumberOfValues().put(TOPIC_BI_OPTIONAL_B, "test-27:inner-bi-b2").check();
+
+    // set opposite role of relation must trigger message
+    biB(1).setToOptionalB(senderBi);
+    checker.incNumberOfValues().put(TOPIC_BI_OPTIONAL_B, "test-25:test-26").check();
+
+    // change a nonterminal target of two relations must trigger two messages
+    biB(1).getInner().setInnerValue("test-28");
+    checker.addToNumberOfValues(2)
+            .put(TOPIC_BI_MY_B, "test-25:test-28")
+            .put(TOPIC_BI_OPTIONAL_B, "test-25:test-28")
+            .check();
+
+    // setting an optional relation to null is allowed, but must not trigger a message
+    senderBi.setBiOptionalB(null);
+    checker.check();
+
+    // setting the previous nonterminal as relation target again won't trigger a message
+    biB(1).setToOptionalB(senderBi);
+    checker.check();
+
+    // --- testing transformed bidirectional list role --- //
+    biB(3).setToManyB(senderBi);
+    checker.incNumberOfValues().put(TOPIC_BI_MANY_B, tuple("bi-b3")).check();
+
+    biB(3).setValue("test-29");
+    checker.incNumberOfValues().put(TOPIC_BI_MANY_B, tuple("test-29:inner-bi-b3")).check();
+
+    senderBi.addBiManyB(biB(2));
+    checker.incNumberOfValues().put(TOPIC_BI_MANY_B, tuple("test-29:inner-bi-b3", "test-27:inner-bi-b2")).check();
+
+    biB(1).setToManyB(senderBi);
+    checker.incNumberOfValues().put(TOPIC_BI_MANY_B, tuple("test-29:inner-bi-b3", "test-27:inner-bi-b2", "test-25:test-28")).check();
+
+    biB(2).getInner().setInnerValue("test-30");
+    // the bug appearing in the unmapped bidirectional list case does not appear here, because here only a string representation of value + inner value is sent. so changes to relations do not trigger a message
+    checker.incNumberOfValues().put(TOPIC_BI_MANY_B, tuple("test-29:inner-bi-b3", "test-27:test-30", "test-25:test-28")).check();
+
+    senderBi.removeBiManyB(biB(2));
+    // the bug from above does not occur here, although the situation is similar
+    // so, only one message is sent
+    checker.incNumberOfValues().put(TOPIC_BI_MANY_B, tuple("test-29:inner-bi-b3", "test-25:test-28")).check();
+
+    biB(3).setToManyB(null);
+    checker.incNumberOfValues().put(TOPIC_BI_MANY_B, tuple("test-25:test-28")).check();
+
+    // change a nonterminal target of three relations must trigger three messages
+    biB(1).setValue("test-31");
+    checker.addToNumberOfValues(3)
+            .put(TOPIC_BI_MY_B, "test-31:test-28")
+            .put(TOPIC_BI_OPTIONAL_B, "test-31:test-28")
+            .put(TOPIC_BI_MANY_B, tuple("test-31:test-28"))
+            .check();
+
+    // disconnect bi-my-a, bi-optional-a, bi-many-a - resetting afterwards must not trigger a message
+    senderBi.disconnectBiMyB(mqttUri(TOPIC_BI_MY_B));
+    senderBi.disconnectBiOptionalB(mqttUri(TOPIC_BI_OPTIONAL_B));
+    senderBi.disconnectBiManyB(mqttUri(TOPIC_BI_MANY_B));
+    biB(1).setValue("bi-b1");
+    biB(1).getInner().setInnerValue("inner-bi-b1");
+    biB(2).setValue("bi-b2");
+    biB(2).getInner().setInnerValue("inner-bi-b2");
+    biB(3).setValue("bi-b3");
+    biB(3).getInner().setInnerValue("inner-bi-b3");
+    checker.check();
+
+    logger.debug(model.ragconnectEvaluationCounterSummary());
+  }
+
+  private void assertNullOrA(String expectedValue, A actual, String alias) {
+    if (expectedValue == null) {
+      assertNull(actual, alias);
+      return;
+    }
+    final String expectedInner;
+    if (expectedValue.contains(":")) {
+      String[] tokens = expectedValue.split(":");
+      assertEquals(2, tokens.length);
+      expectedValue = tokens[0];
+      expectedInner = tokens[1];
+    } else {
+      expectedInner = "inner-" + expectedValue;
+    }
+    assertThat(actual.getValue()).describedAs(alias + ".Value").isEqualTo(expectedValue);
+    assertThat(actual.getInner()).describedAs(alias + ".inner != null").isNotNull();
+    assertThat(actual.getInner().getInnerValue()).describedAs(alias + ".inner.Value").isEqualTo(expectedInner);
+  }
+
+  private void assertListEqualsForA(List<Object> expected, JastAddList<A> actual, String alias) {
+    assertEquals(expected.size(), actual.getNumChild(), alias + ".size");
+    for (int i = 0, expectedSize = expected.size(); i < expectedSize; i++) {
+      String s = (String) expected.get(i);
+      assertNullOrA(s, actual.getChild(i), alias + "[" + i + "]");
+    }
+  }
+
+  private void assertNullOrB(String expectedValue, String actual, String alias) {
+    final String expectedTransformed;
+    if (expectedValue == null) {
+      expectedTransformed = "";
+    } else {
+      final String expectedInner;
+      if (expectedValue.contains(":")) {
+        String[] tokens = expectedValue.split(":");
+        assertEquals(2, tokens.length);
+        expectedValue = tokens[0];
+        expectedInner = tokens[1];
+      } else {
+        expectedInner = "inner-" + expectedValue;
+      }
+      expectedTransformed = expectedValue + "+" + expectedInner;
+    }
+    assertEquals(expectedTransformed, actual, alias);
+  }
+
+  private void assertListEqualsForB(List<Object> expected, String actual, String alias) {
+    String[] actualTokens = actual.isEmpty() ? new String[0] : actual.split(";");
+    assertEquals(expected.size(), actualTokens.length, alias + ".size");
+    for (int i = 0, expectedSize = expected.size(); i < expectedSize; i++) {
+      String s = (String) expected.get(i);
+      assertNullOrB(s, actualTokens[i], alias + "[" + i + "]");
+    }
+  }
+
+  @Override
+  protected void closeConnections() {
+    if (handler != null) {
+      handler.close();
+    }
+    if (model != null) {
+      model.ragconnectCloseConnections();
+    }
+  }
+
+  private static class ReceiverData {
+    int numberOfValues = 0;
+  }
+}
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleList/AbstractSingleListTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleList/AbstractSingleListTest.java
index ff1e655370b85e3c75a69d7b5b4cab0a44fb8329..f3b792d017ef831cd7b29ea6fb05e092cac1f38e 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleList/AbstractSingleListTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleList/AbstractSingleListTest.java
@@ -1,25 +1,22 @@
 package org.jastadd.ragconnect.tests.singleList;
 
 import org.jastadd.ragconnect.tests.AbstractMqttTest;
-import org.jastadd.ragconnect.tests.TestUtils;
-import org.jastadd.ragconnect.tests.TestUtils.IntList;
+import org.jastadd.ragconnect.tests.utils.IntList;
+import org.jastadd.ragconnect.tests.utils.TestChecker;
+import org.jastadd.ragconnect.tests.utils.TestUtils;
 import org.junit.jupiter.api.Tag;
 import org.junit.jupiter.api.Test;
 import singleList.ast.MqttHandler;
 
 import java.io.IOException;
 import java.nio.file.Paths;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Function;
 
-import static org.jastadd.ragconnect.tests.TestUtils.IntList.list;
-import static org.jastadd.ragconnect.tests.TestUtils.mqttUri;
-import static org.jastadd.ragconnect.tests.TestUtils.testJaddContainReferenceToJackson;
+import static org.jastadd.ragconnect.tests.utils.TestUtils.*;
+import static org.jastadd.ragconnect.tests.utils.IntList.list;
 import static org.junit.jupiter.api.Assertions.*;
 
 /**
@@ -31,6 +28,9 @@ import static org.junit.jupiter.api.Assertions.*;
 @Tag("SingleList")
 public abstract class AbstractSingleListTest extends AbstractMqttTest {
 
+  private final TestChecker checker;
+  protected MqttHandler handler;
+
   public interface TestWrapperJastAddList<T> extends Iterable<T> {
     int getNumChild();
   }
@@ -88,6 +88,8 @@ public abstract class AbstractSingleListTest extends AbstractMqttTest {
 
   AbstractSingleListTest(String shortName) {
     this.shortName = shortName;
+    this.checker = new TestChecker();
+    this.checker.setActualNumberOfValues(() -> data.numberOfElements);
   }
 
   protected static final String TOPIC_A_1 = "a/first";
@@ -111,7 +113,7 @@ public abstract class AbstractSingleListTest extends AbstractMqttTest {
   }
 
   @Override
-  protected void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException, InterruptedException {
+  protected void setupReceiverAndConnect() throws IOException, InterruptedException {
     // late model initialization
     senderRoot.setInput1(0);
     senderRoot.setInput2(0);
@@ -141,128 +143,133 @@ public abstract class AbstractSingleListTest extends AbstractMqttTest {
     assertTrue(receiverRoot.connectUsingWildcardWithAddA(mqttUri(TOPIC_A_WILDCARD)));
 
     // send: explicit topics, wait between connections to ensure correct arrival at receiver
-    MqttHandler checkArrivalHandler = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost());
-    Map<String, CountDownLatch> arrived = new HashMap<>() {{
-      put(TOPIC_A_1, new CountDownLatch(1));
-      put(TOPIC_A_2, new CountDownLatch(1));
-      put(TOPIC_A_3, new CountDownLatch(1));
-      put(TOPIC_A_4, new CountDownLatch(1));
-    }};
-    checkArrivalHandler.waitUntilReady(2, TimeUnit.SECONDS);
-    checkArrivalHandler.newConnection("#", (topic, bytes) ->
-        Optional.ofNullable(arrived.get(topic)).ifPresent(CountDownLatch::countDown));
-
-    assertTrue(senderRoot.connectA4(mqttUri(TOPIC_A_4), writeCurrentValue));
-    if (writeCurrentValue) {
-      assertTrue(arrived.get(TOPIC_A_4).await(2, TimeUnit.SECONDS));
-    }
+    handler = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost());
+    data = new ReceiverData();
+    handler.waitUntilReady(2, TimeUnit.SECONDS);
+    handler.newConnection("#", bytes -> {
+      data.valueSentSinceLastCheck.set(true);
+      data.numberOfElements += 1;
+    });
 
-    assertTrue(senderRoot.connectA3(mqttUri(TOPIC_A_3), writeCurrentValue));
-    if (writeCurrentValue) {
-      assertTrue(arrived.get(TOPIC_A_3).await(2, TimeUnit.SECONDS));
-    }
+    assertTrue(senderRoot.connectA4(mqttUri(TOPIC_A_4), isWriteCurrentValue()));
+    waitForValue();
 
-    assertTrue(senderRoot.connectA2(mqttUri(TOPIC_A_2), writeCurrentValue));
-    if (writeCurrentValue) {
-      assertTrue(arrived.get(TOPIC_A_2).await(2, TimeUnit.SECONDS));
-    }
+    assertTrue(senderRoot.connectA3(mqttUri(TOPIC_A_3), isWriteCurrentValue()));
+    waitForValue();
 
-    assertTrue(senderRoot.connectA1(mqttUri(TOPIC_A_1), writeCurrentValue));
-    if (writeCurrentValue) {
-      assertTrue(arrived.get(TOPIC_A_1).await(2, TimeUnit.SECONDS));
-    }
+    assertTrue(senderRoot.connectA2(mqttUri(TOPIC_A_2), isWriteCurrentValue()));
+    waitForValue();
 
-    assertTrue(senderRoot.connectInOutput(mqttUri(TOPIC_A_5_INOUT), writeCurrentValue));
+    assertTrue(senderRoot.connectA1(mqttUri(TOPIC_A_1), isWriteCurrentValue()));
+    waitForValue();
+
+    assertTrue(senderRoot.connectInOutput(mqttUri(TOPIC_A_5_INOUT), isWriteCurrentValue()));
     // no need to wait here, because first "checkTree" will wait anyway
-    checkArrivalHandler.close();
+  }
+
+  private void waitForValue() {
+    if (isWriteCurrentValue()) {
+      awaitMqtt().until(() -> data.valueSentSinceLastCheck.getAndSet(false));
+    }
   }
 
   abstract protected void setupReceiverAndConnectPart() throws IOException;
 
   @Override
   protected void communicateSendInitialValue() throws InterruptedException, IOException {
-    checkTree(5, list(1, 2, 3, 4, 0), list(4, 3, 2, 1, 0), // normal: (normal / wildcard)
-                                     list(4, 3, 2, 1, 0), list(4, 3, 2, 1, 0)); // withAdd: (normal / wildcard)
+    checker.addToNumberOfValues(5);
+    checkTree(list(1, 2, 3, 4, 0), list(4, 3, 2, 1, 0), // normal: (normal / wildcard)
+            list(4, 3, 2, 1, 0), list(4, 3, 2, 1, 0)); // withAdd: (normal / wildcard)
 
     // A1 will be 2 (1+1, previously 1)
     setInput(1, 1);
-    checkTree(6, list(2, 2, 3, 4, 0), list(4, 3, 2, 2, 0), // normal: (normal / wildcard)
-                                     list(4, 3, 2, 1, 0, 2), list(4, 3, 2, 1, 0, 2)); // withAdd: (normal / wildcard)
+    checker.incNumberOfValues();
+    checkTree(list(2, 2, 3, 4, 0), list(4, 3, 2, 2, 0), // normal: (normal / wildcard)
+            list(4, 3, 2, 1, 0, 2), list(4, 3, 2, 1, 0, 2)); // withAdd: (normal / wildcard)
 
     // A1 should stay at 2
     setInput(1, 1);
-    checkTree(6, list(2, 2, 3, 4, 0), list(4, 3, 2, 2, 0), // normal: (normal / wildcard)
-                                     list(4, 3, 2, 1, 0, 2), list(4, 3, 2, 1, 0, 2)); // withAdd: (normal / wildcard)
+    checkTree(list(2, 2, 3, 4, 0), list(4, 3, 2, 2, 0), // normal: (normal / wildcard)
+            list(4, 3, 2, 1, 0, 2), list(4, 3, 2, 1, 0, 2)); // withAdd: (normal / wildcard)
 
     // A1 will be 3 (2+1, previously 2)
     setInput(1, 2);
-    checkTree(7, list(3, 2, 3, 4, 0), list(4, 3, 2, 3, 0), // normal: (normal / wildcard)
-                                     list(4, 3, 2, 1, 0, 2, 3), list(4, 3, 2, 1, 0, 2, 3)); // withAdd: (normal / wildcard)
+    checker.incNumberOfValues();
+    checkTree(list(3, 2, 3, 4, 0), list(4, 3, 2, 3, 0), // normal: (normal / wildcard)
+            list(4, 3, 2, 1, 0, 2, 3), list(4, 3, 2, 1, 0, 2, 3)); // withAdd: (normal / wildcard)
 
     // InOut will be 5 (previously 0)
     setInput(5, 5);
-    checkTree(8, list(3, 2, 3, 4, 5), list(4, 3, 2, 3, 5), // normal: (normal / wildcard)
-                                     list(4, 3, 2, 1, 0, 2, 3, 5), list(4, 3, 2, 1, 0, 2, 3, 5)); // withAdd: (normal / wildcard)
+    checker.incNumberOfValues();
+    checkTree(list(3, 2, 3, 4, 5), list(4, 3, 2, 3, 5), // normal: (normal / wildcard)
+            list(4, 3, 2, 1, 0, 2, 3, 5), list(4, 3, 2, 1, 0, 2, 3, 5)); // withAdd: (normal / wildcard)
 
     // A3 will be 7 (4+3, previously 3)
     setInput(3, 4);
-    checkTree(9, list(3, 2, 7, 4, 5), list(4, 7, 2, 3, 5), // normal: (normal / wildcard)
-                                     list(4, 3, 2, 1, 0, 2, 3, 5, 7), list(4, 3, 2, 1, 0, 2, 3, 5, 7)); // withAdd: (normal / wildcard)
+    checker.incNumberOfValues();
+    checkTree(list(3, 2, 7, 4, 5), list(4, 7, 2, 3, 5), // normal: (normal / wildcard)
+            list(4, 3, 2, 1, 0, 2, 3, 5, 7), list(4, 3, 2, 1, 0, 2, 3, 5, 7)); // withAdd: (normal / wildcard)
 
-    // A2 will be send, but not received
+    // A2 will be sent, but not received
     disconnectReceive();
     setInput(2, 5);
-    checkTree(10, list(3, 2, 7, 4, 5), list(4, 7, 2, 3, 5), // normal: (normal / wildcard)
-        list(4, 3, 2, 1, 0, 2, 3, 5, 7), list(4, 3, 2, 1, 0, 2, 3, 5, 7)); // withAdd: (normal / wildcard)
+    checker.incNumberOfValues();
+    checkTree(list(3, 2, 7, 4, 5), list(4, 7, 2, 3, 5), // normal: (normal / wildcard)
+            list(4, 3, 2, 1, 0, 2, 3, 5, 7), list(4, 3, 2, 1, 0, 2, 3, 5, 7)); // withAdd: (normal / wildcard)
 
-    // A2 will not be send
+    // A2 will not be sent
     disconnectSend();
     setInput(2, 7);
-    checkTree(10, list(3, 2, 7, 4, 5), list(4, 7, 2, 3, 5), // normal: (normal / wildcard)
-        list(4, 3, 2, 1, 0, 2, 3, 5, 7), list(4, 3, 2, 1, 0, 2, 3, 5, 7)); // withAdd: (normal / wildcard)
+    checkTree(list(3, 2, 7, 4, 5), list(4, 7, 2, 3, 5), // normal: (normal / wildcard)
+            list(4, 3, 2, 1, 0, 2, 3, 5, 7), list(4, 3, 2, 1, 0, 2, 3, 5, 7)); // withAdd: (normal / wildcard)
   }
 
   @Override
   protected void communicateOnlyUpdatedValue() throws InterruptedException, IOException {
-    checkTree(0, list(0, 0, 0, 0, 0), list(), // normal
-                                     list(), list()); // withAdd
+    checkTree(list(0, 0, 0, 0, 0), list(), // normal
+            list(), list()); // withAdd
 
     // A1 will be 2 (1+1, previously 1)
     setInput(1, 1);
-    checkTree(1, list(2, 0, 0, 0, 0), list(2), // normal
-                                     list(2), list(2)); // withAdd
+    checker.incNumberOfValues();
+    checkTree(list(2, 0, 0, 0, 0), list(2), // normal
+            list(2), list(2)); // withAdd
 
     // A1 should stay at 2
     setInput(1, 1);
-    checkTree(1, list(2, 0, 0, 0, 0), list(2), // normal
-                                     list(2), list(2)); // withAdd
+    checkTree(list(2, 0, 0, 0, 0), list(2), // normal
+            list(2), list(2)); // withAdd
 
     // A1 will be 3 (2+1, previously 2)
     setInput(1, 2);
-    checkTree(2, list(3, 0, 0, 0, 0), list(3), // normal
-                                     list(2, 3), list(2, 3)); // withAdd
+    checker.incNumberOfValues();
+    checkTree(list(3, 0, 0, 0, 0), list(3), // normal
+            list(2, 3), list(2, 3)); // withAdd
 
     // InOut will be 5 (previously 0)
     setInput(5, 5);
-    checkTree(3, list(3, 0, 0, 0, 5), list(3, 5), // normal
-                                     list(2, 3, 5), list(2, 3, 5)); // withAdd
+    checker.incNumberOfValues();
+    checkTree(list(3, 0, 0, 0, 5), list(3, 5), // normal
+            list(2, 3, 5), list(2, 3, 5)); // withAdd
 
     // A3 will be 7 (4+3, previously 3)
     setInput(3, 4);
-    checkTree(4, list(3, 0, 7, 0, 5), list(3,5,7), // normal
-                                     list(2, 3, 5, 7), list(2, 3, 5, 7)); // withAdd
+    checker.incNumberOfValues();
+    checkTree(list(3, 0, 7, 0, 5), list(3, 5, 7), // normal
+            list(2, 3, 5, 7), list(2, 3, 5, 7)); // withAdd
 
-    // A2 will be send, but not received
+    // A2 will be sent, but not received
     disconnectReceive();
     setInput(2, 5);
-    checkTree(5, list(3, 0, 7, 0, 5), list(3,5,7), // normal
-        list(2, 3, 5, 7), list(2, 3, 5, 7)); // withAdd
+    checker.incNumberOfValues();
+    checkTree(list(3, 0, 7, 0, 5), list(3, 5, 7), // normal
+            list(2, 3, 5, 7), list(2, 3, 5, 7)); // withAdd
 
-    // A2 will not be send
+    // A2 will not be sent
     disconnectSend();
     setInput(2, 7);
-    checkTree(5, list(3, 0, 7, 0, 5), list(3,5,7), // normal
-        list(2, 3, 5, 7), list(2, 3, 5, 7)); // withAdd
+    checkTree(list(3, 0, 7, 0, 5), list(3, 5, 7), // normal
+            list(2, 3, 5, 7), list(2, 3, 5, 7)); // withAdd
   }
 
   protected void disconnectReceive() throws IOException {
@@ -304,9 +311,8 @@ public abstract class AbstractSingleListTest extends AbstractMqttTest {
     assertEquals(input + index, actualComputedValue, "ID value of single A");
   }
 
-  private void checkTree(int expectedTransmissions, IntList normalA, IntList usingWildcardA, IntList withAddA, IntList usingWildcardWithAddA) throws InterruptedException {
-    TestUtils.waitForMqtt();
-    assertEquals(expectedTransmissions, data.numberOfElements, "transmissions for any A");
+  private void checkTree(IntList normalA, IntList usingWildcardA, IntList withAddA, IntList usingWildcardWithAddA) {
+    checker.check();
 
     checkList(normalA.toList(), receiverRoot.getNumA(), receiverRoot::getA);
     checkList(normalA.toList(), receiverRoot.getAList());
@@ -337,6 +343,7 @@ public abstract class AbstractSingleListTest extends AbstractMqttTest {
 
   protected static class ReceiverData {
     int numberOfElements = 0;
+    AtomicBoolean valueSentSinceLastCheck = new AtomicBoolean(false);
   }
 
 }
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleList/SingleListIncrementalTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleList/SingleListIncrementalTest.java
index 54bbf675b2ad4c594a5c001cfaca7342248fbfbc..71280afde1ad015736ea518a161090e795a526ab 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleList/SingleListIncrementalTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleList/SingleListIncrementalTest.java
@@ -1,14 +1,14 @@
 package org.jastadd.ragconnect.tests.singleList;
 
-import org.jastadd.ragconnect.tests.TestUtils;
 import org.junit.jupiter.api.Tag;
-import singleListInc.ast.*;
+import singleListInc.ast.A;
+import singleListInc.ast.ReceiverRoot;
+import singleListInc.ast.Root;
+import singleListInc.ast.SenderRoot;
 
-import java.io.IOException;
 import java.util.concurrent.TimeUnit;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
 
 /**
  * Test case "single list incremental".
@@ -19,7 +19,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 public class SingleListIncrementalTest extends AbstractSingleListTest {
 
   private Root model;
-  private MqttHandler handler;
 
   SingleListIncrementalTest() {
     super("singleListInc");
@@ -43,16 +42,10 @@ public class SingleListIncrementalTest extends AbstractSingleListTest {
   }
 
   @Override
-  protected void setupReceiverAndConnectPart() throws IOException {
+  protected void setupReceiverAndConnectPart() {
     model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS);
 
-    handler = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost());
-    assertTrue(handler.waitUntilReady(2, TimeUnit.SECONDS));
-
     // no dependencies
-
-    data = new ReceiverData();
-    handler.newConnection(TOPIC_A_WILDCARD, bytes -> data.numberOfElements += 1);
   }
 
   @Override
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleList/SingleListManualTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleList/SingleListManualTest.java
index 9a8bf0a0d36d5a2f6e370bb7563453f00e0b5600..39443220a84728f5dcf746775d525534ce8f8a65 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleList/SingleListManualTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleList/SingleListManualTest.java
@@ -1,13 +1,13 @@
 package org.jastadd.ragconnect.tests.singleList;
 
-import org.jastadd.ragconnect.tests.TestUtils;
-import singleList.ast.*;
+import singleList.ast.A;
+import singleList.ast.ReceiverRoot;
+import singleList.ast.Root;
+import singleList.ast.SenderRoot;
 
-import java.io.IOException;
 import java.util.concurrent.TimeUnit;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
 
 /**
  * Test case "single list manual".
@@ -17,7 +17,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 public class SingleListManualTest extends AbstractSingleListTest {
 
   private Root model;
-  private MqttHandler handler;
 
   SingleListManualTest() {
     super("singleList");
@@ -41,20 +40,14 @@ public class SingleListManualTest extends AbstractSingleListTest {
   }
 
   @Override
-  protected void setupReceiverAndConnectPart() throws IOException {
+  protected void setupReceiverAndConnectPart() {
     model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS);
 
-    handler = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost());
-    assertTrue(handler.waitUntilReady(2, TimeUnit.SECONDS));
-
     // add dependencies
     ((SenderRoot) senderRoot).addInputDependencyToA1((SenderRoot) senderRoot);
     ((SenderRoot) senderRoot).addInputDependencyToA2((SenderRoot) senderRoot);
     ((SenderRoot) senderRoot).addInputDependencyToA3((SenderRoot) senderRoot);
     ((SenderRoot) senderRoot).addInputDependencyToA4((SenderRoot) senderRoot);
-
-    data = new ReceiverData();
-    handler.newConnection(TOPIC_A_WILDCARD, bytes -> data.numberOfElements += 1);
   }
 
   @Override
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleListVariant/AbstractSingleListVariantTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleListVariant/AbstractSingleListVariantTest.java
index d4d5f8397b9d8478435c2c8431dd6069800e5a36..6bd40a7a5e8135aec0e2e2f45ed5e271fff1239c 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleListVariant/AbstractSingleListVariantTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleListVariant/AbstractSingleListVariantTest.java
@@ -2,8 +2,8 @@ package org.jastadd.ragconnect.tests.singleListVariant;
 
 import org.assertj.core.api.Assertions;
 import org.jastadd.ragconnect.tests.AbstractMqttTest;
-import org.jastadd.ragconnect.tests.TestUtils;
-import org.jastadd.ragconnect.tests.TestUtils.IntList;
+import org.jastadd.ragconnect.tests.utils.IntList;
+import org.jastadd.ragconnect.tests.utils.TestChecker;
 import org.junit.jupiter.api.Tag;
 import org.junit.jupiter.api.Test;
 
@@ -13,9 +13,9 @@ import java.util.List;
 import java.util.function.BiConsumer;
 
 import static java.lang.Math.abs;
-import static org.jastadd.ragconnect.tests.TestUtils.IntList.list;
-import static org.jastadd.ragconnect.tests.TestUtils.mqttUri;
-import static org.jastadd.ragconnect.tests.TestUtils.testJaddContainReferenceToJackson;
+import static org.jastadd.ragconnect.tests.utils.IntList.list;
+import static org.jastadd.ragconnect.tests.utils.TestUtils.mqttUri;
+import static org.jastadd.ragconnect.tests.utils.TestUtils.testJaddContainReferenceToJackson;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
@@ -28,6 +28,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 @Tag("SingleList")
 public abstract class AbstractSingleListVariantTest extends AbstractMqttTest {
 
+  private final TestChecker checker;
+
   public interface TestWrapperJastAddList<T> extends Iterable<T> {
     int getNumChild();
   }
@@ -155,6 +157,8 @@ public abstract class AbstractSingleListVariantTest extends AbstractMqttTest {
 
   AbstractSingleListVariantTest(String shortName) {
     this.shortName = shortName;
+    this.checker = new TestChecker();
+    this.checker.setActualNumberOfValues(() -> data.numberOfElements);
   }
 
   protected static final String TOPIC_T_Empty = "t/Empty";
@@ -181,7 +185,7 @@ public abstract class AbstractSingleListVariantTest extends AbstractMqttTest {
   }
 
   @Override
-  protected void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException, InterruptedException {
+  protected void setupReceiverAndConnect() throws IOException, InterruptedException {
     // late model initialization
     setInput(0);
     setShouldSetOptAndList(false);
@@ -214,14 +218,14 @@ public abstract class AbstractSingleListVariantTest extends AbstractMqttTest {
     assertTrue(receiverRoot.connectAbstractWithAdd(mqttUri(TOPIC_T_Abstract)));
 
     // send
-    assertTrue(senderRoot.connectT_Empty(mqttUri(TOPIC_T_Empty), writeCurrentValue));
-    assertTrue(senderRoot.connectT_Token(mqttUri(TOPIC_T_Token), writeCurrentValue));
-    assertTrue(senderRoot.connectT_OneChild(mqttUri(TOPIC_T_OneChild), writeCurrentValue));
-    assertTrue(senderRoot.connectT_OneOpt(mqttUri(TOPIC_T_OneOpt), writeCurrentValue));
-    assertTrue(senderRoot.connectT_OneList(mqttUri(TOPIC_T_OneList), writeCurrentValue));
-    assertTrue(senderRoot.connectT_TwoChildren(mqttUri(TOPIC_T_TwoChildren), writeCurrentValue));
-    assertTrue(senderRoot.connectT_OneOfEach(mqttUri(TOPIC_T_OneOfEach), writeCurrentValue));
-    assertTrue(senderRoot.connectT_Abstract(mqttUri(TOPIC_T_Abstract), writeCurrentValue));
+    assertTrue(senderRoot.connectT_Empty(mqttUri(TOPIC_T_Empty), isWriteCurrentValue()));
+    assertTrue(senderRoot.connectT_Token(mqttUri(TOPIC_T_Token), isWriteCurrentValue()));
+    assertTrue(senderRoot.connectT_OneChild(mqttUri(TOPIC_T_OneChild), isWriteCurrentValue()));
+    assertTrue(senderRoot.connectT_OneOpt(mqttUri(TOPIC_T_OneOpt), isWriteCurrentValue()));
+    assertTrue(senderRoot.connectT_OneList(mqttUri(TOPIC_T_OneList), isWriteCurrentValue()));
+    assertTrue(senderRoot.connectT_TwoChildren(mqttUri(TOPIC_T_TwoChildren), isWriteCurrentValue()));
+    assertTrue(senderRoot.connectT_OneOfEach(mqttUri(TOPIC_T_OneOfEach), isWriteCurrentValue()));
+    assertTrue(senderRoot.connectT_Abstract(mqttUri(TOPIC_T_Abstract), isWriteCurrentValue()));
   }
 
   abstract protected void setupReceiverAndConnectPart() throws IOException;
@@ -229,81 +233,92 @@ public abstract class AbstractSingleListVariantTest extends AbstractMqttTest {
   @Override
   protected void communicateSendInitialValue() throws IOException, InterruptedException {
     // transmissions: 8 * 1 = 8
-    checkTree(8, list(-0), list(0), list(-0));
+    checker.addToNumberOfValues(8);
+    checkTree(list(-0), list(0), list(-0));
 
     setInput(1);
     // transmissions: 8 + 8 = 16
-    checkTree(16, list(-1), list(0, 1), list(-0, -1));
+    checker.addToNumberOfValues(8);
+    checkTree(list(-1), list(0, 1), list(-0, -1));
 
     setInput(1);
     // transmissions: 16
-    checkTree(16, list(-1), list(0, 1), list(-0, -1));
+    checkTree(list(-1), list(0, 1), list(-0, -1));
 
     setShouldSetOptAndList(true);
     // transmissions: 16 + 3 = 19
-    checkTree(19, list(1), list(0, 1), list(-0, -1, 1));
+    checker.addToNumberOfValues(3);
+    checkTree(list(1), list(0, 1), list(-0, -1, 1));
 
     setShouldSetOptAndList(true);
     // transmissions: 19
-    checkTree(19, list(1), list(0, 1), list(-0, -1, 1));
+    checkTree(list(1), list(0, 1), list(-0, -1, 1));
 
     setInput(2);
     // transmissions: 19 + 8 = 27
-    checkTree(27, list(2), list(0, 1, 2), list(-0, -1, 1, 2));
+    checker.addToNumberOfValues(8);
+    checkTree(list(2), list(0, 1, 2), list(-0, -1, 1, 2));
 
     setInput(5);
     // transmissions: 27 + 8 = 35
-    checkTree(35, list(5), list(0, 1, 2, 5), list(-0, -1, 1, 2, 5));
+    checker.addToNumberOfValues(8);
+    checkTree(list(5), list(0, 1, 2, 5), list(-0, -1, 1, 2, 5));
 
     disconnectReceive();
     setInput(6); // does not affect receive
     // transmissions: 35 + 8 = 43
-    checkTree(43, list(5), list(0, 1, 2, 5), list(-0, -1, 1, 2, 5));
+    checker.addToNumberOfValues(8);
+    checkTree(list(5), list(0, 1, 2, 5), list(-0, -1, 1, 2, 5));
 
     disconnectSend();
     setInput(7); // not sent
     // transmissions: 43
-    checkTree(43, list(5), list(0, 1, 2, 5), list(-0, -1, 1, 2, 5));
+    checkTree(list(5), list(0, 1, 2, 5), list(-0, -1, 1, 2, 5));
   }
 
   @Override
   protected void communicateOnlyUpdatedValue() throws IOException, InterruptedException {
     // transmissions: 0
-    checkTree(0, list(), list(), list());
+    checkTree(list(), list(), list());
 
     setInput(1);
     // transmissions: 8 * 1 = 0
-    checkTree(8, list(-1), list(1), list(-1));
+    checker.addToNumberOfValues(8);
+    checkTree(list(-1), list(1), list(-1));
 
     setInput(1);
     // transmissions: 8
-    checkTree(8, list(-1), list(1), list(-1));
+    checkTree(list(-1), list(1), list(-1));
 
     setShouldSetOptAndList(true);
     // transmissions: 8 + 3 = 11
-    checkTree(11, list(1), list(1), list(-1, 1));
+    checker.addToNumberOfValues(3);
+    checkTree(list(1), list(1), list(-1, 1));
 
     setShouldSetOptAndList(true);
     // transmissions: 11
-    checkTree(11, list(1), list(1), list(-1, 1));
+    checkTree(list(1), list(1), list(-1, 1));
 
     setInput(2);
     // transmissions: 11 + 8 = 19
-    checkTree(19, list(2), list(1, 2), list(-1, 1, 2));
+    checker.addToNumberOfValues(8);
+    checkTree(list(2), list(1, 2), list(-1, 1, 2));
 
     setInput(5);
     // transmissions: 19 + 8 = 27
-    checkTree(27, list(5), list(1, 2, 5), list(-1, 1, 2, 5));
+    checker.addToNumberOfValues(8);
+    checkTree(list(5), list(1, 2, 5), list(-1, 1, 2, 5));
 
     disconnectReceive();
     setInput(6); // does not affect receive
     // transmissions: 27 + 8 = 35
-    checkTree(35, list(5), list(1, 2, 5), list(-1, 1, 2, 5));
+    checker.addToNumberOfValues(8);
+    checkTree(list(5), list(1, 2, 5), list(-1, 1, 2, 5));
 
     disconnectSend();
     setInput(7); // not sent
     // transmissions: 35
-    checkTree(35, list(5), list(1, 2, 5), list(-1, 1, 2, 5));
+    checkTree(list(5), list(1, 2, 5), list(-1, 1, 2, 5));
   }
 
   private void disconnectReceive() throws IOException {
@@ -357,20 +372,15 @@ public abstract class AbstractSingleListVariantTest extends AbstractMqttTest {
    * Check against expected lists of IDs.
    * If an ID is negative, do not check Opts and Lists, but use absolute value for comparison.
    * The tests starts with ID 0 and does not use opts and lists at this point, so checking with > 0 is used.
-   * @param expectedTransmissions            expected number of total transmissions
-   * @param expectedList                     ids for unnamed and named endpoints without add
-   * @param expectedWithAddList              ids for endpoints with add, but not those with opts and lists
+   * @param expectedList                     ids for unnamed and named ports without add
+   * @param expectedWithAddList              ids for ports with add, but not those with opts and lists
    *                                         (only positive numbers can/need to be passed)
-   * @param expectedWithAddListForOptAndList ids for endpoints with add and with opts and lists
-   * @throws InterruptedException if interrupted in TestUtils.waitForMqtt
+   * @param expectedWithAddListForOptAndList ids for ports with add and with opts and lists
    */
-  private void checkTree(int expectedTransmissions,
-                         IntList expectedList,
+  private void checkTree(IntList expectedList,
                          IntList expectedWithAddList,
-                         IntList expectedWithAddListForOptAndList)
-      throws InterruptedException {
-    TestUtils.waitForMqtt();
-    assertEquals(expectedTransmissions, data.numberOfElements, "transmissions for any element");
+                         IntList expectedWithAddListForOptAndList) {
+    checker.check();
 
     // check unnamed
     checkList(expectedList, receiverRoot.getT_EmptyList(), (e, n) -> {});
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleListVariant/SingleListVariantIncrementalVariantTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleListVariant/SingleListVariantIncrementalTest.java
similarity index 80%
rename from ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleListVariant/SingleListVariantIncrementalVariantTest.java
rename to ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleListVariant/SingleListVariantIncrementalTest.java
index 174bea2923e2977c93a058dc1408113276e47382..1d74649ccb0c714f7a3ee39beeb1d512e560dcf5 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleListVariant/SingleListVariantIncrementalVariantTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleListVariant/SingleListVariantIncrementalTest.java
@@ -1,8 +1,11 @@
 package org.jastadd.ragconnect.tests.singleListVariant;
 
-import org.jastadd.ragconnect.tests.TestUtils;
+import org.jastadd.ragconnect.tests.utils.TestUtils;
 import org.junit.jupiter.api.Tag;
-import singleListVariantInc.ast.*;
+import singleListVariantInc.ast.MqttHandler;
+import singleListVariantInc.ast.ReceiverRoot;
+import singleListVariantInc.ast.Root;
+import singleListVariantInc.ast.SenderRoot;
 
 import java.io.IOException;
 import java.util.concurrent.TimeUnit;
@@ -16,12 +19,12 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
  * @author rschoene - Initial contribution
  */
 @Tag("Incremental")
-public class SingleListVariantIncrementalVariantTest extends AbstractSingleListVariantTest {
+public class SingleListVariantIncrementalTest extends AbstractSingleListVariantTest {
 
   private Root model;
   private MqttHandler handler;
 
-  SingleListVariantIncrementalVariantTest() {
+  SingleListVariantIncrementalTest() {
     super("singleListVariantInc");
   }
 
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleListVariant/SingleListVariantManualVariantTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleListVariant/SingleListVariantManualTest.java
similarity index 93%
rename from ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleListVariant/SingleListVariantManualVariantTest.java
rename to ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleListVariant/SingleListVariantManualTest.java
index 6ae0e75b5d2d35375baf508ce7af8044158370bc..907826d455e334b5b781b2fbeb1b4dea0d6ff4af 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleListVariant/SingleListVariantManualVariantTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleListVariant/SingleListVariantManualTest.java
@@ -1,6 +1,6 @@
 package org.jastadd.ragconnect.tests.singleListVariant;
 
-import org.jastadd.ragconnect.tests.TestUtils;
+import org.jastadd.ragconnect.tests.utils.TestUtils;
 import singleListVariant.ast.*;
 
 import java.io.IOException;
@@ -14,12 +14,12 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
  *
  * @author rschoene - Initial contribution
  */
-public class SingleListVariantManualVariantTest extends AbstractSingleListVariantTest {
+public class SingleListVariantManualTest extends AbstractSingleListVariantTest {
 
   private Root model;
   private MqttHandler handler;
 
-  SingleListVariantManualVariantTest() {
+  SingleListVariantManualTest() {
     super("singleListVariant");
   }
 
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/tokenValue/AbstractTokenValueSendTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/tokenValue/AbstractTokenValueSendTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..5d55b729f26fb4d95fd7898b30d8ff1647deafdf
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/tokenValue/AbstractTokenValueSendTest.java
@@ -0,0 +1,224 @@
+package org.jastadd.ragconnect.tests.tokenValue;
+
+import org.jastadd.ragconnect.tests.AbstractMqttTest;
+import org.jastadd.ragconnect.tests.utils.DefaultMappings;
+import org.jastadd.ragconnect.tests.utils.TestUtils;
+import org.jastadd.ragconnect.tests.utils.TestChecker;
+import org.junit.jupiter.api.Test;
+import tokenValueSend.ast.MqttHandler;
+
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.concurrent.TimeUnit;
+
+import static org.jastadd.ragconnect.tests.utils.TestUtils.mqttUri;
+import static org.jastadd.ragconnect.tests.utils.TestUtils.testJaddContainReferenceToJackson;
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Test case "tokenValueSend".
+ *
+ * @author rschoene - Initial contribution
+ */
+public abstract class AbstractTokenValueSendTest extends AbstractMqttTest {
+  protected static final String TOPIC_SEND_ONE = "one/value/out";
+
+  protected static final String TOPIC_RECEIVE_TWO = "two/value/in";
+  protected static final String TOPIC_SEND_TWO = "two/value/out";
+
+  protected static final String TOPIC_RECEIVE_THREE_VALUE = "three/value/in";
+  protected static final String TOPIC_SEND_THREE_VALUE = "three/value/out";
+  protected static final String TOPIC_SEND_THREE_OTHER = "three/other/out";
+
+  protected static final String INITIAL_VALUE = "Start";
+
+  protected MqttHandler handler;
+  protected TestChecker checker;
+
+  protected final String shortName;
+
+  private ReceiverData dataOne;
+  private ReceiverData dataTwo;
+  private ReceiverData dataThree;
+  private ReceiverData dataThreeOther;
+
+  AbstractTokenValueSendTest(String shortName) {
+    this.shortName = shortName;
+    this.checker = new TestChecker();
+    this.checker.setActualNumberOfValues(() -> dataOne.numberOfStringValues + dataTwo.numberOfStringValues +
+            dataThree.numberOfStringValues + dataThreeOther.numberOfStringValues);
+  }
+
+  @Test
+  public void checkJacksonReference() {
+    testJaddContainReferenceToJackson(
+            Paths.get("src", "test",
+                    "02-after-ragconnect", shortName, "RagConnect.jadd"), false);
+  }
+
+  @Override
+  protected void setupReceiverAndConnect() throws IOException {
+    handler = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost());
+    assertTrue(handler.waitUntilReady(2, TimeUnit.SECONDS));
+
+    dataOne = new ReceiverData();
+    dataTwo = new ReceiverData();
+    dataThree = new ReceiverData();
+    dataThreeOther = new ReceiverData();
+
+    handler.newConnection(TOPIC_SEND_ONE, bytes -> {
+      dataOne.numberOfStringValues += 1;
+      dataOne.lastStringValue = DefaultMappings.BytesToString(bytes);
+    });
+    handler.newConnection(TOPIC_SEND_TWO, bytes -> {
+      dataTwo.numberOfStringValues += 1;
+      dataTwo.lastStringValue = DefaultMappings.BytesToString(bytes);
+    });
+
+    handler.newConnection(TOPIC_SEND_THREE_VALUE, bytes -> {
+      dataThree.numberOfStringValues += 1;
+      dataThree.lastStringValue = DefaultMappings.BytesToString(bytes);
+    });
+    handler.newConnection(TOPIC_SEND_THREE_OTHER, bytes -> {
+      dataThreeOther.numberOfStringValues += 1;
+      dataThreeOther.lastStringValue = DefaultMappings.BytesToString(bytes);
+    });
+
+    connectModel();
+  }
+
+  protected abstract void connectModel() throws IOException;
+
+  @Override
+  protected void communicateSendInitialValue() throws InterruptedException, IOException {
+    // check initial value
+    checker.addToNumberOfValues(4);
+    checkData(1, "Start-Post",
+        1, "Start-Post",
+        1, "Start-Post",
+        1, "Start-T-Post");
+
+    communicateBoth(1, "Start-Post");
+  }
+
+  @Override
+  protected void communicateOnlyUpdatedValue() throws InterruptedException, IOException {
+    // check initial value
+    checkData(0, null,
+            0, null,
+            0, null,
+            0, null);
+
+    communicateBoth(0, null);
+  }
+
+  private void communicateBoth(int initialNumberOfValues, String initialLastOneStringValue) throws IOException {
+    // send new value
+    sendData("210", "310");
+    checker.addToNumberOfValues(3);
+    checkData(initialNumberOfValues, initialLastOneStringValue,
+        initialNumberOfValues + 1, "Pre-210-Post",
+        initialNumberOfValues + 1, "Pre-310-Post",
+        initialNumberOfValues + 1, "Pre-310-T-Post");
+
+    // set new value
+    setData("111", "211", "311");
+    checker.addToNumberOfValues(4);
+    checkData(initialNumberOfValues + 1, "111-Post",
+        initialNumberOfValues + 2, "211-Post",
+        initialNumberOfValues + 2, "311-Post",
+        initialNumberOfValues + 2, "311-T-Post");
+
+    // send the same values (will not be sent again)
+    setData("111", "211", "311");
+    checkData(initialNumberOfValues + 1, "111-Post",
+        initialNumberOfValues + 2, "211-Post",
+        initialNumberOfValues + 2, "311-Post",
+        initialNumberOfValues + 2, "311-T-Post");
+
+    // send values with prefixes imitating receiving
+    checker.addToNumberOfValues(4);
+    setData("112", "Pre-212", "Pre-312");
+    checkData(initialNumberOfValues + 2, "112-Post",
+        initialNumberOfValues + 3, "Pre-212-Post",
+        initialNumberOfValues + 3, "Pre-312-Post",
+        initialNumberOfValues + 3, "Pre-312-T-Post");
+
+    // send the same values (will not be sent again, because previously prefixed)
+    sendData("212", "312");
+    checkData(initialNumberOfValues + 2, "112-Post",
+        initialNumberOfValues + 3, "Pre-212-Post",
+        initialNumberOfValues + 3, "Pre-312-Post",
+        initialNumberOfValues + 3, "Pre-312-T-Post");
+
+    // new values for two and three, but two will not send updated value
+    assertTrue(disconnect("two", "SendValue", mqttUri(TOPIC_SEND_TWO)));
+    sendData("213", "313");
+    checker.addToNumberOfValues(2);
+    checkData(initialNumberOfValues + 2, "112-Post",
+        initialNumberOfValues + 3, "Pre-212-Post",
+        initialNumberOfValues + 4, "Pre-313-Post",
+        initialNumberOfValues + 4, "Pre-313-T-Post");
+    assertEquals("Pre-213", valueOfReceiveAndSend());
+
+    // can not disconnect again, and also not for different topic
+    assertFalse(disconnect("two", "SendValue", mqttUri(TOPIC_SEND_TWO)));
+    assertFalse(disconnect("two", "SendValue", mqttUri(TOPIC_RECEIVE_TWO)));
+
+    // new values for two and three, but two will neither receive nor send updated value
+    assertTrue(disconnect("two", "ReceiveValue", mqttUri(TOPIC_RECEIVE_TWO)));
+    sendData("214", "314");
+    checker.addToNumberOfValues(2);
+    checkData(initialNumberOfValues + 2, "112-Post",
+        initialNumberOfValues + 3, "Pre-212-Post",
+        initialNumberOfValues + 5, "Pre-314-Post",
+        initialNumberOfValues + 5, "Pre-314-T-Post");
+    assertEquals("Pre-213", valueOfReceiveAndSend());
+
+    // new values for three, but it will not receive updated value, and, thus, not send it either
+    assertTrue(disconnect("three", "ReceiveValue", mqttUri(TOPIC_RECEIVE_THREE_VALUE)));
+    sendData("214", "315");
+    checkData(initialNumberOfValues + 2, "112-Post",
+        initialNumberOfValues + 3, "Pre-212-Post",
+        initialNumberOfValues + 5, "Pre-314-Post",
+        initialNumberOfValues + 5, "Pre-314-T-Post");
+    assertEquals("Pre-213", valueOfReceiveAndSend());
+
+    // disconnect send is possible
+    assertTrue(disconnect("three", "SendValue", mqttUri(TOPIC_SEND_THREE_VALUE)));
+  }
+
+  protected abstract boolean disconnect(String objectIdentifier, String targetIdentifier, String topic) throws IOException;
+
+  protected abstract String valueOfReceiveAndSend();
+
+  private void sendData(String inputTwo, String inputThree) {
+    publisher.publish(TOPIC_RECEIVE_TWO, inputTwo.getBytes());
+    publisher.publish(TOPIC_RECEIVE_THREE_VALUE, inputThree.getBytes());
+  }
+
+  protected abstract void setData(String inputOne, String inputTwo, String inputThree);
+
+  private void checkData(int numberOfOneValues, String lastOneStringValue,
+                         int numberOfTwoValues, String lastTwoStringValue,
+                         int numberOfThreeValues, String lastThreeStringValue,
+                         int numberOfOtherValues, String lastOtherStringValue
+  ) {
+    checker.check();
+    dataOne.assertEqualData(numberOfOneValues, lastOneStringValue);
+    dataTwo.assertEqualData(numberOfTwoValues, lastTwoStringValue);
+    dataThree.assertEqualData(numberOfThreeValues, lastThreeStringValue);
+    dataThreeOther.assertEqualData(numberOfOtherValues, lastOtherStringValue);
+  }
+
+  private static class ReceiverData {
+    String lastStringValue;
+    int numberOfStringValues = 0;
+
+    public void assertEqualData(int expectedNumberOfValues, String expectedLastValue) {
+      assertEquals(expectedNumberOfValues, this.numberOfStringValues);
+      assertEquals(expectedLastValue, this.lastStringValue);
+    }
+  }
+
+}
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/tokenValue/TokenValueSendIncrementalTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/tokenValue/TokenValueSendIncrementalTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..bdb1ab9bf735db280d6022f445140495d3ff955f
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/tokenValue/TokenValueSendIncrementalTest.java
@@ -0,0 +1,93 @@
+package org.jastadd.ragconnect.tests.tokenValue;
+
+import org.junit.jupiter.api.Tag;
+import tokenValueSendInc.ast.*;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+import static org.jastadd.ragconnect.tests.utils.TestUtils.mqttUri;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Test case "tokenValueSend incremental".
+ *
+ * @author rschoene - Initial contribution
+ */
+@Tag("Incremental")
+public class TokenValueSendIncrementalTest extends AbstractTokenValueSendTest {
+
+  private A model;
+
+  TokenValueSendIncrementalTest() {
+    super("tokenValueSend");
+  }
+
+  @Override
+  protected void createModel() {
+    // Setting value for Input without dependencies does not trigger any updates
+    model = new A();
+
+    model.setOnlySend(new OnlySend().setValue(INITIAL_VALUE));
+    model.setReceiveAndSend(new ReceiveAndSend().setValue(INITIAL_VALUE));
+    model.setReceiveSendAndDepend(new ReceiveSendAndDepend().setValue(INITIAL_VALUE));
+  }
+
+
+  @Override
+  public void closeConnections() {
+    if (handler != null) {
+      handler.close();
+    }
+    if (model != null) {
+      model.ragconnectCloseConnections();
+    }
+  }
+
+  @Override
+  protected void connectModel() throws IOException {
+    model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS);
+
+    // no dependencies
+
+    assertTrue(model.getOnlySend().connectValue(mqttUri(TOPIC_SEND_ONE), isWriteCurrentValue()));
+    assertTrue(model.getReceiveAndSend().connectValue(mqttUri(TOPIC_RECEIVE_TWO)));
+    assertTrue(model.getReceiveAndSend().connectValue(mqttUri(TOPIC_SEND_TWO), isWriteCurrentValue()));
+    assertTrue(model.getReceiveSendAndDepend().connectValue(mqttUri(TOPIC_RECEIVE_THREE_VALUE)));
+    assertTrue(model.getReceiveSendAndDepend().connectValue(mqttUri(TOPIC_SEND_THREE_VALUE), isWriteCurrentValue()));
+    assertTrue(model.getReceiveSendAndDepend().connectOtherOutput(mqttUri(TOPIC_SEND_THREE_OTHER), isWriteCurrentValue()));
+  }
+
+  @Override
+  protected boolean disconnect(String objectIdentifier, String targetIdentifier, String topic) throws IOException {
+    switch (objectIdentifier) {
+      case "two":
+        ReceiveAndSend receiveAndSend = model.getReceiveAndSend();
+        switch (targetIdentifier) {
+          case "SendValue": return receiveAndSend.disconnectSendValue(topic);
+          case "ReceiveValue": return receiveAndSend.disconnectReceiveValue(topic);
+        }
+        break;
+      case "three":
+        ReceiveSendAndDepend receiveSendAndDepend = model.getReceiveSendAndDepend();
+        switch (targetIdentifier) {
+          case "SendValue": return receiveSendAndDepend.disconnectSendValue(topic);
+          case "ReceiveValue": return receiveSendAndDepend.disconnectReceiveValue(topic);
+        }
+        break;
+    }
+    throw new IllegalArgumentException(objectIdentifier + " on " + targetIdentifier + " for " + topic);
+  }
+
+  @Override
+  protected String valueOfReceiveAndSend() {
+    return model.getReceiveAndSend().getValue();
+  }
+
+  @Override
+  protected void setData(String inputOne, String inputTwo, String inputThree) {
+    model.getOnlySend().setValue(inputOne);
+    model.getReceiveAndSend().setValue(inputTwo);
+    model.getReceiveSendAndDepend().setValue(inputThree);
+  }
+}
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/tokenValue/TokenValueSendManualTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/tokenValue/TokenValueSendManualTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..ac13bfacb45cfdb72ac9472dcba6e176dc553680
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/tokenValue/TokenValueSendManualTest.java
@@ -0,0 +1,91 @@
+package org.jastadd.ragconnect.tests.tokenValue;
+
+import tokenValueSend.ast.*;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+import static org.jastadd.ragconnect.tests.utils.TestUtils.mqttUri;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Test case "tokenValueSend manual".
+ *
+ * @author rschoene - Initial contribution
+ */
+public class TokenValueSendManualTest extends AbstractTokenValueSendTest {
+
+  private A model;
+
+  TokenValueSendManualTest() {
+    super("tokenValueSend");
+  }
+
+  @Override
+  protected void createModel() {
+    // Setting value for Input without dependencies does not trigger any updates
+    model = new A();
+
+    model.setOnlySend(new OnlySend().setValue(INITIAL_VALUE));
+    model.setReceiveAndSend(new ReceiveAndSend().setValue(INITIAL_VALUE));
+    model.setReceiveSendAndDepend(new ReceiveSendAndDepend().setValue(INITIAL_VALUE));
+  }
+
+
+  @Override
+  public void closeConnections() {
+    if (handler != null) {
+      handler.close();
+    }
+    if (model != null) {
+      model.ragconnectCloseConnections();
+    }
+  }
+
+  @Override
+  protected void connectModel() throws IOException {
+    model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS);
+
+    model.getReceiveSendAndDepend().addDependency1(model.getReceiveSendAndDepend());
+
+    assertTrue(model.getOnlySend().connectValue(mqttUri(TOPIC_SEND_ONE), isWriteCurrentValue()));
+    assertTrue(model.getReceiveAndSend().connectValue(mqttUri(TOPIC_RECEIVE_TWO)));
+    assertTrue(model.getReceiveAndSend().connectValue(mqttUri(TOPIC_SEND_TWO), isWriteCurrentValue()));
+    assertTrue(model.getReceiveSendAndDepend().connectValue(mqttUri(TOPIC_RECEIVE_THREE_VALUE)));
+    assertTrue(model.getReceiveSendAndDepend().connectValue(mqttUri(TOPIC_SEND_THREE_VALUE), isWriteCurrentValue()));
+    assertTrue(model.getReceiveSendAndDepend().connectOtherOutput(mqttUri(TOPIC_SEND_THREE_OTHER), isWriteCurrentValue()));
+  }
+
+  @Override
+  protected boolean disconnect(String objectIdentifier, String targetIdentifier, String topic) throws IOException {
+    switch (objectIdentifier) {
+      case "two":
+        ReceiveAndSend receiveAndSend = model.getReceiveAndSend();
+        switch (targetIdentifier) {
+          case "SendValue": return receiveAndSend.disconnectSendValue(topic);
+          case "ReceiveValue": return receiveAndSend.disconnectReceiveValue(topic);
+        }
+        break;
+      case "three":
+        ReceiveSendAndDepend receiveSendAndDepend = model.getReceiveSendAndDepend();
+        switch (targetIdentifier) {
+          case "SendValue": return receiveSendAndDepend.disconnectSendValue(topic);
+          case "ReceiveValue": return receiveSendAndDepend.disconnectReceiveValue(topic);
+        }
+        break;
+    }
+    throw new IllegalArgumentException(objectIdentifier + " on " + targetIdentifier + " for " + topic);
+  }
+
+  @Override
+  protected String valueOfReceiveAndSend() {
+    return model.getReceiveAndSend().getValue();
+  }
+
+  @Override
+  protected void setData(String inputOne, String inputTwo, String inputThree) {
+    model.getOnlySend().setValue(inputOne);
+    model.getReceiveAndSend().setValue(inputTwo);
+    model.getReceiveSendAndDepend().setValue(inputThree);
+  }
+}
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/tree/AbstractTreeTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/tree/AbstractTreeTest.java
index 22cc2c0c4334d86c96fdec48682b7a92842fbc9f..9b2e18f5a5fd075843b4022386408c944506f0be 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/tree/AbstractTreeTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/tree/AbstractTreeTest.java
@@ -1,7 +1,7 @@
 package org.jastadd.ragconnect.tests.tree;
 
 import org.jastadd.ragconnect.tests.AbstractMqttTest;
-import org.jastadd.ragconnect.tests.TestUtils;
+import org.jastadd.ragconnect.tests.utils.TestUtils;
 import org.junit.jupiter.api.Tag;
 
 import java.io.IOException;
@@ -16,6 +16,7 @@ import static org.junit.jupiter.api.Assertions.*;
  * @author rschoene - Initial contribution
  */
 @Tag("Tree")
+@Tag("New")
 public abstract class AbstractTreeTest extends AbstractMqttTest {
   protected static final String TOPIC_ALFA = "alfa";
   protected ReceiverData data;
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/tree/TreeIncrementalTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/tree/TreeIncrementalTest.java
index 96cbadc4fbfabed7685c6a5d4c0f0ff827c4f9b1..419e5a4aae21dbcfc3018a289bc91e0b2753ba17 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/tree/TreeIncrementalTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/tree/TreeIncrementalTest.java
@@ -1,6 +1,6 @@
 package org.jastadd.ragconnect.tests.tree;
 
-import org.jastadd.ragconnect.tests.TestUtils;
+import org.jastadd.ragconnect.tests.utils.TestUtils;
 import org.junit.jupiter.api.Tag;
 import org.junit.jupiter.api.Test;
 import treeInc.ast.*;
@@ -10,8 +10,8 @@ import java.nio.file.Paths;
 import java.util.concurrent.TimeUnit;
 
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.jastadd.ragconnect.tests.TestUtils.mqttUri;
-import static org.jastadd.ragconnect.tests.TestUtils.testJaddContainReferenceToJackson;
+import static org.jastadd.ragconnect.tests.utils.TestUtils.mqttUri;
+import static org.jastadd.ragconnect.tests.utils.TestUtils.testJaddContainReferenceToJackson;
 import static org.junit.jupiter.api.Assertions.*;
 
 /**
@@ -44,7 +44,7 @@ public class TreeIncrementalTest extends AbstractTreeTest {
   }
 
   @Override
-  protected void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException {
+  protected void setupReceiverAndConnect() throws IOException {
     model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS);
 
     handler = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost());
@@ -57,7 +57,7 @@ public class TreeIncrementalTest extends AbstractTreeTest {
 
     // connect. important: first receiver, then sender. to not miss initial value.
     assertTrue(receiverRoot.connectAlfa(mqttUri(TOPIC_ALFA)));
-    assertTrue(senderRoot.connectAlfa(mqttUri(TOPIC_ALFA), writeCurrentValue));
+    assertTrue(senderRoot.connectAlfa(mqttUri(TOPIC_ALFA), isWriteCurrentValue()));
   }
 
   protected void setInput(int input) {
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/tree/TreeManualTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/tree/TreeManualTest.java
index e557c9442be50d04d4b9d1bb2de7d003f749d4f0..c0845d514f1fe5e62f1c8af85e9feadff2f903c4 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/tree/TreeManualTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/tree/TreeManualTest.java
@@ -1,7 +1,6 @@
 package org.jastadd.ragconnect.tests.tree;
 
-import org.jastadd.ragconnect.tests.TestUtils;
-import org.junit.jupiter.api.Tag;
+import org.jastadd.ragconnect.tests.utils.TestUtils;
 import org.junit.jupiter.api.Test;
 import tree.ast.MqttHandler;
 import tree.ast.ReceiverRoot;
@@ -12,8 +11,8 @@ import java.io.IOException;
 import java.nio.file.Paths;
 import java.util.concurrent.TimeUnit;
 
-import static org.jastadd.ragconnect.tests.TestUtils.mqttUri;
-import static org.jastadd.ragconnect.tests.TestUtils.testJaddContainReferenceToJackson;
+import static org.jastadd.ragconnect.tests.utils.TestUtils.mqttUri;
+import static org.jastadd.ragconnect.tests.utils.TestUtils.testJaddContainReferenceToJackson;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 /**
@@ -45,7 +44,7 @@ public class TreeManualTest extends AbstractTreeTest {
   }
 
   @Override
-  protected void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException {
+  protected void setupReceiverAndConnect() throws IOException {
     model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS);
 
     handler = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost());
@@ -59,7 +58,7 @@ public class TreeManualTest extends AbstractTreeTest {
 
     // connect. important: first receiver, then sender. to not miss initial value.
     assertTrue(receiverRoot.connectAlfa(mqttUri(TOPIC_ALFA)));
-    assertTrue(senderRoot.connectAlfa(mqttUri(TOPIC_ALFA), writeCurrentValue));
+    assertTrue(senderRoot.connectAlfa(mqttUri(TOPIC_ALFA), isWriteCurrentValue()));
   }
 
   protected void setInput(int input) {
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/treeAllowedTokens/AbstractTreeAllowedTokensTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/treeAllowedTokens/AbstractTreeAllowedTokensTest.java
index 7c29538a71609e73a82fe0e22ad6c8bd74582f8c..533d5d1ebb1fe1f82547a7b3120ddcb636432e63 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/treeAllowedTokens/AbstractTreeAllowedTokensTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/treeAllowedTokens/AbstractTreeAllowedTokensTest.java
@@ -1,7 +1,8 @@
 package org.jastadd.ragconnect.tests.treeAllowedTokens;
 
 import org.jastadd.ragconnect.tests.AbstractMqttTest;
-import org.jastadd.ragconnect.tests.TestUtils;
+import org.jastadd.ragconnect.tests.utils.DefaultMappings;
+import org.jastadd.ragconnect.tests.utils.TestUtils;
 import org.junit.jupiter.api.Tag;
 
 import java.io.IOException;
@@ -183,19 +184,19 @@ public abstract class AbstractTreeAllowedTokensTest extends AbstractMqttTest {
   protected abstract void disconnectSend() throws IOException;
 
   protected void sendInput1WhenFalse(int value) {
-    publisher.publish(TOPIC_INPUT1FALSE, TestUtils.DefaultMappings.IntToBytes(value));
+    publisher.publish(TOPIC_INPUT1FALSE, DefaultMappings.IntToBytes(value));
   }
 
   protected void sendInput1WhenTrue(int value) {
-    publisher.publish(TOPIC_INPUT1TRUE, TestUtils.DefaultMappings.IntToBytes(value));
+    publisher.publish(TOPIC_INPUT1TRUE, DefaultMappings.IntToBytes(value));
   }
 
   protected void sendInput2(String value) {
-    publisher.publish(TOPIC_INPUT2, TestUtils.DefaultMappings.StringToBytes(value));
+    publisher.publish(TOPIC_INPUT2, DefaultMappings.StringToBytes(value));
   }
 
   protected void sendInput3(double value) {
-    publisher.publish(TOPIC_INPUT3, TestUtils.DefaultMappings.DoubleToBytes(value));
+    publisher.publish(TOPIC_INPUT3, DefaultMappings.DoubleToBytes(value));
   }
 
   protected abstract void setFlag(boolean value);
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/treeAllowedTokens/TreeAllowedTokensIncrementalTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/treeAllowedTokens/TreeAllowedTokensIncrementalTest.java
index 851565047c4c8a95284d9b8d58e158863596467f..7d6ab574dafd79d1948412408f45ffaed54db449 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/treeAllowedTokens/TreeAllowedTokensIncrementalTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/treeAllowedTokens/TreeAllowedTokensIncrementalTest.java
@@ -1,6 +1,6 @@
 package org.jastadd.ragconnect.tests.treeAllowedTokens;
 
-import org.jastadd.ragconnect.tests.TestUtils;
+import org.jastadd.ragconnect.tests.utils.TestUtils;
 import org.junit.jupiter.api.Tag;
 import org.junit.jupiter.api.Test;
 import treeAllowedTokensInc.ast.*;
@@ -9,8 +9,8 @@ import java.io.IOException;
 import java.nio.file.Paths;
 import java.util.concurrent.TimeUnit;
 
-import static org.jastadd.ragconnect.tests.TestUtils.mqttUri;
-import static org.jastadd.ragconnect.tests.TestUtils.testJaddContainReferenceToJackson;
+import static org.jastadd.ragconnect.tests.utils.TestUtils.mqttUri;
+import static org.jastadd.ragconnect.tests.utils.TestUtils.testJaddContainReferenceToJackson;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
@@ -48,7 +48,7 @@ public class TreeAllowedTokensIncrementalTest extends AbstractTreeAllowedTokensT
   }
 
   @Override
-  protected void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException {
+  protected void setupReceiverAndConnect() throws IOException {
     model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS);
 
     handler = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost());
@@ -67,8 +67,8 @@ public class TreeAllowedTokensIncrementalTest extends AbstractTreeAllowedTokensT
     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));
+    assertTrue(senderRoot.connectAlfa(mqttUri(TOPIC_ALFA), isWriteCurrentValue()));
+    assertTrue(senderRoot.connectAlfaPrimitive(mqttUri(TOPIC_ALFA_PRIMITIVE), isWriteCurrentValue()));
   }
 
   protected void setFlag(boolean value) {
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/treeAllowedTokens/TreeAllowedTokensManualTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/treeAllowedTokens/TreeAllowedTokensManualTest.java
index 8563e0b04808f939cf1070f08c5666f9de2fef00..7479396772bc83b8e4ba5c35151145af3c60c3f6 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/treeAllowedTokens/TreeAllowedTokensManualTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/treeAllowedTokens/TreeAllowedTokensManualTest.java
@@ -1,7 +1,6 @@
 package org.jastadd.ragconnect.tests.treeAllowedTokens;
 
-import org.jastadd.ragconnect.tests.TestUtils;
-import org.junit.jupiter.api.Tag;
+import org.jastadd.ragconnect.tests.utils.TestUtils;
 import org.junit.jupiter.api.Test;
 import treeAllowedTokens.ast.*;
 
@@ -9,8 +8,8 @@ import java.io.IOException;
 import java.nio.file.Paths;
 import java.util.concurrent.TimeUnit;
 
-import static org.jastadd.ragconnect.tests.TestUtils.mqttUri;
-import static org.jastadd.ragconnect.tests.TestUtils.testJaddContainReferenceToJackson;
+import static org.jastadd.ragconnect.tests.utils.TestUtils.mqttUri;
+import static org.jastadd.ragconnect.tests.utils.TestUtils.testJaddContainReferenceToJackson;
 import static org.junit.jupiter.api.Assertions.*;
 
 /**
@@ -44,7 +43,7 @@ public class TreeAllowedTokensManualTest extends AbstractTreeAllowedTokensTest {
   }
 
   @Override
-  protected void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException {
+  protected void setupReceiverAndConnect() throws IOException {
     model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS);
 
     handler = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost());
@@ -69,8 +68,8 @@ public class TreeAllowedTokensManualTest extends AbstractTreeAllowedTokensTest {
     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));
+    assertTrue(senderRoot.connectAlfa(mqttUri(TOPIC_ALFA), isWriteCurrentValue()));
+    assertTrue(senderRoot.connectAlfaPrimitive(mqttUri(TOPIC_ALFA_PRIMITIVE), isWriteCurrentValue()));
   }
 
   protected void setFlag(boolean value) {
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/utils/ChangeObserver.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/utils/ChangeObserver.java
new file mode 100644
index 0000000000000000000000000000000000000000..2b0dbef6668a98e5d8aa4e9a64d9687f6885ca2c
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/utils/ChangeObserver.java
@@ -0,0 +1,49 @@
+package org.jastadd.ragconnect.tests.utils;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.function.Supplier;
+
+import static org.junit.jupiter.api.Assertions.fail;
+
+/**
+ * TODO: Add description.
+ *
+ * @author rschoene - Initial contribution
+ */
+public class ChangeObserver {
+  Map<Supplier<String>, String> callableToPrevious = new HashMap<>();
+  private Callable<Boolean> hasChanged;
+
+  @SafeVarargs
+  public final void init(Supplier<String>... suppliers) {
+    callableToPrevious.clear();
+    Arrays.stream(suppliers).forEach(callable -> callableToPrevious.put(callable, callable.get()));
+    hasChanged = () -> callableToPrevious.entrySet().stream()
+            .anyMatch(entry -> !entry.getKey().get().equals(entry.getValue()));
+  }
+
+  public void start() {
+    updatePrevious();
+  }
+
+  private void updatePrevious() {
+    callableToPrevious.keySet().forEach(callable -> callableToPrevious.put(callable, callable.get()));
+  }
+
+  public void awaitChange() {
+    TestUtils.awaitMqtt().until(hasChanged);
+    updatePrevious();
+  }
+
+  public boolean hasChanged() {
+    try {
+      return hasChanged.call();
+    } catch (Exception e) {
+      fail(e);
+      return false;
+    }
+  }
+}
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/utils/DefaultMappings.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/utils/DefaultMappings.java
new file mode 100644
index 0000000000000000000000000000000000000000..81711cfda338ca76311a5ec7f5a69f19497ac61d
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/utils/DefaultMappings.java
@@ -0,0 +1,246 @@
+package org.jastadd.ragconnect.tests.utils;
+
+import com.fasterxml.jackson.core.JsonEncoding;
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonGenerator;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+/**
+ * TODO: Add description.
+ *
+ * @author rschoene - Initial contribution
+ */
+public class DefaultMappings {
+  @SuppressWarnings("rawtypes")
+  static class ReadNode extends defaultOnlyRead.ast.ASTNode {
+    public boolean DefaultBytesToBooleanMapping(byte[] input) throws Exception {
+      return _ragconnect__apply__DefaultBytesToBooleanMapping(input);
+    }
+
+    public int DefaultBytesToIntMapping(byte[] input) throws Exception {
+      return _ragconnect__apply__DefaultBytesToIntMapping(input);
+    }
+
+    public short DefaultBytesToShortMapping(byte[] input) throws Exception {
+      return _ragconnect__apply__DefaultBytesToShortMapping(input);
+    }
+
+    public long DefaultBytesToLongMapping(byte[] input) throws Exception {
+      return _ragconnect__apply__DefaultBytesToLongMapping(input);
+    }
+
+    public float DefaultBytesToFloatMapping(byte[] input) throws Exception {
+      return _ragconnect__apply__DefaultBytesToFloatMapping(input);
+    }
+
+    public double DefaultBytesToDoubleMapping(byte[] input) throws Exception {
+      return _ragconnect__apply__DefaultBytesToDoubleMapping(input);
+    }
+
+    public char DefaultBytesToCharMapping(byte[] input) throws Exception {
+      return _ragconnect__apply__DefaultBytesToCharMapping(input);
+    }
+
+    public String DefaultBytesToStringMapping(byte[] input) throws Exception {
+      return _ragconnect__apply__DefaultBytesToStringMapping(input);
+    }
+  }
+
+  @SuppressWarnings("rawtypes")
+  static class WriteNode extends defaultOnlyWrite.ast.ASTNode {
+    public byte[] DefaultBooleanToBytesMapping(boolean input) throws Exception {
+      return _ragconnect__apply__DefaultBooleanToBytesMapping(input);
+    }
+
+    public byte[] DefaultIntToBytesMapping(int input) throws Exception {
+      return _ragconnect__apply__DefaultIntToBytesMapping(input);
+    }
+
+    public byte[] DefaultShortToBytesMapping(short input) throws Exception {
+      return _ragconnect__apply__DefaultShortToBytesMapping(input);
+    }
+
+    public byte[] DefaultLongToBytesMapping(long input) throws Exception {
+      return _ragconnect__apply__DefaultLongToBytesMapping(input);
+    }
+
+    public byte[] DefaultFloatToBytesMapping(float input) throws Exception {
+      return _ragconnect__apply__DefaultFloatToBytesMapping(input);
+    }
+
+    public byte[] DefaultDoubleToBytesMapping(double input) throws Exception {
+      return _ragconnect__apply__DefaultDoubleToBytesMapping(input);
+    }
+
+    public byte[] DefaultCharToBytesMapping(char input) throws Exception {
+      return _ragconnect__apply__DefaultCharToBytesMapping(input);
+    }
+
+    public byte[] DefaultStringToBytesMapping(String input) throws Exception {
+      return _ragconnect__apply__DefaultStringToBytesMapping(input);
+    }
+  }
+
+  @FunctionalInterface
+  public interface SerializeFunction<E extends Throwable> {
+    void accept(JsonGenerator g, String fieldName) throws E;
+  }
+
+  static ReadNode readNode = new ReadNode();
+  static WriteNode writeNode = new WriteNode();
+
+  public static boolean BytesToBool(byte[] input) {
+    try {
+      return readNode.DefaultBytesToBooleanMapping(input);
+    } catch (Exception e) {
+      e.printStackTrace();
+      return false;
+    }
+  }
+
+  public static int BytesToInt(byte[] input) {
+    try {
+      return readNode.DefaultBytesToIntMapping(input);
+    } catch (Exception e) {
+      e.printStackTrace();
+      return 0;
+    }
+  }
+
+  public static short BytesToShort(byte[] input) {
+    try {
+      return readNode.DefaultBytesToShortMapping(input);
+    } catch (Exception e) {
+      e.printStackTrace();
+      return 0;
+    }
+  }
+
+  public static long BytesToLong(byte[] input) {
+    try {
+      return readNode.DefaultBytesToLongMapping(input);
+    } catch (Exception e) {
+      e.printStackTrace();
+      return 0;
+    }
+  }
+
+  public static float BytesToFloat(byte[] input) {
+    try {
+      return readNode.DefaultBytesToFloatMapping(input);
+    } catch (Exception e) {
+      e.printStackTrace();
+      return 0;
+    }
+  }
+
+  public static double BytesToDouble(byte[] input) {
+    try {
+      return readNode.DefaultBytesToDoubleMapping(input);
+    } catch (Exception e) {
+      e.printStackTrace();
+      return 0;
+    }
+  }
+
+  public static char BytesToChar(byte[] input) {
+    try {
+      return readNode.DefaultBytesToCharMapping(input);
+    } catch (Exception e) {
+      e.printStackTrace();
+      return 0;
+    }
+  }
+
+  public static String BytesToString(byte[] input) {
+    try {
+      return readNode.DefaultBytesToStringMapping(input);
+    } catch (Exception e) {
+      e.printStackTrace();
+      return null;
+    }
+  }
+
+  public static byte[] BoolToBytes(boolean input) {
+    try {
+      return writeNode.DefaultBooleanToBytesMapping(input);
+    } catch (Exception e) {
+      e.printStackTrace();
+      return null;
+    }
+  }
+
+  public static byte[] IntToBytes(int input) {
+    try {
+      return writeNode.DefaultIntToBytesMapping(input);
+    } catch (Exception e) {
+      e.printStackTrace();
+      return null;
+    }
+  }
+
+  public static byte[] ShortToBytes(short input) {
+    try {
+      return writeNode.DefaultShortToBytesMapping(input);
+    } catch (Exception e) {
+      e.printStackTrace();
+      return null;
+    }
+  }
+
+  public static byte[] LongToBytes(long input) {
+    try {
+      return writeNode.DefaultLongToBytesMapping(input);
+    } catch (Exception e) {
+      e.printStackTrace();
+      return null;
+    }
+  }
+
+  public static byte[] FloatToBytes(float input) {
+    try {
+      return writeNode.DefaultFloatToBytesMapping(input);
+    } catch (Exception e) {
+      e.printStackTrace();
+      return null;
+    }
+  }
+
+  public static byte[] DoubleToBytes(double input) {
+    try {
+      return writeNode.DefaultDoubleToBytesMapping(input);
+    } catch (Exception e) {
+      e.printStackTrace();
+      return null;
+    }
+  }
+
+  public static byte[] CharToBytes(char input) {
+    try {
+      return writeNode.DefaultCharToBytesMapping(input);
+    } catch (Exception e) {
+      e.printStackTrace();
+      return null;
+    }
+  }
+
+  public static byte[] StringToBytes(String input) {
+    try {
+      return writeNode.DefaultStringToBytesMapping(input);
+    } catch (Exception e) {
+      e.printStackTrace();
+      return null;
+    }
+  }
+
+  public static <E extends Throwable> byte[] TreeToBytes(SerializeFunction<E> serializeFunction) throws E, IOException {
+    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+    JsonFactory factory = new JsonFactory();
+    JsonGenerator generator = factory.createGenerator(outputStream, JsonEncoding.UTF8);
+    serializeFunction.accept(generator, null);
+    generator.flush();
+    return outputStream.toString().getBytes();
+  }
+}
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/utils/IntList.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/utils/IntList.java
new file mode 100644
index 0000000000000000000000000000000000000000..7dba3a4c3c161519d26f5db3021aa2a53df513bf
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/utils/IntList.java
@@ -0,0 +1,31 @@
+package org.jastadd.ragconnect.tests.utils;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * TODO: Add description.
+ *
+ * @author rschoene - Initial contribution
+ */
+public class IntList {
+  private final List<Integer> integers;
+
+  public IntList(Integer... values) {
+    integers = Arrays.stream(values).filter(Objects::nonNull).collect(Collectors.toList());
+  }
+
+  public List<Integer> toList() {
+    return integers;
+  }
+
+  public List<Integer> toAbsList() {
+    return integers.stream().map(Math::abs).collect(Collectors.toList());
+  }
+
+  public static IntList list(Integer... values) {
+    return new IntList(values);
+  }
+}
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/utils/TestChecker.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/utils/TestChecker.java
new file mode 100644
index 0000000000000000000000000000000000000000..ed54fb954362c77bdbde120b127afb0deff34060
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/utils/TestChecker.java
@@ -0,0 +1,222 @@
+package org.jastadd.ragconnect.tests.utils;
+
+import org.assertj.core.groups.Tuple;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.fail;
+
+/**
+ * Lean checking of constraints in tests.
+ *
+ * @author rschoene - Initial contribution
+ */
+public class TestChecker {
+  private static final Logger logger = LoggerFactory.getLogger(TestChecker.class);
+  private final static String NUMBER_OF_VALUES = "numberOfValues";
+  public final ValuesToCompare<Object> objectValues = new ValuesToCompare<>(this);
+  public final ValuesToCompare<String> stringValues = new ValuesToCompare<>(this);
+  public final ValuesToCompare<Tuple> tupleValues = new ValuesToCompare<>(this);
+  public final IntegerValuesToCompare intValues = new IntegerValuesToCompare(this);
+  private boolean needManualWait = true;
+  private boolean useManualWait = true;
+
+  public TestChecker incNumberOfValues() {
+    return intValues.incNumberOfValues();
+  }
+
+  public TestChecker addToNumberOfValues(int increment) {
+    return intValues.addToNumberOfValues(increment);
+  }
+
+  public TestChecker setActualNumberOfValues(Callable<Integer> actual) {
+    return intValues.setActualNumberOfValues(actual);
+  }
+
+  public TestChecker alwaysWait() {
+    return setActualNumberOfValues(() -> 0);
+  }
+
+  public TestChecker put(String name, Object expected) {
+    return objectValues.put(name, expected);
+  }
+
+  public TestChecker setCheckForObject(String name, BiConsumer<String, Object> check) {
+    return objectValues.setCheck(name, check);
+  }
+
+  public TestChecker setActualString(String name, Callable<String> actual) {
+    return stringValues.setActual(name, actual);
+  }
+
+  public TestChecker setCheckForString(String name, BiConsumer<String, String> check) {
+    return stringValues.setCheck(name, check);
+  }
+
+  public TestChecker put(String name, String expected) {
+    return stringValues.put(name, expected);
+  }
+
+  public TestChecker setActualTuple(String name, Callable<Tuple> actual) {
+    return tupleValues.setActual(name, actual);
+  }
+
+  public TestChecker setCheckForTuple(String name, BiConsumer<String, Tuple> check) {
+    return tupleValues.setCheck(name, check);
+  }
+
+  public TestChecker put(String name, Tuple expected) {
+    return tupleValues.put(name, expected);
+  }
+
+  public TestChecker setActualInteger(String name, Callable<Integer> actual) {
+    return intValues.setActual(name, actual);
+  }
+
+  public TestChecker setCheckForInteger(String name, BiConsumer<String, Integer> check) {
+    return intValues.setCheck(name, check);
+  }
+
+  public TestChecker put(String name, Integer expected) {
+    return intValues.put(name, expected);
+  }
+
+  public TestChecker disableManualWait() {
+    useManualWait = false;
+    needManualWait = false;
+    return this;
+  }
+
+  public void check() {
+    if (needManualWait) {
+      try {
+        TestUtils.waitForMqtt();
+      } catch (InterruptedException e) {
+        fail(e);
+      }
+    }
+    intValues.get(NUMBER_OF_VALUES).checkAwait(NUMBER_OF_VALUES);
+
+    objectValues.forEach((name, aae) -> aae.check(name));
+    stringValues.forEach((name, aae) -> aae.check(name));
+    tupleValues.forEach((name, aae) -> aae.check(name));
+    intValues.forEach((name, aae) -> {
+      if (!name.equals(NUMBER_OF_VALUES)) {
+        aae.check(name);
+      }
+    });
+    needManualWait = useManualWait;
+  }
+
+  static class ActualAndExpected<T> {
+    Callable<T> actual;
+    T expected;
+    BiConsumer<String, T> customCheck;
+
+    ActualAndExpected(String key) {
+      expected = null;
+    }
+
+    void check(String name) {
+      if (customCheck != null) {
+        customCheck.accept(name, expected);
+        return;
+      }
+      if (actual == null) {
+        fail("No actual getter defined for " + name);
+      }
+      T actualValue = null;
+      try {
+        actualValue = this.actual.call();
+      } catch (Exception e) {
+        fail(e);
+      }
+      assertThat(actualValue).as(name).isEqualTo(expected);
+    }
+
+    void checkAwait(String name) {
+      if (customCheck != null) {
+        logger.warn("Custom check set for {}. Can't await for that.", name);
+        customCheck.accept(name, expected);
+        return;
+      }
+      if (actual == null) {
+        fail("No actual getter defined for " + name);
+      }
+      TestUtils.awaitMqtt().alias(name + " == " + expected).until(actual, Predicate.isEqual(expected));
+    }
+  }
+
+  static class ValuesToCompare<T> {
+    protected final Map<String, ActualAndExpected<T>> values = new HashMap<>();
+    protected final TestChecker parent;
+
+    ValuesToCompare(TestChecker parent) {
+      this.parent = parent;
+    }
+
+    public TestChecker setActual(String name, Callable<T> actual) {
+      _computeIfAbsent(name).actual = actual;
+      return parent;
+    }
+
+    public TestChecker setCheck(String name, BiConsumer<String, T> check) {
+      _computeIfAbsent(name).customCheck = check;
+      return parent;
+    }
+
+    public TestChecker put(String name, T expected) {
+      _computeIfAbsent(name).expected = expected;
+      return parent;
+    }
+
+    public TestChecker updateExpected(String name, Function<T, T> updater) {
+      ActualAndExpected<T> aae = _computeIfAbsent(name);
+      aae.expected = updater.apply(aae.expected);
+      return parent;
+    }
+
+    private ActualAndExpected<T> _computeIfAbsent(String name) {
+      return values.computeIfAbsent(name, ActualAndExpected::new);
+    }
+
+    ActualAndExpected<T> get(String name) {
+      return values.get(name);
+    }
+
+    void forEach(BiConsumer<? super String, ? super ActualAndExpected<T>> action) {
+      values.forEach(action);
+    }
+  }
+
+  static class IntegerValuesToCompare extends ValuesToCompare<Integer> {
+    IntegerValuesToCompare(TestChecker parent) {
+      super(parent);
+    }
+
+    public TestChecker incNumberOfValues() {
+      return addToNumberOfValues(1);
+    }
+
+    public TestChecker addToNumberOfValues(int increment) {
+      // if there is at least one call to this, we do not need to manually wait in the next check()
+      parent.needManualWait = false;
+      Integer currentExpected = values.computeIfAbsent(NUMBER_OF_VALUES, ActualAndExpected::new).expected;
+      return put(NUMBER_OF_VALUES, currentExpected + increment);
+    }
+
+    public TestChecker setActualNumberOfValues(Callable<Integer> actual) {
+      setActual(NUMBER_OF_VALUES, actual);
+      values.get(NUMBER_OF_VALUES).expected = 0;
+      return parent;
+    }
+  }
+}
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/utils/TestUtils.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/utils/TestUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..c77192b09c2e1c16b2728bcf6baf91d6209db541
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/utils/TestUtils.java
@@ -0,0 +1,181 @@
+package org.jastadd.ragconnect.tests.utils;
+
+import org.awaitility.Awaitility;
+import org.awaitility.core.ConditionFactory;
+import org.jastadd.ragconnect.compiler.Compiler;
+import org.junit.jupiter.api.Assertions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+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.*;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+/**
+ * Utility methods for tests.
+ *
+ * @author rschoene - Initial contribution
+ */
+public class TestUtils {
+
+  private static final Logger logger = LoggerFactory.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 boolean isCi() {
+    return System.getenv("GITLAB_CI") != null;
+  }
+
+  public static String getMqttHost() {
+    if (isCi()) {
+      // we are in the CI, so use "mqtt" as host
+      return "mqtt";
+    } {
+      // else assume a locally running mqtt broker
+      return "localhost";
+    }
+  }
+
+  public static String mqttUri(String path) {
+    return "mqtt://" + getMqttHost() + "/" + path;
+  }
+
+  public static String restUri(String path, int port) {
+    return "rest://localhost:" + port + "/" + path;
+  }
+
+  public static String javaUri(String path) {
+    return "java://localhost/" + path;
+  }
+
+  public static int getMqttDefaultPort() {
+    return 1883;
+  }
+
+  public static Path runCompiler(String grammarFile, Iterable<String> connectFiles, String rootNode, String outputDirectory, int expectedReturnValue, String... additionalArguments) {
+
+    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));
+      args.addAll(Arrays.asList(additionalArguments));
+
+      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;
+  }
+
+  public static <T> String prettyPrint(Iterable<T> aList, Function<T, String> elementPrinter) {
+    StringJoiner sj = new StringJoiner(", ", "[", "]");
+    aList.forEach(element -> sj.add(elementPrinter.apply(element)));
+    return sj.toString();
+  }
+
+  public static void assertLinesMatch(String directory, String expectedName, String out) throws IOException {
+    Path expectedPath = Paths.get(TestUtils.INPUT_DIRECTORY_PREFIX)
+            .resolve(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"))
+            .sorted()
+            .filter(s -> !s.isEmpty() && !s.startsWith("//"))
+            .collect(Collectors.toList());
+
+    Assertions.assertLinesMatch(expectedList, outList);
+  }
+
+  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");
+    String javaBin = javaHome + File.separator + "bin" + File.separator + "java";
+    String classpath = System.getProperty("java.class.path");
+    String className = klass.getName();
+
+    String[] newArgs = new String[args.length + 4];
+    newArgs[0] = javaBin;
+    newArgs[1] = "-cp";
+    newArgs[2] = classpath;
+    newArgs[3] = className;
+    System.arraycopy(args, 0, newArgs, 4, args.length);
+
+    ProcessBuilder builder = new ProcessBuilder(newArgs);
+//    builder.redirectOutput(err);
+    builder.redirectError(err);
+
+    Process process = builder.start();
+    process.waitFor();
+    return process.exitValue();
+  }
+
+  public static void testJaddContainReferenceToJackson(Path path, boolean shouldContain) {
+    try {
+      String content = Files.readString(path);
+      boolean actualContain = content.contains("com.fasterxml.jackson.databind.ObjectMapper");
+      if (actualContain && !shouldContain) {
+        fail(path + " should not depend on jackson library, but does");
+      }
+      if (!actualContain && shouldContain) {
+        fail(path + " does not depend on jackson library");
+      }
+    } catch (IOException e) {
+      fail(e);
+    }
+  }
+
+  public static String readFile(Path path, Charset encoding)
+      throws IOException {
+    byte[] encoded = Files.readAllBytes(path);
+    return new String(encoded, encoding);
+  }
+
+  public static void waitForMqtt() throws InterruptedException {
+    TimeUnit.MILLISECONDS.sleep(1500);
+  }
+
+  public static ConditionFactory awaitMqtt() {
+    return Awaitility.await().atMost(1500, TimeUnit.MILLISECONDS);
+  }
+
+  static <T_Event, T_ASTNode> void logEvent(T_Event event, T_ASTNode node, String attribute, Object params, Object value) {
+    logger.info("event: {}, node: {}, attribute: {}, params: {}, value: {}",
+            event, node, attribute, params, value);
+  }
+
+}
diff --git a/ragconnect.tests/src/test/resources/log4j2.xml b/ragconnect.tests/src/test/resources/log4j2.xml
index 4c0d4548c61b23abad6aabc6811e68cd8a928871..9a1b22ee0120043991dfcfc7ad9c41f6dd5b4f06 100644
--- a/ragconnect.tests/src/test/resources/log4j2.xml
+++ b/ragconnect.tests/src/test/resources/log4j2.xml
@@ -2,8 +2,11 @@
 <Configuration status="INFO">
     <Appenders>
         <Console name="Console" target="SYSTEM_OUT">
-            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
+            <PatternLayout pattern="%highlight{%d{HH:mm:ss.SSS} %-5level [%t] %logger{20} - %msg%n}" disableAnsi="false"/>
         </Console>
+        <File name="TestLogs" fileName="test.log" append="false">
+            <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level [%t] %logger{20} - %msg%n"/>
+        </File>
     </Appenders>
     <Loggers>
         <Root level="debug">
diff --git a/relast-preprocessor b/relast-preprocessor
deleted file mode 160000
index 02f8e35993dc3f62ab49e94f69a6dc27170660da..0000000000000000000000000000000000000000
--- a/relast-preprocessor
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 02f8e35993dc3f62ab49e94f69a6dc27170660da
diff --git a/settings.gradle b/settings.gradle
index 8a597cfa491920744bb8b5e1d3f103fb4fc95bb1..fb822723486ddf9b5c6937af69332d8409d52e1b 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -6,6 +6,6 @@ pluginManagement {
 
 rootProject.name = 'ragconnect'
 
-include 'relast-preprocessor'
 include 'ragconnect.base'
 include 'ragconnect.tests'
+include 'ragconnect.featureTest'