diff --git a/.gitignore b/.gitignore
index 78aba4bb88ae15cc77fd1bb1b454f37c145e3839..64adf30139f5da14a6e83494de50159c10b76665 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,4 @@
 .idea/
 .gradle/
 build/
+public/
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index e5275bd786ef8e40f8ee96b03564c36f41f22394..76278f6e80dfc8b78a152a6f653ffff7c3c5461c 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,11 +1,14 @@
 variables:
   GIT_SUBMODULE_STRATEGY: recursive
+  GIT_DEPTH: 1000
 
 stages:
 - build
 - test
-# - publish
-
+#- ragdoc_build
+#- ragdoc_view
+#- publish
+>
 before_script:
   - export GRADLE_USER_HOME=`pwd`/.gradle
 
@@ -21,38 +24,84 @@ build:
     - ./gradlew --console=plain --no-daemon assemble jar
   artifacts:
     paths:
-      - "/builds/jastadd/ragconnect/ragconnect.base/build/libs/ragconnect-*.jar"
+      - "ragconnect.base/build/libs/ragconnect-*.jar"
+      - "ragconnect.base/src/gen"
     expire_in: 1 week
 
 test:
   image: openjdk:11
   stage: test
   services:
-    - name: "eclipse-mosquitto:1.6.9"
+    - name: "eclipse-mosquitto:1.6"
       alias: "mqtt"
+  needs:
+    - build
   script:
     - ./gradlew --console=plain --no-daemon allTests
   artifacts:
+    when: always
     reports:
-      junit: "/builds/jastadd/ragconnect/ragconnect.tests/build/test-results/test/TEST-*.xml"
+      junit: "ragconnect.tests/build/test-results/**/TEST-*.xml"
     expire_in: 1 week
 
 #publish:
 #  image: openjdk:11
 #  stage: publish
+#  needs:
+#    - test
 #  script:
 #    - "./gradlew publish"
 #  only:
+#    - dev
 #    - master
 
-#pages:
-#  image: python:3.7-alpine
-#  stage: publish
+#ragdoc_build:
+#  image:
+#    name: "git-st.inf.tu-dresden.de:4567/jastadd/ragdoc-builder"
+#    entrypoint: [""]
+#  stage: ragdoc_build
+#  needs:
+#    - build
 #  script:
-#  - pip install -U sphinx sphinx-rtd-theme recommonmark sphinxemoji sphinx-markdown-tables
-#  - sphinx-build -b html pages/ public
+#    - JAVA_FILES=$(find ragconnect.base/src/ -name '*.java')
+#    - /ragdoc-builder/start-builder.sh -excludeGenerated -d data/ $JAVA_FILES
 #  artifacts:
 #    paths:
-#    - public
+#      - "data/"
+
+#ragdoc_view:
+#  image:
+#    name: "git-st.inf.tu-dresden.de:4567/jastadd/ragdoc-view:relations"
+#    entrypoint: [""]
+#  stage: ragdoc_view
+#  needs:
+#    - ragdoc_build
+#  script:
+#    - DATA_DIR=$(pwd -P)/data
+#    - mkdir -p pages/docs/ragdoc
+#    - 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:
-#  - master
+#    - dev
+#    - master
+#  artifacts:
+#    paths:
+#     - "pages/docs/ragdoc"
+
+#pages:
+#  image: python:3.8-buster
+# stage: publish
+# needs:
+#   - ragdoc_view
+#    - test
+# before_script:
+#   - pip install -U mkdocs mkdocs-macros-plugin mkdocs-git-revision-date-localized-plugin
+#  script:
+#   - cd pages && mkdocs build
+#only:
+#  - dev
+#    - master
+#artifacts:
+# paths:
+#  - public
diff --git a/.gitmodules b/.gitmodules
index 0cf1e8a6d073c7c58b0881fe53f5642e6a1022e7..14c250a57ed1d3278b5d6ce961c8fae45e9266cb 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,6 +1,7 @@
 [submodule "relast-preprocessor"]
 	path = relast-preprocessor
 	url = ../relast-preprocessor.git
+	branch = develop
 [submodule "ragconnect.base/src/main/jastadd/mustache"]
 	path = ragconnect.base/src/main/jastadd/mustache
-	url = ../mustache
+	url = git@git-st.inf.tu-dresden.de:jastadd/jastadd-ceti/mustache.git
diff --git a/README.md b/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..3b162ddae3e87812e517c2c20ce639186cf05946
--- /dev/null
+++ b/README.md
@@ -0,0 +1,7 @@
+# RagConnect
+
+[RagConnect](https://git-st.inf.tu-dresden.de/jastadd/ragconnect) is a preprocessor to enable easy connection to/from models based on [Reference Attribute Grammars](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.108.8792&rep=rep1&type=pdf) and [Relational Reference Attribute Grammars](https://doi.org/10.1016/j.cola.2019.100940) built with [JastAdd](http://jastadd.org/).
+
+Documentation can be found at <http://connect.relational-rags.eu> including an API documentation.
+
+The most recent version is listed at the [package registry](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/packages).
diff --git a/libs/buildSrc.jar b/libs/buildSrc.jar
deleted file mode 100644
index b0ca2cbc4fc7f12022592944d7446a429d855add..0000000000000000000000000000000000000000
Binary files a/libs/buildSrc.jar and /dev/null differ
diff --git a/libs/jastadd2.jar b/libs/jastadd2.jar
new file mode 100644
index 0000000000000000000000000000000000000000..d615b895453d660f0e7397fffad58a05029169fd
Binary files /dev/null and b/libs/jastadd2.jar differ
diff --git a/pages/.gitattributes b/pages/.gitattributes
index 6b5d9827cd5416e9e2568615d135b563f76cdbc5..1397af0c29996b68da5f01a8a92d8f4aca20427a 100644
--- a/pages/.gitattributes
+++ b/pages/.gitattributes
@@ -1 +1,2 @@
-_static/poster-presentation.mp4 filter=lfs diff=lfs merge=lfs -text
+docs/img/poster-presentation.mp4 filter=lfs diff=lfs merge=lfs -text
+docs/img/moving-robot.mp4 filter=lfs diff=lfs merge=lfs -text
diff --git a/pages/.gitignore b/pages/.gitignore
index 87174b686c0670d3fb6a66535729af2a8a249d72..14338f39f89e2a45e3473a6597e640aa9458f9a8 100644
--- a/pages/.gitignore
+++ b/pages/.gitignore
@@ -1 +1,2 @@
-/public/
+/docs/ragdoc/
+__pycache__
diff --git a/pages/Makefile b/pages/Makefile
deleted file mode 100644
index 7878ee5305af7d6c04860113aa9ef008991d07af..0000000000000000000000000000000000000000
--- a/pages/Makefile
+++ /dev/null
@@ -1,20 +0,0 @@
-# Minimal makefile for Sphinx documentation
-#
-
-# You can set these variables from the command line, and also
-# from the environment for the first two.
-SPHINXOPTS    ?=
-SPHINXBUILD   ?= sphinx-build
-SOURCEDIR     = .
-BUILDDIR      = public
-
-# Put it first so that "make" without argument is like "make help".
-help:
-	@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
-
-.PHONY: help Makefile
-
-# Catch-all target: route all unknown targets to Sphinx using the new
-# "make mode" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).
-%: Makefile
-	@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
diff --git a/pages/_static/moving-robot.mp4 b/pages/_static/moving-robot.mp4
deleted file mode 100644
index 05f00803bcceb88eb08a7b4dd44f297e359c8b32..0000000000000000000000000000000000000000
Binary files a/pages/_static/moving-robot.mp4 and /dev/null differ
diff --git a/pages/conf.py b/pages/conf.py
deleted file mode 100644
index bdd91e4f410636b477239f5ff4285eced4516914..0000000000000000000000000000000000000000
--- a/pages/conf.py
+++ /dev/null
@@ -1,66 +0,0 @@
-# Configuration file for the Sphinx documentation builder.
-#
-# This file only contains a selection of the most common options. For a full
-# list see the documentation:
-# https://www.sphinx-doc.org/en/master/usage/configuration.html
-
-# -- Path setup --------------------------------------------------------------
-
-# If extensions (or modules to document with autodoc) are in another directory,
-# add these directories to sys.path here. If the directory is relative to the
-# documentation root, use os.path.abspath to make it absolute, like shown here.
-#
-# import os
-# import sys
-# sys.path.insert(0, os.path.abspath('.'))
-import sphinx_rtd_theme
-
-
-# -- Project information -----------------------------------------------------
-
-project = 'RagConnect'
-copyright = '2021, René Schöne, Johannes Mey'
-author = 'René Schöne, Johannes Mey'
-
-# The full version, including alpha/beta/rc tags
-ragconnectVersionFileName = '../ragconnect.base/src/main/resources/ragConnectVersion.properties'
-with open(ragconnectVersionFileName) as ragconnectVersionFile:
-    versionFileContent = ragconnectVersionFile.read()
-release = version = versionFileContent[versionFileContent.rindex('version=') + 8:].strip()
-print('Version: ' + version)
-
-
-# -- General configuration ---------------------------------------------------
-
-# Add any Sphinx extension module names here, as strings. They can be
-# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
-# ones.
-extensions = [
-    'sphinx_rtd_theme',
-    'recommonmark',
-    'sphinxemoji.sphinxemoji',
-    'sphinx_markdown_tables'
-]
-
-# Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
-
-# List of patterns, relative to source directory, that match files and
-# directories to ignore when looking for source files.
-# This pattern also affects html_static_path and html_extra_path.
-exclude_patterns = []
-
-
-# -- Options for HTML output -------------------------------------------------
-
-# The theme to use for HTML and HTML Help pages.  See the documentation for
-# a list of builtin themes.
-#
-html_theme = 'sphinx_rtd_theme'
-
-# Add any paths that contain custom static files (such as style sheets) here,
-# relative to this directory. They are copied after the builtin static files,
-# so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ['_static']
-
-sphinxemoji_style = 'twemoji'
diff --git a/pages/custom_theme/footer.html b/pages/custom_theme/footer.html
new file mode 100644
index 0000000000000000000000000000000000000000..2d6c164695b3a1ed19711d3497caf142a8416ebb
--- /dev/null
+++ b/pages/custom_theme/footer.html
@@ -0,0 +1,11 @@
+{% block footer %}
+<p>{% if config.copyright %}
+<small>{{ config.copyright }}<br></small>
+{% endif %}
+<hr>
+Built with <a href="https://www.mkdocs.org/">MkDocs</a> using a <a href="https://github.com/snide/sphinx_rtd_theme">theme</a> provided by <a href="https://readthedocs.org">Read the Docs</a>.
+{% if page and page.meta and page.meta.git_revision_date_localized %}
+<small><br><i>Last updated {{ page.meta.git_revision_date_localized }}</i></small>
+{% endif %}
+</p>
+{% endblock %}
diff --git a/pages/adding.md b/pages/docs/adding.md
similarity index 100%
rename from pages/adding.md
rename to pages/docs/adding.md
diff --git a/pages/docs/changelog.md b/pages/docs/changelog.md
new file mode 100644
index 0000000000000000000000000000000000000000..b2607fdf120d0b44ee8dd4a560cc4ad4047f0524
--- /dev/null
+++ b/pages/docs/changelog.md
@@ -0,0 +1,34 @@
+# Changelog
+
+## 0.3.1
+
+- Full support for incremental dependency tracking
+- Full support for subtree endpoint definitions ([#9](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/9))
+- Internal: Use updated gradle plugin for tests ([#18](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/18))
+- Bugfix [#22](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/22): Correct handling of malformed URIs passed when connecting an endpoint
+- Bugfix [#23](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/23): Correct handling of OptComponents as endpoints
+- Bugfix [#27](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/27): Correctly handle whitespaces in grammars
+
+## 0.3.0
+
+- Added [API documentation](ragdoc/index.html) to documentation
+- Add methods to `disconnect` an endpoint
+- Internal: PoC for incremental dependency tracking and subtree endpoint definitions ([#14](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/14))
+- Bugfix [#17](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/17): Added missing support for `boolean`
+
+## 0.2.2
+
+- Allow normal tokens to be used in send definitions
+
+## 0.2.1
+
+- New communication protocol: REST
+- Selection of protocol when `connect` methods are called, by scheme of given URI
+- Development changes:
+    - Supported printing out YAML data used for mustache templates
+    - Moved string constants to `MRagConnect` structure
+
+## 0.2.0
+
+- Version submitted in paper "A Connection from ROS to RAG-Based Models" (2020)
+- Supported communication protocols: MQTT
diff --git a/pages/docs/extending.md b/pages/docs/extending.md
new file mode 100644
index 0000000000000000000000000000000000000000..61cabca224b7834f08cca7d96f3913c7cb9feb37
--- /dev/null
+++ b/pages/docs/extending.md
@@ -0,0 +1,85 @@
+# 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):
+
+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`.
+{% endraw %}
+
+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 `ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/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`
+
+Furthermore, new test cases are appreciated, see [below](#writing-tests).
+
+## Writing Tests
+
+To add new tests, have a look at the module `ragconnect.tests`.
+It has three parts:
+1) In `src/test/01-input/*` are the [specifications](#specifications) that are going to be compiled (in principle using the steps described in [the guide to add RagConnect](adding)).
+2) In `src/test/java`, the jUnit 5 [test classes](#test-classes) are implemented. They mostly correspond 1-to-1 to a directory of the first part.
+3) In `build.gradle` the [instructions how to compile](#buildgradle) the specifications using the gradle plugin [PreprocessorPlugin][preprocessor-plugin] (`org.jastadd.preprocessor:testing`).
+
+### Specifications
+
+Every specification must have at least a `README.md` to describe the purpose of the test, a grammar `Test.relast`, and a RagConnect specification `Test.connect`.
+Usually an aspect file `Test.jadd` is included.
+
+### Test Classes
+
+Based on jUnit 5, the test classes testing some behaviour. If sending and/or receiving functionality is used, consider extending `AbstractMqttTest` in order to avoid duplicate code. In case of extending this class, please order the methods according to their lifecycle, i.e.:
+- createModel
+- setupReceiverAndConnect
+- communicateSendInitialValue
+- communicateOnlyUpdatedValue
+- closeConnections
+
+Within `AbstractMqttTest`, an `MqttHandler` named `publisher` is available to publish content.
+Some convenience methods are provided in `TestUtils`, e.g., the `DefaultMappings`, and `mqttUri` to prepend `"mqtt://"` and the correct host for the mqtt broker (`localhost` or a CI-specific host).
+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:
+
+```groovy
+task compileTreeAllowedTokens(type: RagConnectTest) {
+    ragconnect {
+        outputDir = file('src/test/02-after-ragconnect/treeAllowedTokens')
+        inputFiles = [file('src/test/01-input/treeAllowedTokens/Test.relast'),
+                      file('src/test/01-input/treeAllowedTokens/Test.connect'),
+                      file('src/test/01-input/treeAllowedTokens/TestDependencies.connect')]
+        rootNode = 'Root'
+    }
+    relast {
+        useJastAddNames = true
+        grammarName = 'src/test/03-after-relast/treeAllowedTokens/treeAllowedTokens'
+        serializer = 'jackson'
+    }
+    jastadd {
+        jastAddList = 'JastAddList'
+        packageName = 'treeAllowedTokens.ast'
+        inputFiles = [file('src/test/01-input/treeAllowedTokens/Test.jadd')]
+    }
+}
+```
+
+[preprocessor-plugin]: https://git-st.inf.tu-dresden.de/jastadd/testing
diff --git a/pages/docs/img/moving-robot.mp4 b/pages/docs/img/moving-robot.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..40da2c6367777d89f1b0193b19312363f0ef0db1
--- /dev/null
+++ b/pages/docs/img/moving-robot.mp4
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:814b592f202dd72ae2cdb7b0841faefbc963d7847d9f568aca75547c647ee1e3
+size 1867731
diff --git a/pages/_static/mpm4cps-slides.pdf b/pages/docs/img/mpm4cps-slides.pdf
similarity index 100%
rename from pages/_static/mpm4cps-slides.pdf
rename to pages/docs/img/mpm4cps-slides.pdf
diff --git a/pages/_static/poster-presentation.mp4 b/pages/docs/img/poster-presentation.mp4
similarity index 100%
rename from pages/_static/poster-presentation.mp4
rename to pages/docs/img/poster-presentation.mp4
diff --git a/pages/_static/poster.pdf b/pages/docs/img/poster.pdf
similarity index 100%
rename from pages/_static/poster.pdf
rename to pages/docs/img/poster.pdf
diff --git a/pages/images/robo3d.png b/pages/docs/img/robo3d.png
similarity index 100%
rename from pages/images/robo3d.png
rename to pages/docs/img/robo3d.png
diff --git a/pages/images/ros2rag-process.png b/pages/docs/img/ros2rag-process.png
similarity index 100%
rename from pages/images/ros2rag-process.png
rename to pages/docs/img/ros2rag-process.png
diff --git a/pages/docs/index.md b/pages/docs/index.md
new file mode 100644
index 0000000000000000000000000000000000000000..d345eec5f7a9f7a688dbf28ced0f95e7dbd74566
--- /dev/null
+++ b/pages/docs/index.md
@@ -0,0 +1,3 @@
+# RagConnect Documentation
+
+[RagConnect](https://git-st.inf.tu-dresden.de/jastadd/ragconnect) is a preprocessor to enable easy connection to/from models based on [Reference Attribute Grammars](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.108.8792&rep=rep1&type=pdf) and [Relational Reference Attribute Grammars](https://doi.org/10.1016/j.cola.2019.100940) built with [JastAdd](http://jastadd.org/).
diff --git a/pages/inner-workings.md b/pages/docs/inner-workings.md
similarity index 93%
rename from pages/inner-workings.md
rename to pages/docs/inner-workings.md
index 9a244b9e72debabc3cdb138c7654fb66ce217f53..6d8b41e13847f2ae337f08c65068cbef6085e065 100644
--- a/pages/inner-workings.md
+++ b/pages/docs/inner-workings.md
@@ -1,6 +1,8 @@
 # Inner workings of `RagConnect`
 
-![ros2rag-process](images/ros2rag-process.png)
+Please see [API documentation](ragdoc/index.html) for more details.
+
+![ros2rag-process](img/ros2rag-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`.
diff --git a/pages/use_cases.md b/pages/docs/use_cases.md
similarity index 74%
rename from pages/use_cases.md
rename to pages/docs/use_cases.md
index c479b65fdf40260ec8759617fa9f20e7529302fe..b815b7a5f6fab004f4573e29054858a10fd5d7db 100644
--- a/pages/use_cases.md
+++ b/pages/docs/use_cases.md
@@ -1,17 +1,17 @@
 # Use cases with `RagConnect`
 
-## 1⃣ MPM4CPS Paper - Codename 'Ros2Rag'
+## MPM4CPS Paper - 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 conencted to it was shown.
+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](images/robo3d.png)
+![Screenshot of Gazebo](img/robo3d.png)
 
-[📽 Recording of the simulated robot during the use case](_static/moving-robot.mp4)
+[📽 Recording of the simulated robot during the use case](img/moving-robot.mp4)
 
-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](_static/mpm4cps-slides.pdf) or the [a recording of the session](https://youtu.be/Hgc1qFfmr44?t=1220).
+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) or the [a recording of the session](https://youtu.be/Hgc1qFfmr44?t=1220).
 
-There is also a [poster](_static/poster.pdf) and a [pre-recorded presentation](_static/poster-presentation.mp4) of this poster.
+There is also a [poster](img/poster.pdf) and a [pre-recorded presentation](img/poster-presentation.mp4) of this poster.
 
 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.
diff --git a/pages/using.md b/pages/docs/using.md
similarity index 100%
rename from pages/using.md
rename to pages/docs/using.md
diff --git a/pages/extending.md b/pages/extending.md
deleted file mode 100644
index baa6c5380f5dad2c3e351a3027c6c9a287e96f6d..0000000000000000000000000000000000000000
--- a/pages/extending.md
+++ /dev/null
@@ -1,26 +0,0 @@
-# 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):
-
-Within `ragconnect.base/src/main/resources`:
-- 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`.
-
-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 `ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/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`
-
-Furthermore, new test cases are appreciated. They can be added in the [ragconnect.rests repository](https://git-st.inf.tu-dresden.de/jastadd/ragconnect-tests)
diff --git a/pages/index.rst b/pages/index.rst
deleted file mode 100644
index 2da98fa853da557649b89085755852cac20cef7c..0000000000000000000000000000000000000000
--- a/pages/index.rst
+++ /dev/null
@@ -1,27 +0,0 @@
-.. RagConnect documentation master file, created by
-   sphinx-quickstart on Fri Aug 28 10:16:26 2020.
-   You can adapt this file completely to your liking, but it should at least
-   contain the root `toctree` directive.
-
-RagConnect Documentation
-========================
-
-`RagConnect <https://git-st.inf.tu-dresden.de/jastadd/ragconnect>`_ is a preprocessor to enable easy connection to/from models based on `Reference Attribute Grammars <http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.108.8792&rep=rep1&type=pdf>`_ and `Relation Reference Attribute Grammars <https://doi.org/10.1016/j.cola.2019.100940>`_ built with `JastAdd <http://jastadd.org/>`_.
-
-.. toctree::
-   :maxdepth: 2
-   :caption: Contents:
-
-   use_cases.md
-   adding.md
-   inner-workings.md
-   using.md
-   extending.md
-
-
-
-Indices and tables
-==================
-
-* :ref:`genindex`
-* :ref:`search`
diff --git a/pages/main.py b/pages/main.py
new file mode 100644
index 0000000000000000000000000000000000000000..5f574f69198e31785d5d9a4e2b228d5567d61384
--- /dev/null
+++ b/pages/main.py
@@ -0,0 +1,25 @@
+ragconnectVersionFileName = '../ragconnect.base/src/main/resources/ragConnectVersion.properties'
+
+
+def get_version():
+    with open(ragconnectVersionFileName) as ragconnectVersionFile:
+        versionFileContent = ragconnectVersionFile.read()
+    return versionFileContent[versionFileContent.rindex('version=') + 8:].strip()
+
+
+def define_env(env):
+    """
+    This is the hook for defining variables, macros and filters
+
+    - variables: the dictionary that contains the environment variables
+    - macro: a decorator function, to declare a macro.
+    """
+    env.conf['site_name'] = 'RagConnect ' + get_version()
+
+    @env.macro
+    def ragconnect_version():
+        return get_version()
+
+
+if __name__ == '__main__':
+    print(get_version())
diff --git a/pages/make.bat b/pages/make.bat
deleted file mode 100644
index c6abfba46443f5335943b13912c8d7b6d62ae644..0000000000000000000000000000000000000000
--- a/pages/make.bat
+++ /dev/null
@@ -1,35 +0,0 @@
-@ECHO OFF
-
-pushd %~dp0
-
-REM Command file for Sphinx documentation
-
-if "%SPHINXBUILD%" == "" (
-	set SPHINXBUILD=sphinx-build
-)
-set SOURCEDIR=.
-set BUILDDIR=public
-
-if "%1" == "" goto help
-
-%SPHINXBUILD% >NUL 2>NUL
-if errorlevel 9009 (
-	echo.
-	echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
-	echo.installed, then set the SPHINXBUILD environment variable to point
-	echo.to the full path of the 'sphinx-build' executable. Alternatively you
-	echo.may add the Sphinx directory to PATH.
-	echo.
-	echo.If you don't have Sphinx installed, grab it from
-	echo.http://sphinx-doc.org/
-	exit /b 1
-)
-
-%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
-goto end
-
-:help
-%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
-
-:end
-popd
diff --git a/pages/material/mpm4cps-slides.pdf b/pages/material/mpm4cps-slides.pdf
deleted file mode 100644
index 266f4e68e8806a398189cee8939ec393bf7f4ef4..0000000000000000000000000000000000000000
Binary files a/pages/material/mpm4cps-slides.pdf and /dev/null differ
diff --git a/pages/mkdocs.yml b/pages/mkdocs.yml
new file mode 100644
index 0000000000000000000000000000000000000000..6363c73ae7ac43b9c4d04c4eb0036ef36fb5019a
--- /dev/null
+++ b/pages/mkdocs.yml
@@ -0,0 +1,22 @@
+site_name: RagConnect
+nav:
+  - use_cases.md
+  - adding.md
+  - inner-workings.md
+  - using.md
+  - extending.md
+  - changelog.md
+  - API documentation: ragdoc/index.html
+theme:
+  name: readthedocs
+  custom_dir: custom_theme/
+plugins:
+  - search
+  - git-revision-date-localized:
+      type: datetime
+      timezone: Europe/Berlin
+      locale: en
+      fallback_to_build_date: True
+  - macros
+repo_url: https://git-st.inf.tu-dresden.de/jastadd/ragconnect
+site_dir: ../public
diff --git a/ragconnect.base/build.gradle b/ragconnect.base/build.gradle
index 5c25104cfc36370e958807e99961cc26502ccb24..753814f384186d096929fd2794ac6a19129bfb1f 100644
--- a/ragconnect.base/build.gradle
+++ b/ragconnect.base/build.gradle
@@ -27,22 +27,21 @@ repositories {
 
 dependencies {
     implementation project(':relast-preprocessor')
-    implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: "${log4j_version}"
-    implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: "${log4j_version}"
-    implementation group: 'org.apache.logging.log4j', name: 'log4j-jul', version: "${log4j_version}"
     implementation group: 'com.github.spullara.mustache.java', name: 'compiler', version: "${mustache_java_version}"
     // https://mvnrepository.com/artifact/org.apache.commons/commons-lang3
     api group: 'org.apache.commons', name: 'commons-lang3', version: '3.0'
     runtimeOnly group: 'org.jastadd', name: 'jastadd', version: '2.3.4'
+//    runtimeOnly group: 'org.jastadd', name: 'jastadd', version: '2.3.5'
+    runtimeOnly fileTree(include: ['jastadd2.jar'], dir: '../libs')
     api group: 'net.sf.beaver', name: 'beaver-rt', version: '0.9.11'
 }
 
 def versionFile = 'src/main/resources/ragConnectVersion.properties'
-def oldProps = new Properties()
+def props = new Properties()
 
 try {
-    file(versionFile).withInputStream { stream -> oldProps.load(stream) }
-    version = oldProps['version']
+    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)
@@ -56,9 +55,9 @@ task printVersion() {
 
 task newVersion() {
     doFirst {
-        def props = new Properties()
-        props['version'] = value
-        props.store(file(versionFile).newWriter(), null)
+        def newProps = new Properties()
+        newProps['version'] = value
+        newProps.store(file(versionFile).newWriter(), null)
     }
 }
 
@@ -69,14 +68,12 @@ idea.module.generatedSourceDirs += genSrc
 jar {
     manifest {
         attributes "Main-Class": 'org.jastadd.ragconnect.compiler.Compiler'
-
-        // Log4J + Java 11 compatibility, see https://stackoverflow.com/q/53049346/2493208
-        attributes "Multi-Release": true
     }
 
     from {
         configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
     }
+    duplicatesStrategy = DuplicatesStrategy.EXCLUDE
 
     archiveBaseName = 'ragconnect'
 }
@@ -84,7 +81,7 @@ jar {
 File preprocessorGrammar = file('../relast-preprocessor/src/main/jastadd/RelAst.relast')
 File ragConnectGrammar = file('./src/main/jastadd/RagConnect.relast')
 File intermediateGrammar = file('./src/main/jastadd/intermediate/MustacheNodes.relast')
-File mustacheGrammar = file('./src/main/jastadd/mustache/Mustache.relast')
+File mustacheGrammar = file('../relast-preprocessor/src/main/jastadd/mustache/Mustache.relast')
 task relast(type: JavaExec) {
     group = 'Build'
     main = "-jar"
@@ -226,3 +223,4 @@ publishing {
 }
 
 publish.dependsOn jar
+jar.dependsOn ":relast-preprocessor:jar"
diff --git a/ragconnect.base/src/main/jastadd/Analysis.jrag b/ragconnect.base/src/main/jastadd/Analysis.jrag
index a1367a9c21bf2f293dd33216ea20a8941f5411b5..ac891b0e819b1e57e135afced64495a76396db5e 100644
--- a/ragconnect.base/src/main/jastadd/Analysis.jrag
+++ b/ragconnect.base/src/main/jastadd/Analysis.jrag
@@ -1,10 +1,10 @@
 aspect Analysis {
   // --- lookupTokenEndpointDefinition ---
   inh java.util.List<TokenEndpointDefinition> TokenEndpointDefinition.lookupTokenEndpointDefinitions(TokenComponent token);
-  eq RagConnect.getEndpointDefinition().lookupTokenEndpointDefinitions(TokenComponent token) = lookupTokenEndpointDefinitions(token);
+  eq RagConnect.getConnectSpecificationFile().lookupTokenEndpointDefinitions(TokenComponent token) = lookupTokenEndpointDefinitions(token);
   syn java.util.List<TokenEndpointDefinition> RagConnect.lookupTokenEndpointDefinitions(TokenComponent token) {
     java.util.List<TokenEndpointDefinition> result = new java.util.ArrayList<>();
-    for (EndpointDefinition def : getEndpointDefinitionList()) {
+    for (EndpointDefinition def : allEndpointDefinitionList()) {
       if (def.isTokenEndpointDefinition() && def.asTokenEndpointDefinition().getToken().equals(token)) {
         result.add(def.asTokenEndpointDefinition());
       }
@@ -12,10 +12,23 @@ aspect Analysis {
     return result;
   }
 
+  // --- 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());
+      }
+    }
+    return result;
+  }
+
   // --- lookupDependencyDefinition ---
   inh DependencyDefinition DependencyDefinition.lookupDependencyDefinition(TypeDecl source, String id);
-  eq RagConnect.getDependencyDefinition().lookupDependencyDefinition(TypeDecl source, String id) {
-    for (DependencyDefinition def : getDependencyDefinitionList()) {
+  eq RagConnect.getConnectSpecificationFile().lookupDependencyDefinition(TypeDecl source, String id) {
+    for (DependencyDefinition def : allDependencyDefinitionList()) {
       if (def.getID().equals(id) && def.getSource().containingTypeDecl().equals(source)) {
         return def;
       }
@@ -31,10 +44,12 @@ aspect Analysis {
   }
   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();
 
+  // --- assignableTo ---
   syn boolean MappingDefinitionType.assignableTo(JavaTypeUse target);
   eq JavaMappingDefinitionType.assignableTo(JavaTypeUse target) = getType().assignableTo(target);
   eq JavaArrayMappingDefinitionType.assignableTo(JavaTypeUse target) {
@@ -47,6 +62,8 @@ aspect Analysis {
   }
   syn String JavaTypeUse.primitivePrettyPrint() {
     switch(getName()) {
+      case "boolean":
+      case "Boolean": return "boolean";
       case "int":
       case "Integer": return "int";
       case "short":
@@ -65,7 +82,23 @@ aspect Analysis {
 
   // --- shouldSendValue ---
   syn boolean TokenEndpointDefinition.shouldSendValue() = isSendTokenEndpointDefinition() && !getToken().getNTA();
+  syn boolean TypeEndpointDefinition.shouldSendValue() = isSendTypeEndpointDefinition() && !getType().getNTA();
 
   // --- needProxyToken ---
   syn boolean TokenComponent.needProxyToken() = !getDependencySourceDefinitionList().isEmpty() || getTokenEndpointDefinitionList().stream().anyMatch(TokenEndpointDefinition::shouldSendValue);
+
+  // --- effectiveUsedAt ---
+  coll Set<EndpointDefinition> MappingDefinition.effectiveUsedAt()
+    [new java.util.HashSet<EndpointDefinition>()]
+    root RagConnect;
+  EndpointDefinition 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 32f13d45b54541d43af1a5417d2daf9082bb5711..8c7213850afc5471c70c215d5462442b7da32326 100644
--- a/ragconnect.base/src/main/jastadd/Configuration.jadd
+++ b/ragconnect.base/src/main/jastadd/Configuration.jadd
@@ -1,8 +1,12 @@
 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.usesJava;
+  public static boolean ASTNode.incrementalOptionActive;
+  public static boolean ASTNode.experimentalJastAdd329;
 }
diff --git a/ragconnect.base/src/main/jastadd/Errors.jrag b/ragconnect.base/src/main/jastadd/Errors.jrag
index 88f3aece6a64041a95f2784be4882d01008ff8c5..1aa3666c2b19bf446667f1a763bfb4824aa44f25 100644
--- a/ragconnect.base/src/main/jastadd/Errors.jrag
+++ b/ragconnect.base/src/main/jastadd/Errors.jrag
@@ -39,10 +39,6 @@ aspect 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();
-
-    DependencyDefinition contributes error("There must be a send endpoint definition targeting " + getSource().parentTypeypeAndName() + " for dependency definition " + getID())
-      when targetEndpointDefinition() == null
-      to RagConnect.errors();
 }
 
 aspect ErrorHelpers {
@@ -54,7 +50,7 @@ aspect ErrorHelpers {
     }
     return false;
   }
-  syn String TokenComponent.parentTypeypeAndName() = containingTypeDecl().getName() + "." + getName();
+  syn String TokenComponent.parentTypeDeclAndName() = containingTypeDecl().getName() + "." + getName();
 }
 
 aspect ErrorMessage {
diff --git a/ragconnect.base/src/main/jastadd/NameResolution.jrag b/ragconnect.base/src/main/jastadd/NameResolution.jrag
index f282ba55983feeae814e12823ff31f1d6480b6a8..3b75bc0eb633830cb0e63c80d5d51e7d04e1b4f0 100644
--- a/ragconnect.base/src/main/jastadd/NameResolution.jrag
+++ b/ragconnect.base/src/main/jastadd/NameResolution.jrag
@@ -2,7 +2,7 @@ aspect NameResolution {
 
   refine RefResolverStubs eq EndpointDefinition.resolveMappingByToken(String id, int position) {
     // return a MappingDefinition
-    for (MappingDefinition mappingDefinition : ragconnect().getMappingDefinitionList()) {
+    for (MappingDefinition mappingDefinition : ragconnect().allMappingDefinitionList()) {
       if (mappingDefinition.getID().equals(id)) {
         return mappingDefinition;
       }
@@ -11,4 +11,36 @@ aspect NameResolution {
     return null;
   }
 
+  refine RefResolverStubs eq ASTNode.globallyResolveTypeComponentByToken(String id) {
+    // return a TypeComponent. 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);
+    TypeDecl type = program().resolveTypeDecl(parentTypeName);
+    // iterate over components and find the matching typeComponent
+    for (Component comp : type.getComponentList()) {
+      if (comp.isTypeComponent() && comp.getName().equals(childTypeName)) {
+        return comp.asTypeComponent();
+      }
+    }
+    System.err.println("Could not resolve TypeComponent '" + id + "'.");
+    return null;
+  }
+
+  refine RefResolverStubs eq ASTNode.globallyResolveComponentByToken(String id) {
+    // return a Component. 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);
+    TypeDecl type = program().resolveTypeDecl(parentTypeName);
+    // iterate over components and find the matching typeComponent
+    for (Component comp : type.getComponentList()) {
+      if (comp.getName().equals(childTypeName)) {
+        return comp;
+      }
+    }
+    System.err.println("Could not resolve Component '" + id + "'.");
+    return null;
+  }
+
 }
diff --git a/ragconnect.base/src/main/jastadd/Navigation.jrag b/ragconnect.base/src/main/jastadd/Navigation.jrag
index 1c2c4af4a40db2ff7ffb0bf962716d515553fd97..d3890f7d22a195127e7688435f9168aa64a37eac 100644
--- a/ragconnect.base/src/main/jastadd/Navigation.jrag
+++ b/ragconnect.base/src/main/jastadd/Navigation.jrag
@@ -1,4 +1,7 @@
-aspect Navigation {
+import java.util.List;
+import java.util.ArrayList;
+
+aspect RagConnectNavigation {
 
   // --- program ---
   eq RagConnect.getChild().program() = getProgram();
@@ -9,15 +12,54 @@ aspect Navigation {
   eq RagConnect.getChild().ragconnect() = this;
   eq MRagConnect.getChild().ragconnect() = getRagConnect();
 
+  // --- containedConnectSpecification ---
+  inh ConnectSpecification ASTNode.containedConnectSpecification();
+  eq RagConnect.getChild().containedConnectSpecification() = null;
+  eq MRagConnect.getChild().containedConnectSpecification() = null;
+  eq Document.getChild().containedConnectSpecification() = null;
+  eq Program.getChild().containedConnectSpecification() = null;
+  eq ConnectSpecification.getChild().containedConnectSpecification() = this;
+
   // --- containedFile
-  eq Grammar.getChild().containedFile() = null;
   eq RagConnect.getChild().containedFile() = null;
   eq MRagConnect.getChild().containedFile() = null;
 
   // --- containedFileName ---
-  eq Grammar.getChild().containedFileName() = null;  // should be in PP
-  eq RagConnect.getChild().containedFileName() = getFileName();
-  eq MRagConnect.getChild().containedFileName() = null;
+  eq ConnectSpecificationFile.containedFileName() = getFileName();
+  refine Navigation eq ASTNode.containedFileName() {
+    if (containedFile() == null) {
+      return containedConnectSpecification().containedFileName();
+    }
+    return refined();
+//    return containedFile().getFileName();
+  }
+
+  //--- allEndpointDefinitionList ---
+  syn List<EndpointDefinition> RagConnect.allEndpointDefinitionList() {
+    List<EndpointDefinition> result = new ArrayList<>();
+    for (var spec : getConnectSpecificationFileList()) {
+      spec.getEndpointDefinitionList().forEach(result::add);
+    }
+    return result;
+  }
+
+  //--- allDependencyDefinitionList ---
+  syn List<DependencyDefinition> RagConnect.allDependencyDefinitionList() {
+    List<DependencyDefinition> result = new ArrayList<>();
+    for (var spec : getConnectSpecificationFileList()) {
+      spec.getDependencyDefinitionList().forEach(result::add);
+    }
+    return result;
+  }
+
+  //--- allMappingDefinitionList ---
+  syn List<MappingDefinition> RagConnect.allMappingDefinitionList() {
+    List<MappingDefinition> result = new ArrayList<>();
+    for (var spec : getConnectSpecificationFileList()) {
+      spec.getMappingDefinitionList().forEach(result::add);
+    }
+    return result;
+  }
 
   // --- isTokenEndpointDefinition ---
   syn boolean EndpointDefinition.isTokenEndpointDefinition() = false;
@@ -27,13 +69,13 @@ aspect Navigation {
   syn TokenEndpointDefinition EndpointDefinition.asTokenEndpointDefinition() = null;
   eq TokenEndpointDefinition.asTokenEndpointDefinition() = this;
 
-  // --- isSendTokenEndpointDefinition ---
-  syn boolean EndpointDefinition.isSendTokenEndpointDefinition() = false;
-  eq SendTokenEndpointDefinition.isSendTokenEndpointDefinition() = true;
+  // --- isTypeEndpointDefinition ---
+  syn boolean EndpointDefinition.isTypeEndpointDefinition() = false;
+  eq TypeEndpointDefinition.isTypeEndpointDefinition() = true;
 
-  // --- asSendTokenEndpointDefinition ---
-  syn SendTokenEndpointDefinition EndpointDefinition.asSendTokenEndpointDefinition() = null;
-  eq SendTokenEndpointDefinition.asSendTokenEndpointDefinition() = this;
+  // --- asTypeEndpointDefinition ---
+  syn TypeEndpointDefinition EndpointDefinition.asTypeEndpointDefinition() = null;
+  eq TypeEndpointDefinition.asTypeEndpointDefinition() = this;
 
   // --- isReceiveTokenEndpointDefinition ---
   syn boolean EndpointDefinition.isReceiveTokenEndpointDefinition() = false;
@@ -43,13 +85,41 @@ aspect Navigation {
   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 SendTokenEndpointDefinition DependencyDefinition.targetEndpointDefinition() {
+  syn EndpointDefinition DependencyDefinition.targetEndpointDefinition() {
     // resolve definition in here, as we do not need resolveMethod in any other place (yet)
-    for (EndpointDefinition endpointDefinition : ragconnect().getEndpointDefinitionList()) {
+    for (EndpointDefinition endpointDefinition : ragconnect().allEndpointDefinitionList()) {
       if (endpointDefinition.isSendTokenEndpointDefinition() &&
           endpointDefinition.asSendTokenEndpointDefinition().getToken().equals(this.getTarget())) {
-        return endpointDefinition.asSendTokenEndpointDefinition();
+        return endpointDefinition;
+      }
+      if (endpointDefinition.isSendTypeEndpointDefinition() &&
+          endpointDefinition.asSendTypeEndpointDefinition().getType().equals(this.getTarget())) {
+        return endpointDefinition;
       }
     }
     return null;
@@ -68,4 +138,7 @@ aspect Navigation {
 
   // --- rootTypeComponents ---
   syn JastAddList<MTypeComponent> MHandler.rootTypeComponents() = mragconnect().getRootTypeComponents();
+
+  // --- isListComponent --- (defined in PP, but only on TypeComponent)
+  syn boolean Component.isListComponent() = false;
 }
diff --git a/ragconnect.base/src/main/jastadd/RagConnect.relast b/ragconnect.base/src/main/jastadd/RagConnect.relast
index 9958106ba82c6efe32126c9f7b795ac7c66378e6..c1cbbb2445e55956f41ee780eab2e216175b6b6f 100644
--- a/ragconnect.base/src/main/jastadd/RagConnect.relast
+++ b/ragconnect.base/src/main/jastadd/RagConnect.relast
@@ -1,4 +1,7 @@
-RagConnect ::= EndpointDefinition* DependencyDefinition* MappingDefinition* Program <FileName> ;
+RagConnect ::= ConnectSpecificationFile* Program ;
+
+abstract ConnectSpecification ::= EndpointDefinition* DependencyDefinition* MappingDefinition* ;
+ConnectSpecificationFile : ConnectSpecification ::= <FileName> ;
 
 abstract EndpointDefinition ::= <AlwaysApply:boolean> ;
 
@@ -10,9 +13,15 @@ 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;
+
 DependencyDefinition ::= <ID>;
 rel DependencyDefinition.Source <-> TokenComponent.DependencySourceDefinition*;
-rel DependencyDefinition.Target -> TokenComponent;
+rel DependencyDefinition.Target -> Component;
 
 MappingDefinition ::= <ID> FromType:MappingDefinitionType <FromVariableName> ToType:MappingDefinitionType <Content> ;
 abstract MappingDefinitionType ::= ;
diff --git a/ragconnect.base/src/main/jastadd/intermediate/Generation.jadd b/ragconnect.base/src/main/jastadd/intermediate/Generation.jadd
index 0c9190a1cfeed156c6353495f327244bef3ed4b0..7a09e6547c1ce7c635e2d2f37f9d7db77248ae68 100644
--- a/ragconnect.base/src/main/jastadd/intermediate/Generation.jadd
+++ b/ragconnect.base/src/main/jastadd/intermediate/Generation.jadd
@@ -4,6 +4,9 @@ Design considerations
 */
 
 aspect AttributesForMustache {
+  // --- EndpointDefinition ---
+  syn String EndpointDefinition.idTokenName() = "InternalRagconnectTopicInList";
+
   // --- MRagConnect ---
   eq MRagConnect.getRootTypeComponent(int i).isFirst() = i == 0;
 
@@ -18,48 +21,115 @@ aspect AttributesForMustache {
   syn String MRagConnect.javaHandlerAttribute() = "_javaHandler";
   syn String MRagConnect.javaHandlerField() = "_javaHandler";
 
+  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 TokenEndpointDefinition MEndpointDefinition.endpointDef();
+  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" + tokenName();
+  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 the token, ensure methods with different names
-    String extra = endpointDef().lookupTokenEndpointDefinitions(token()).size() > 1 ? uniqueSuffix() : "";
-    return "disconnect" + extra + tokenName();
+    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 MSendDefinition.uniqueSuffix() = "Send";
-  eq MReceiveDefinition.uniqueSuffix() = "Receive";
+  eq MTokenSendDefinition.uniqueSuffix() = "Send";
+  eq MTokenReceiveDefinition.uniqueSuffix() = "Receive";
+  eq MTypeSendDefinition.uniqueSuffix() = "Send";
+  eq MTypeReceiveDefinition.uniqueSuffix() = "Receive";
 
-  syn TokenComponent MEndpointDefinition.token() = endpointDef().getToken();
+  // 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 String MEndpointDefinition.parentTypeName() = token().containingTypeDecl().getName();
+  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 (token().isPrimitiveType() && lastDefinition().mappingDef().getToType().isPrimitiveType()) {
+    if (endpointDef().isTokenEndpointDefinition() && token().isPrimitiveType() && lastDefinition().mappingDef().getToType().isPrimitiveType()) {
       return preemptiveExpectedValue() + " == " + lastResult();
     }
-    if (lastDefinition().mappingDef().isDefaultMappingDefinition()) {
+    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";
   }
 
+  // --- 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();
@@ -68,24 +138,50 @@ aspect AttributesForMustache {
   syn MappingDefinition MInnerMappingDefinition.mappingDef() = getMMappingDefinition().getMappingDefinition();
   syn String MInnerMappingDefinition.outputVarName() = "result" + methodName();  // we do not need "_" in between here, because methodName begins with one
 
-  // --- MReceiveDefinition ---
-  eq MReceiveDefinition.preemptiveExpectedValue() = "get" + tokenName() + "()";
-  eq MReceiveDefinition.preemptiveReturn() = "return;";
-  eq MReceiveDefinition.endpointDef() = getReceiveTokenEndpointDefinition();
-  eq MReceiveDefinition.firstInputVarName() = "message";
-
-  // --- MSendDefinition ---
-  eq MSendDefinition.preemptiveExpectedValue() = lastValue();
-  eq MSendDefinition.preemptiveReturn() = "return false;";
-  eq MSendDefinition.endpointDef() = getSendTokenEndpointDefinition();
-  eq MSendDefinition.firstInputVarName() = "get" + tokenName() + "()";
-
-  syn String MSendDefinition.sender() = "_sender_" + tokenName();
-  syn String MSendDefinition.lastValue() = "_lastValue" + tokenName();
-  syn String MSendDefinition.updateMethod() = "_update_" + tokenName();
-  syn String MSendDefinition.writeMethod() = "_writeLastValue_" + tokenName();
-  syn String MSendDefinition.tokenResetMethod() = "get" + tokenName() + "_reset";
-  syn boolean MSendDefinition.shouldSendValue() = endpointDef().shouldSendValue();
+  // --- 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();
+
+  syn String MTokenSendDefinition.sender() = "_sender_" + tokenName();
+  syn String MTokenSendDefinition.lastValue() = "_lastValue" + 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.resolveInListAttributeName() = "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();
+
+  syn String MTypeSendDefinition.sender() = "_sender_" + typeName();
+  syn String MTypeSendDefinition.lastValue() = "_lastValue" + typeName();
+  syn String MTypeSendDefinition.tokenResetMethod() = getterMethod() + "_reset";
+  syn boolean MTypeSendDefinition.shouldSendValue() = endpointDef().asTypeEndpointDefinition().shouldSendValue();
 
   // --- MMappingDefinition ---
   syn String MMappingDefinition.toType() = getMappingDefinition().getToType().prettyPrint();
@@ -93,13 +189,14 @@ aspect AttributesForMustache {
   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 MSendDefinition MDependencyDefinition.targetEndpointDefinition() {
+  syn nta MEndpointDefinition MDependencyDefinition.targetEndpointDefinition() {
     return getDependencyDefinition().targetEndpointDefinition().toMustache();
   }
 
@@ -113,7 +210,7 @@ aspect AttributesForMustache {
   syn String MTokenComponent.javaType() = getTokenComponent().effectiveJavaTypeUse().prettyPrint();
   syn String MTokenComponent.internalName() = getTokenComponent().needProxyToken() ? "_internal_" + name() : externalName();
   syn String MTokenComponent.externalName() = name();
-  syn MSendDefinition MTokenComponent.normalTokenSendDef() {
+  syn MTokenSendDefinition MTokenComponent.normalTokenSendDef() {
     for (TokenEndpointDefinition endpointDef : getTokenComponent().getTokenEndpointDefinitionList()) {
       if (endpointDef.shouldSendValue()) {
         return endpointDef.asSendTokenEndpointDefinition().toMustache();
@@ -126,18 +223,23 @@ aspect AttributesForMustache {
   syn lazy MRagConnect RagConnect.toMustache() {
     MRagConnect result = new MRagConnect();
     result.setRagConnect(this);
-    for (EndpointDefinition def : getEndpointDefinitionList()) {
-      if (def.isSendTokenEndpointDefinition()) {
-        SendTokenEndpointDefinition sendDef = def.asSendTokenEndpointDefinition();
-        result.addSendDefinition(sendDef.toMustache());
+    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 {
-        result.addReceiveDefinition(def.asReceiveTokenEndpointDefinition().toMustache());
+        throw new RuntimeException("Unknown endpoint definition: " + def);
       }
     }
     for (MappingDefinition def : allMappingDefinitions()) {
       result.addMappingDefinition(def.toMustache());
     }
-    for (DependencyDefinition def : getDependencyDefinitionList()) {
+    for (DependencyDefinition def : allDependencyDefinitionList()) {
       result.addDependencyDefinition(def.toMustache());
     }
     for (TokenComponent token : getProgram().allTokenComponents()) {
@@ -168,20 +270,35 @@ aspect AttributesForMustache {
     }
   }
 
-  syn lazy MReceiveDefinition ReceiveTokenEndpointDefinition.toMustache() {
-    MReceiveDefinition result = new MReceiveDefinition();
+  public abstract MEndpointDefinition EndpointDefinition.toMustache();
+  syn lazy MTokenReceiveDefinition ReceiveTokenEndpointDefinition.toMustache() {
+    MTokenReceiveDefinition result = new MTokenReceiveDefinition();
     result.setReceiveTokenEndpointDefinition(this);
     result.addInnerMappings();
     return result;
   }
 
-  syn lazy MSendDefinition SendTokenEndpointDefinition.toMustache() {
-    MSendDefinition result = new MSendDefinition();
+  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);
@@ -244,10 +361,10 @@ aspect AspectGeneration {
   }
 }
 
-aspect RelationGeneration {
+aspect GrammarGeneration {
   syn java.util.List<Relation> RagConnect.additionalRelations() {
     java.util.List<Relation> result = new java.util.ArrayList<>();
-    for (DependencyDefinition dd : getDependencyDefinitionList()) {
+    for (DependencyDefinition dd : allDependencyDefinitionList()) {
       result.add(dd.getRelationToCreate());
     }
     return result;
@@ -265,6 +382,37 @@ aspect RelationGeneration {
     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 {
diff --git a/ragconnect.base/src/main/jastadd/intermediate/Mappings.jrag b/ragconnect.base/src/main/jastadd/intermediate/Mappings.jrag
index 7aa82d304079592d605997484d1348a3d3681875..3a7fce51404e43be679d7b8829127099fd78b683 100644
--- a/ragconnect.base/src/main/jastadd/intermediate/Mappings.jrag
+++ b/ragconnect.base/src/main/jastadd/intermediate/Mappings.jrag
@@ -1,7 +1,7 @@
 aspect DefaultMappings {
 
   private String RagConnect.baseDefaultMappingTypeNamePart(String typeName) {
-    return capitalize(typeName).replace("[]", "s");
+    return capitalize(typeName).replace("[]", "s").replace("<", "").replace(">", "List");
   }
 
   private MappingDefinitionType RagConnect.baseDefaultMappingTypeFromName(String typeName) {
@@ -10,9 +10,9 @@ aspect DefaultMappings {
         new JavaMappingDefinitionType(new SimpleJavaTypeUse(typeName));
   }
 
-  private DefaultMappingDefinition RagConnect.baseDefaultMappingDefinition(String fromTypeName, String toTypeName, String content) {
+  private DefaultMappingDefinition RagConnect.createDefaultMappingDefinition(String prefix, String fromTypeName, String toTypeName, String content) {
     DefaultMappingDefinition result = new DefaultMappingDefinition();
-    result.setID("_Default" + baseDefaultMappingTypeNamePart(fromTypeName) + "To" + baseDefaultMappingTypeNamePart(toTypeName) + "Mapping");
+    result.setID(prefix + baseDefaultMappingTypeNamePart(fromTypeName) + "To" + baseDefaultMappingTypeNamePart(toTypeName) + "Mapping");
     result.setFromType(baseDefaultMappingTypeFromName(fromTypeName));
     result.setFromVariableName("input");
     result.setToType(baseDefaultMappingTypeFromName(toTypeName));
@@ -20,6 +20,16 @@ aspect DefaultMappings {
     return result;
   }
 
+  private DefaultMappingDefinition RagConnect.baseDefaultMappingDefinition(String fromTypeName, String toTypeName, String content) {
+    return createDefaultMappingDefinition("_Default", fromTypeName, toTypeName, content);
+  }
+
+  private DefaultMappingDefinition RagConnect.treeDefaultMappingDefinition(String fromTypeName, String toTypeName, String content) {
+    return createDefaultMappingDefinition("_TreeDefault", fromTypeName, toTypeName, content);
+  }
+
+  syn nta DefaultMappingDefinition RagConnect.defaultBytesToBooleanMapping() = baseDefaultMappingDefinition(
+      "byte[]", "boolean", "return input[0] == (byte) 1;");
   syn nta DefaultMappingDefinition RagConnect.defaultBytesToIntMapping() = baseDefaultMappingDefinition(
       "byte[]", "int", "return java.nio.ByteBuffer.wrap(input).getInt();");
   syn nta DefaultMappingDefinition RagConnect.defaultBytesToShortMapping() = baseDefaultMappingDefinition(
@@ -35,6 +45,52 @@ aspect DefaultMappings {
   syn nta DefaultMappingDefinition RagConnect.defaultBytesToStringMapping() = baseDefaultMappingDefinition(
       "byte[]", "String", "return new String(input);");
 
+  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;"
+    );
+  }
+  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();"
+    );
+  }
+
+  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.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.defaultBooleanToBytesMapping() = baseDefaultMappingDefinition(
+        "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(
@@ -50,6 +106,8 @@ aspect DefaultMappings {
   syn nta DefaultMappingDefinition RagConnect.defaultStringToBytesMapping() = baseDefaultMappingDefinition(
       "String", "byte[]", "return input.getBytes();");
 
+  syn nta DefaultMappingDefinition RagConnect.defaultStringToBooleanMapping() = baseDefaultMappingDefinition(
+      "String", "boolean", "return Boolean.parseBoolean(input);");
   syn nta DefaultMappingDefinition RagConnect.defaultStringToIntMapping() = baseDefaultMappingDefinition(
       "String", "int", "return Integer.parseInt(input);");
   syn nta DefaultMappingDefinition RagConnect.defaultStringToShortMapping() = baseDefaultMappingDefinition(
@@ -63,6 +121,8 @@ aspect DefaultMappings {
   syn nta DefaultMappingDefinition RagConnect.defaultStringToCharMapping() = baseDefaultMappingDefinition(
       "String", "char", "return input.charAt(0);");
 
+  syn nta DefaultMappingDefinition RagConnect.defaultBooleanToStringMapping() = baseDefaultMappingDefinition(
+      "boolean", "String", "return String.valueOf(input);");
   syn nta DefaultMappingDefinition RagConnect.defaultIntToStringMapping() = baseDefaultMappingDefinition(
       "int", "String", "return String.valueOf(input);");
   syn nta DefaultMappingDefinition RagConnect.defaultShortToStringMapping() = baseDefaultMappingDefinition(
@@ -79,50 +139,39 @@ aspect DefaultMappings {
 
 aspect Mappings {
   // --- effectiveMappings ---
-  syn java.util.List<MappingDefinition> EndpointDefinition.effectiveMappings();
-  eq ReceiveTokenEndpointDefinition.effectiveMappings() {
-    // if there is a first mapping, check if it is suitable.
-    //  or if no mappings are specified.
-    // then prepend the suitable default mapping
+  syn java.util.List<MappingDefinition> EndpointDefinition.effectiveMappings() {
     java.util.List<MappingDefinition> result;
-    if (getMappingList().isEmpty() || !hasSuitableEdgeMapping()) {
-      result = new java.util.ArrayList();
-      result.add(suitableDefaultMapping());
-      result.addAll(getMappingList());
+    if (isReceiveTokenEndpointDefinition() || isReceiveTypeEndpointDefinition()) {
+      // 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.add(suitableReceiveDefaultMapping());
+        result.addAll(getMappingList());
+      } else {
+        result = getMappingList();
+      }
+    } else if (isSendTokenEndpointDefinition() || isSendTypeEndpointDefinition()) {
+      // 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.add(suitableSendDefaultMapping());
+      } else {
+        result = getMappingList();
+      }
     } else {
-      result = getMappingList();
+      throw new RuntimeException("Unknown endpoint definition: " + this);
     }
     return result;
   }
-  eq SendTokenEndpointDefinition.effectiveMappings() {
-    // if there is a mapping, check if it is suitable.
-    //  or if no mappings are specified.
-    // then append the suitable default mapping
-    java.util.List<MappingDefinition> result;
-    if (getMappingList().isEmpty() || !hasSuitableEdgeMapping()) {
-      result = new java.util.ArrayList(getMappingList());
-      result.add(suitableDefaultMapping());
-    } else {
-      result = getMappingList();
-    }
-    return result;
-  }
-
-  // --- hasSuitableEdgeMapping ---
-  syn boolean TokenEndpointDefinition.hasSuitableEdgeMapping();
-  eq ReceiveTokenEndpointDefinition.hasSuitableEdgeMapping() = isSuitableEdgeMapping(getMappingList().get(0));
-  eq SendTokenEndpointDefinition.hasSuitableEdgeMapping() = isSuitableEdgeMapping(getMappingList().get(getMappingList().size() - 1));
-
-  // --- isSuitableEdgeMapping(def) ---
-  syn boolean TokenEndpointDefinition.isSuitableEdgeMapping(MappingDefinition def);
-  eq ReceiveTokenEndpointDefinition.isSuitableEdgeMapping(MappingDefinition def) = def.getFromType().isByteArray();
-  eq SendTokenEndpointDefinition.isSuitableEdgeMapping(MappingDefinition def) = def.getToType().isByteArray();
 
   // --- isPrimitiveType ---
   syn boolean TokenComponent.isPrimitiveType() = effectiveJavaTypeUse().isPrimitiveType();
   syn boolean JavaTypeUse.isPrimitiveType() = false;
   eq SimpleJavaTypeUse.isPrimitiveType() {
     switch(getName()) {
+      case "boolean":
       case "int":
       case "short":
       case "long":
@@ -140,13 +189,11 @@ aspect Mappings {
   syn boolean MappingDefinitionType.isArray() = false;
   eq JavaArrayMappingDefinitionType.isArray() = true;
 
-  // --- suitableDefaultMapping ---
-  syn DefaultMappingDefinition EndpointDefinition.suitableDefaultMapping();
-  eq ReceiveTokenEndpointDefinition.suitableDefaultMapping() {
-    String typeName = getMappingList().isEmpty() ?
-        getToken().effectiveJavaTypeUse().getName() :
-        getMappingList().get(0).getFromType().prettyPrint();
-    switch(typeName) {
+  // --- suitableReceiveDefaultMapping ---
+  syn DefaultMappingDefinition EndpointDefinition.suitableReceiveDefaultMapping() {
+    switch (targetTypeName()) {
+      case "boolean":
+      case "Boolean": return ragconnect().defaultBytesToBooleanMapping();
       case "int":
       case "Integer": return ragconnect().defaultBytesToIntMapping();
       case "short":
@@ -160,14 +207,29 @@ aspect Mappings {
       case "char":
       case "Character": return ragconnect().defaultBytesToCharMapping();
       case "String": return ragconnect().defaultBytesToStringMapping();
-      default: return null;
+      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 null;
     }
   }
-  eq SendTokenEndpointDefinition.suitableDefaultMapping() {
-    String typeName = getMappingList().isEmpty() ?
-        getToken().effectiveJavaTypeUse().getName() :
-        getMappingList().get(getMappingList().size() - 1).getFromType().prettyPrint();
-    switch(typeName) {
+  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() {
+    switch (targetTypeName()) {
+      case "boolean":
+      case "Boolean": return ragconnect().defaultBooleanToBytesMapping();
       case "int":
       case "Integer": return ragconnect().defaultIntToBytesMapping();
       case "short":
@@ -181,9 +243,47 @@ aspect Mappings {
       case "char":
       case "Character": return ragconnect().defaultCharToBytesMapping();
       case "String": return ragconnect().defaultStringToBytesMapping();
-      default: return null;
+      default:
+        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);
+        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();
+  }
+
 //  eq ReceiveFromRestDefinition.suitableDefaultMapping() {
 //    String typeName = getMappingList().isEmpty() ?
 //        getToken().getJavaTypeUse().getName() :
@@ -236,8 +336,10 @@ aspect Mappings {
   // --- allMappingDefinitions ---
   syn java.util.List<MappingDefinition> RagConnect.allMappingDefinitions() {
     java.util.List<MappingDefinition> result = new java.util.ArrayList<>();
-    getMappingDefinitionList().iterator().forEachRemaining(result::add);
-    // byte[] conversion
+    // user-defined mappings
+    allMappingDefinitionList().iterator().forEachRemaining(result::add);
+    // byte[] <-> primitive conversion
+    result.add(defaultBytesToBooleanMapping());
     result.add(defaultBytesToIntMapping());
     result.add(defaultBytesToShortMapping());
     result.add(defaultBytesToLongMapping());
@@ -245,6 +347,7 @@ aspect Mappings {
     result.add(defaultBytesToDoubleMapping());
     result.add(defaultBytesToCharMapping());
     result.add(defaultBytesToStringMapping());
+    result.add(defaultBooleanToBytesMapping());
     result.add(defaultIntToBytesMapping());
     result.add(defaultShortToBytesMapping());
     result.add(defaultLongToBytesMapping());
@@ -252,13 +355,22 @@ aspect Mappings {
     result.add(defaultDoubleToBytesMapping());
     result.add(defaultCharToBytesMapping());
     result.add(defaultStringToBytesMapping());
+    // byte[] <-> tree conversion
+    for (TypeDecl typeDecl : getProgram().typeDecls()) {
+      result.add(defaultBytesToTreeMapping(typeDecl.getName()));
+      result.add(defaultTreeToBytesMapping(typeDecl.getName()));
+      result.add(defaultBytesToListTreeMapping(typeDecl.getName()));
+    }
+    result.add(defaultListTreeToBytesMapping());
 //    // string conversion
+//    result.add(defaultStringToBooleanMapping());
 //    result.add(defaultStringToIntMapping());
 //    result.add(defaultStringToShortMapping());
 //    result.add(defaultStringToLongMapping());
 //    result.add(defaultStringToFloatMapping());
 //    result.add(defaultStringToDoubleMapping());
 //    result.add(defaultStringToCharMapping());
+//    result.add(defaultBooleanToStringMapping());
 //    result.add(defaultIntToStringMapping());
 //    result.add(defaultShortToStringMapping());
 //    result.add(defaultLongToStringMapping());
diff --git a/ragconnect.base/src/main/jastadd/intermediate/MustacheNodes.relast b/ragconnect.base/src/main/jastadd/intermediate/MustacheNodes.relast
index 06843177098ba889b665257c9ba2f965f5ecda88..8255cfe5ad9e67e00e53e95b4ddc110a480dce28 100644
--- a/ragconnect.base/src/main/jastadd/intermediate/MustacheNodes.relast
+++ b/ragconnect.base/src/main/jastadd/intermediate/MustacheNodes.relast
@@ -1,7 +1,13 @@
-MRagConnect ::= ReceiveDefinition:MReceiveDefinition* SendDefinition:MSendDefinition* MappingDefinition:MMappingDefinition* DependencyDefinition:MDependencyDefinition* RootTypeComponent:MTypeComponent* TokenComponent:MTokenComponent* Handler:MHandler*;
+MRagConnect ::= TokenReceiveDefinition:MTokenReceiveDefinition* TokenSendDefinition:MTokenSendDefinition* TypeReceiveDefinition:MTypeReceiveDefinition* TypeSendDefinition:MTypeSendDefinition* MappingDefinition:MMappingDefinition* DependencyDefinition:MDependencyDefinition* RootTypeComponent:MTypeComponent* TokenComponent:MTokenComponent* Handler:MHandler*;
+
 abstract MEndpointDefinition ::= InnerMappingDefinition:MInnerMappingDefinition*;
-MReceiveDefinition : MEndpointDefinition;
-MSendDefinition : MEndpointDefinition;
+abstract MTokenEndpointDefinition : MEndpointDefinition;
+MTokenReceiveDefinition : MTokenEndpointDefinition;
+MTokenSendDefinition : MTokenEndpointDefinition;
+abstract MTypeEndpointDefinition : MEndpointDefinition;
+MTypeReceiveDefinition : MTypeEndpointDefinition;
+MTypeSendDefinition : MTypeEndpointDefinition;
+
 MMappingDefinition;
 MInnerMappingDefinition;
 MDependencyDefinition;
@@ -11,8 +17,10 @@ MHandler ::= <ClassName> <Construction> <AttributeName> <FieldName> <InUse:boole
 
 rel MRagConnect.RagConnect -> RagConnect;
 rel MInnerMappingDefinition.MMappingDefinition -> MMappingDefinition;
-rel MReceiveDefinition.ReceiveTokenEndpointDefinition -> ReceiveTokenEndpointDefinition;
-rel MSendDefinition.SendTokenEndpointDefinition -> SendTokenEndpointDefinition;
+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;
diff --git a/ragconnect.base/src/main/jastadd/intermediate2mustache/MustacheNodesToYAML.jrag b/ragconnect.base/src/main/jastadd/intermediate2mustache/MustacheNodesToYAML.jrag
index 4688e9915ddb04a97c7d53b637977f54e1c9b803..bd3afcd607a3be9757ec920251d48080fccd1901 100644
--- a/ragconnect.base/src/main/jastadd/intermediate2mustache/MustacheNodesToYAML.jrag
+++ b/ragconnect.base/src/main/jastadd/intermediate2mustache/MustacheNodesToYAML.jrag
@@ -31,19 +31,33 @@ aspect MustacheNodesToYAML {
     root.put("restHandlerField", restHandlerField());
     root.put("restHandlerAttribute", restHandlerAttribute());
 
-    // ReceiveDefinitions
+    // TokenReceiveDefinitions
     ListElement receiveDefinitions = new ListElement();
-    for (MReceiveDefinition def : getReceiveDefinitionList()) {
+    for (MTokenReceiveDefinition def : getTokenReceiveDefinitionList()) {
       receiveDefinitions.addElement(def.toYAML());
     }
-    root.put("ReceiveDefinitions", receiveDefinitions);
+    root.put("TokenReceiveDefinitions", receiveDefinitions);
 
-    // SendDefinitions
+    // TokenSendDefinitions
     ListElement sendDefinitions = new ListElement();
-    for (MSendDefinition def : getSendDefinitionList()) {
+    for (MTokenSendDefinition def : getTokenSendDefinitionList()) {
       sendDefinitions.addElement(def.toYAML());
     }
-    root.put("SendDefinitions", sendDefinitions);
+    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();
@@ -94,14 +108,33 @@ aspect MustacheNodesToYAML {
     return result;
   }
 
-  syn MappingElement MReceiveDefinition.toYAML() {
+  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 MSendDefinition.toYAML() {
+  syn MappingElement MTypeSendDefinition.toYAML() {
     MappingElement result = super.toYAML();
+    result.put("typeIsList", typeIsList());
     result.put("sender", sender());
     result.put("lastValue", lastValue());
     result.put("loggingEnabledForWrites", loggingEnabledForWrites);
@@ -181,5 +214,5 @@ aspect Navigation {
   eq Document.getChild().program() = null;
   eq Document.getChild().ragconnect() = null;
   eq Document.getChild().containedFile() = null;
-  eq Document.getChild().containedFileName() = getFileName();
+  eq Document.containedFileName() = getFileName();
 }
diff --git a/ragconnect.base/src/main/jastadd/parser/Preamble.parser b/ragconnect.base/src/main/jastadd/parser/Preamble.parser
index d701db19d6edb6d6c616bc79fc0145ea59d2713d..c157ae91f69b0e8d41c40a5bd7426bb2d7fae803 100644
--- a/ragconnect.base/src/main/jastadd/parser/Preamble.parser
+++ b/ragconnect.base/src/main/jastadd/parser/Preamble.parser
@@ -4,4 +4,4 @@ import org.jastadd.ragconnect.ast.*;
 :};
 
 %goal goal;
-%goal ragconnect;
+%goal connect_specification_file;
diff --git a/ragconnect.base/src/main/jastadd/parser/RagConnect.parser b/ragconnect.base/src/main/jastadd/parser/RagConnect.parser
index 0432b0df96c44060f6df1d6918a856f62810230d..a7ba289535a01938c1284ad67f46b2a9e81b9fb5 100644
--- a/ragconnect.base/src/main/jastadd/parser/RagConnect.parser
+++ b/ragconnect.base/src/main/jastadd/parser/RagConnect.parser
@@ -1,9 +1,21 @@
-RagConnect ragconnect
-  = endpoint_definition.d ragconnect.r       {: r.getEndpointDefinitionList().insertChild(d, 0); return r; :}
-  | dependency_definition.d ragconnect.r   {: r.getDependencyDefinitionList().insertChild(d, 0); return r; :}
-  | mapping_definition.d ragconnect.r      {: r.getMappingDefinitionList().insertChild(d, 0); return r; :}
-  | comment ragconnect.r                   {: return r; :}
-  |                                     {: return new RagConnect(); :}
+ConnectSpecificationFile connect_specification_file
+  = endpoint_definition.d connect_specification_file.r
+    {:
+      r.getEndpointDefinitionList().insertChild(d, 0); return r;
+    :}
+  | dependency_definition.d connect_specification_file.r
+    {:
+      r.getDependencyDefinitionList().insertChild(d, 0); return r;
+    :}
+  | mapping_definition.d connect_specification_file.r
+    {:
+      r.getMappingDefinitionList().insertChild(d, 0); return r;
+    :}
+  | comment connect_specification_file.r
+    {:
+      return r;
+    :}
+  | {: return new ConnectSpecificationFile(); :}
 ;
 
 %embed {:
@@ -33,23 +45,58 @@ EndpointDefinition endpoint_definition
 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;
+    :}
 ;
 
 TokenComponent token_ref
   = ID.type_name DOT ID.token_name  {: return TokenComponent.createRef(type_name + "." + token_name); :}
 ;
 
+TypeComponent type_ref
+  = ID.parent_type_name DOT ID.child_type_name  {: return TypeComponent.createRef(parent_type_name + "." + child_type_name); :}
+;
+
 ArrayList string_list
   = ID
   | string_list COMMA ID
 ;
 
 DependencyDefinition dependency_definition
-  = ID.target_type DOT ID.target_token CAN_DEPEND_ON ID.source_type DOT ID.source_token AS ID.id SCOL
+  = ID.target_type DOT ID.target_component CAN_DEPEND_ON ID.source_type DOT ID.source_token AS ID.id SCOL
     {:
       DependencyDefinition result = new DependencyDefinition();
       result.setSource(TokenComponent.createRef(source_type + "." + source_token));
-      result.setTarget(TokenComponent.createRef(target_type + "." + target_token));
+      result.setTarget(Component.createRef(target_type + "." + target_component));
       result.setID(id);
       return result;
     :}
diff --git a/ragconnect.base/src/main/jastadd/scanner/Keywords.flex b/ragconnect.base/src/main/jastadd/scanner/Keywords.flex
index 5773118d8fda8ffdc536b504aa7056b8784ddfb3..69830108bfb0644cd75008b47b58ee149dc3b2ad 100644
--- a/ragconnect.base/src/main/jastadd/scanner/Keywords.flex
+++ b/ragconnect.base/src/main/jastadd/scanner/Keywords.flex
@@ -5,3 +5,7 @@
 "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); }
+"add"        { return sym(Terminals.ADD); }
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 6f8cf3307070db058911b2e98d795293b7b9a1e9..c16c6261b688eb6e784c9c8954578d81f0b8cd24 100644
--- a/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/Compiler.java
+++ b/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/Compiler.java
@@ -3,11 +3,11 @@ package org.jastadd.ragconnect.compiler;
 import beaver.Parser;
 import org.jastadd.option.BooleanOption;
 import org.jastadd.option.ValueOption;
-import org.jastadd.relast.compiler.AbstractCompiler;
-import org.jastadd.relast.compiler.CompilerException;
 import org.jastadd.ragconnect.ast.*;
 import org.jastadd.ragconnect.parser.RagConnectParser;
 import org.jastadd.ragconnect.scanner.RagConnectScanner;
+import org.jastadd.relast.compiler.AbstractCompiler;
+import org.jastadd.relast.compiler.CompilerException;
 
 import java.io.*;
 import java.nio.file.Files;
@@ -15,6 +15,8 @@ import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.nio.file.StandardCopyOption;
 import java.util.*;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 
 public class Compiler extends AbstractCompiler {
 
@@ -25,11 +27,15 @@ public class Compiler extends AbstractCompiler {
   private BooleanOption optionVerbose;
   private BooleanOption optionLogReads;
   private BooleanOption optionLogWrites;
+  private BooleanOption optionLogIncremental;
+  private BooleanOption optionExperimentalJastAdd329;
 
   private static final String OPTION_PROTOCOL_MQTT = "mqtt";
   private static final String OPTION_PROTOCOL_REST = "rest";
   private static final String OPTION_PROTOCOL_JAVA = "java";
 
+  private final static Logger LOGGER = Logger.getLogger(Compiler.class.getName());
+
   public Compiler() {
     super("ragconnect", true);
   }
@@ -44,8 +50,11 @@ public class Compiler extends AbstractCompiler {
       getConfiguration().printHelp(System.out);
       return 0;
     }
+    if (optionVerbose.value()) {
+      LOGGER.setLevel(Level.FINE);
+    }
 
-    printMessage("Running RagConnect " + readVersion());
+    LOGGER.info(() -> "Running RagConnect " + readVersion());
 
     if (!getConfiguration().outputDir().exists()) {
       try {
@@ -74,10 +83,11 @@ public class Compiler extends AbstractCompiler {
     }
     */
     if (!ragConnect.errors().isEmpty()) {
-      System.err.println("Errors:");
+      StringBuilder sb = new StringBuilder("Errors:\n");
       for (ErrorMessage e : ragConnect.errors()) {
-        System.err.println(e);
+        sb.append(e).append("\n");
       }
+      LOGGER.severe(sb::toString);
       System.exit(1);
     }
 
@@ -89,7 +99,7 @@ public class Compiler extends AbstractCompiler {
       return 0;
     }
 
-    printMessage("Writing output files");
+    LOGGER.fine("Writing output files");
     final List<String> handlers = new ArrayList<>();
     if (ASTNode.usesMqtt) {
       handlers.add("MqttHandler.jadd");
@@ -123,12 +133,11 @@ public class Compiler extends AbstractCompiler {
   }
 
   public static void main(String[] args) {
-    System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager");
     System.setProperty("mustache.debug", "true");
     try {
       new Compiler().run(args);
     } catch (CompilerException e) {
-      e.printStackTrace();
+      LOGGER.log(Level.SEVERE, e.getMessage(), e);
       System.exit(1);
     }
   }
@@ -153,12 +162,6 @@ public class Compiler extends AbstractCompiler {
     }
   }
 
-  private void printMessage(String message) {
-    if (optionVerbose.value()) {
-      System.out.println(message);
-    }
-  }
-
   private void writeToFile(Path path, String str) throws CompilerException {
     try (BufferedWriter writer = Files.newBufferedWriter(path)) {
       writer.append(str);
@@ -192,6 +195,12 @@ public class Compiler extends AbstractCompiler {
     optionLogWrites = addOption(
         new BooleanOption("logWrites", "Enable logging for every write.")
         .defaultValue(false));
+    optionLogIncremental = addOption(
+        new BooleanOption("logIncremental", "Enable logging for observer in incremental dependency tracking.")
+            .defaultValue(false));
+    optionExperimentalJastAdd329 = addOption(
+        new BooleanOption("experimental-jastadd-329", "Use trace events INC_FLUSH_START and INC_FLUSH_END (JastAdd issue #329).")
+            .defaultValue(false));
   }
 
   private RagConnect parseProgram(Collection<String> files) throws CompilerException {
@@ -212,64 +221,75 @@ public class Compiler extends AbstractCompiler {
         case "ast":
         case "relast":
           // processGrammar
-          parseGrammar(program, filename);
+          program.addGrammarFile(parseGrammar(filename));
           atLeastOneGrammar = true;
           break;
         case "connect":
         case "ragconnect":
           // process ragConnect
-          RagConnect parsedRagConnect = parseRagConnect(program, filename);
-          mergeRagConnectDefinitions(ragConnect, parsedRagConnect);
+          ragConnect.addConnectSpecificationFile(parseConnectSpec(filename));
           atLeastOneRagConnect = true;
           break;
         default:
-          throw new CompilerException("Unknown file extension in " + filename);
+          throw new CompilerException("Unknown file extension " + extension + " in " + filename);
       }
     }
     if (!atLeastOneGrammar) {
-      System.err.println("No grammar file specified! (*.ast, *.relast)");
+      LOGGER.severe(() -> "No grammar file specified! (*.ast, *.relast)");
     }
     if (!atLeastOneRagConnect) {
-      System.err.println("No ragconnect file specified! (*.connect, *.ragconnect)");
+      LOGGER.severe(() -> "No ragconnect file specified! (*.connect, *.ragconnect)");
     }
     if (!atLeastOneGrammar && !atLeastOneRagConnect) {
       System.exit(1);
     }
+
+    // here, the program subtree is also flushed and resolved
     ragConnect.flushTreeCache();
     ragConnect.treeResolveAll();
+
     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.usesJava = optionProtocols.hasValue(OPTION_PROTOCOL_JAVA);
     ASTNode.usesRest = optionProtocols.hasValue(OPTION_PROTOCOL_REST);
     return ragConnect;
   }
 
-  private void parseGrammar(Program program, String filename) throws CompilerException {
+  private GrammarFile parseGrammar(String filename) throws CompilerException {
     try (BufferedReader reader = Files.newBufferedReader(Paths.get(filename))) {
       RagConnectScanner scanner = new RagConnectScanner(reader);
       RagConnectParser parser = new RagConnectParser();
       GrammarFile grammarFile = (GrammarFile) parser.parse(scanner);
       if (optionVerbose.value()) {
-        grammarFile.dumpTree(System.out);
+        LOGGER.fine(grammarFile::dumpTree);
       }
-      program.addGrammarFile(grammarFile);
-      //grammarFile.treeResolveAll();
       grammarFile.setFileName(toBaseName(filename));
+      return grammarFile;
     } catch (IOException | Parser.Exception e) {
       throw new CompilerException("Could not parse grammar file " + filename, e);
     }
   }
 
-  private RagConnect parseRagConnect(Program program, String filename) throws CompilerException {
+  private ConnectSpecificationFile parseConnectSpec(String filename) throws CompilerException {
     try (BufferedReader reader = Files.newBufferedReader(Paths.get(filename))) {
       RagConnectScanner scanner = new RagConnectScanner(reader);
       RagConnectParser parser = new RagConnectParser();
-      RagConnect ragConnect = (RagConnect) parser.parse(scanner, RagConnectParser.AltGoals.ragconnect);
-      ragConnect.setProgram(program);
-      ragConnect.setFileName(toBaseName(filename));
-      return ragConnect;
+      ConnectSpecificationFile specificationFile = (ConnectSpecificationFile) parser.parse(scanner, RagConnectParser.AltGoals.connect_specification_file);
+      specificationFile.setFileName(toBaseName(filename));
+      return specificationFile;
     } catch (IOException | Parser.Exception e) {
       throw new CompilerException("Could not parse connect file " + filename, e);
     }
@@ -284,12 +304,6 @@ public class Compiler extends AbstractCompiler {
     return new File(filename).getName();
   }
 
-  private void mergeRagConnectDefinitions(RagConnect ragConnect, RagConnect ragConnectToIntegrate) {
-    ragConnect.setEndpointDefinitionList(ragConnectToIntegrate.getEndpointDefinitionList());
-    ragConnect.setMappingDefinitionList(ragConnectToIntegrate.getMappingDefinitionList());
-    ragConnect.setDependencyDefinitionList(ragConnectToIntegrate.getDependencyDefinitionList());
-  }
-
 //  protected void printUsage() {
 //    System.out.println("Usage: java -jar ragconnect.jar [--option1] [--option2=value] ...  <filename1> <filename2> ... ");
 //    System.out.println("Options:");
diff --git a/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/SimpleMain.java b/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/SimpleMain.java
deleted file mode 100644
index 83c282f205af2ff337c3eea7d2b1b7fda8e3cc47..0000000000000000000000000000000000000000
--- a/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/SimpleMain.java
+++ /dev/null
@@ -1,81 +0,0 @@
-package org.jastadd.ragconnect.compiler;
-
-import org.jastadd.ragconnect.ast.Document;
-import org.jastadd.ragconnect.ast.ListElement;
-import org.jastadd.ragconnect.ast.MappingElement;
-
-/**
- * Testing Ros2Rag without parser.
- *
- * @author rschoene - Initial contribution
- */
-public class SimpleMain {
-
-  private static void printManualYAML() {
-    Document doc = new Document();
-    MappingElement root = new MappingElement();
-    MappingElement firstLevel = new MappingElement();
-    firstLevel.put("server", "tcp://localhost:1883");
-    firstLevel.put("robot_speed_factor", ".7");
-
-    MappingElement theTopics = new MappingElement();
-    theTopics.put("robotConfig", "robotconfig");
-    theTopics.put("trajectory", "trajectory");
-    theTopics.put("nextStep", "ros2rag/nextStep");
-    firstLevel.put("topics", theTopics);
-
-    firstLevel.put("zone_size", "0.5");
-
-    ListElement theZones = new ListElement();
-    theZones.add("1 1");
-    theZones.add("0 1");
-    theZones.add("-1 1");
-    firstLevel.put("zones", theZones);
-
-    MappingElement pandaParts = new MappingElement();
-    MappingElement thePanda = new MappingElement();
-    thePanda.put("Link0", "panda_link0");
-    thePanda.put("Link1", "panda_link1");
-    thePanda.put("Link2", "panda_link2");
-    thePanda.put("Link3", "panda_link3");
-    thePanda.put("Link4", "panda_link4");
-    thePanda.put("Link5", "panda_link5");
-    thePanda.put("Link6", "panda_link6");
-    thePanda.put("RightFinger", "panda_rightfinger");
-    thePanda.put("LeftFinger", "panda_leftfinger");
-    pandaParts.put("panda", thePanda);
-    firstLevel.put("parts", pandaParts);
-
-    MappingElement endEffectorParts = new MappingElement();
-    MappingElement endEffector = new MappingElement();
-    endEffector.put("EndEffector", "panda_hand");
-    endEffectorParts.put("panda", endEffector);
-    firstLevel.put("end_effectors", endEffectorParts);
-
-    ListElement theGoalPoses = new ListElement();
-    theGoalPoses.add(makePose("0.4 0.4 0.3"));
-    theGoalPoses.add(makePose("-0.4 0.4 0.3"));
-    theGoalPoses.add(makePose("-0.4 -0.4 0.3"));
-    theGoalPoses.add(makePose("0.4 0.4 0.3"));
-    theGoalPoses.add(makePose("-0.4 0.4 0.3"));
-    theGoalPoses.add(makePose("0.4 0.4 0.3"));
-    firstLevel.put("goal_poses", theGoalPoses);
-
-    root.put("panda_mqtt_connector", firstLevel);
-    doc.setRootElement(root);
-
-    System.out.println(doc.prettyPrint());
-  }
-
-  private static MappingElement makePose(String position) {
-    MappingElement goalPose = new MappingElement();
-    goalPose.put("position", position);
-    goalPose.put("orientation", "1 1 0 0");
-    goalPose.put("work", "20000");
-    return goalPose;
-  }
-
-  public static void main(String[] args) {
-    printManualYAML();
-  }
-}
diff --git a/ragconnect.base/src/main/resources/ListAspect.mustache b/ragconnect.base/src/main/resources/ListAspect.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..764b5441862c96a010f6d07f86e2c1aad2dec81c
--- /dev/null
+++ b/ragconnect.base/src/main/resources/ListAspect.mustache
@@ -0,0 +1,24 @@
+{{#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.jadd
index f5e6ba99b3058f59f6b9b40ae0d9c07f18da88c3..a8f065e818c32874703d7f2775c589492fb6e8ee 100644
--- a/ragconnect.base/src/main/resources/MqttHandler.jadd
+++ b/ragconnect.base/src/main/resources/MqttHandler.jadd
@@ -1,10 +1,10 @@
-import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
-
-aspect MqttHandler {
+import java.util.function.BiConsumer;aspect MqttHandler {
 public class MqttServerHandler {
   private final java.util.Map<String, MqttHandler> handlers = new java.util.HashMap<>();
-  private final java.util.Map<ConnectToken, Object> tokensForRemoval = new java.util.HashMap<>();
+  private final java.util.Map<ConnectToken, java.util.function.BiConsumer<String, byte[]>> tokensForRemoval = new java.util.HashMap<>();
   private long time;
   private java.util.concurrent.TimeUnit unit;
   private String name;
@@ -15,7 +15,7 @@ public class MqttServerHandler {
 
   public MqttServerHandler(String name) {
     this.name = name;
-    setupWaitUntilReady(1, TimeUnit.SECONDS);
+    setupWaitUntilReady(1, java.util.concurrent.TimeUnit.SECONDS);
   }
 
   public void setupWaitUntilReady(long time, java.util.concurrent.TimeUnit unit) {
@@ -23,7 +23,7 @@ public class MqttServerHandler {
     this.unit = unit;
   }
 
-  public MqttHandler resolveHandler(java.net.URI uri) throws IOException {
+  public MqttHandler resolveHandler(java.net.URI uri) throws java.io.IOException {
     MqttHandler handler = handlers.get(uri.getHost());
     if (handler == null) {
       // first connect to that server
@@ -39,33 +39,37 @@ public class MqttServerHandler {
     return handler;
   }
 
-  public ConnectToken newConnection(java.net.URI uri, java.util.function.Consumer<byte[]> callback) throws IOException {
+  public ConnectToken newConnection(java.net.URI uri, java.util.function.BiConsumer<String, byte[]> callback) throws java.io.IOException {
     ConnectToken connectToken = new ConnectToken(uri);
     resolveHandler(uri).newConnection(extractTopic(uri), callback);
     tokensForRemoval.put(connectToken, callback);
     return connectToken;
   }
 
-  public boolean disconnect(ConnectToken connectToken) throws IOException {
+  public boolean disconnect(ConnectToken connectToken) throws java.io.IOException {
     MqttHandler handler = resolveHandler(connectToken.uri);
     return handler != null ? handler.disconnect(extractTopic(connectToken.uri), tokensForRemoval.get(connectToken)) : false;
   }
 
-  public void publish(java.net.URI uri, byte[] bytes) throws IOException {
+  public void publish(java.net.URI uri, byte[] bytes) throws java.io.IOException {
     resolveHandler(uri).publish(extractTopic(uri), bytes);
   }
 
-  public void publish(java.net.URI uri, byte[] bytes, boolean retain) throws IOException {
+  public void publish(java.net.URI uri, byte[] bytes, boolean retain) throws java.io.IOException {
     resolveHandler(uri).publish(extractTopic(uri), bytes, retain);
   }
 
   public void publish(java.net.URI uri, byte[] bytes,
-                      org.fusesource.mqtt.client.QoS qos, boolean retain) throws IOException {
+                      org.fusesource.mqtt.client.QoS qos, boolean retain) throws java.io.IOException {
     resolveHandler(uri).publish(extractTopic(uri), bytes, qos, retain);
   }
 
   public static String extractTopic(java.net.URI uri) {
     String path = uri.getPath();
+    if (uri.getFragment() != null) {
+      // do not also append fragment, as it is illegal, that anything follows "#" in a mqtt topic anyway
+      path += "#";
+    }
     if (path.charAt(0) == '/') {
       path = path.substring(1);
     }
@@ -99,7 +103,8 @@ public class MqttHandler {
   private boolean sendWelcomeMessage = true;
   private org.fusesource.mqtt.client.QoS qos;
   /** Dispatch knowledge */
-  private final java.util.Map<String, java.util.List<java.util.function.Consumer<byte[]>>> callbacks;
+  private final java.util.Map<String, java.util.List<java.util.function.BiConsumer<String, byte[]>>> normalCallbacks;
+  private final java.util.Map<java.util.regex.Pattern, java.util.List<java.util.function.BiConsumer<String, byte[]>>> wildcardCallbacks;
 
   public MqttHandler() {
     this("RagConnect");
@@ -108,7 +113,8 @@ 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.callbacks = new java.util.HashMap<>();
+    this.normalCallbacks = new java.util.HashMap<>();
+    this.wildcardCallbacks = new java.util.HashMap<>();
     this.readyLatch = new java.util.concurrent.CountDownLatch(1);
     this.qos = org.fusesource.mqtt.client.QoS.AT_LEAST_ONCE;
   }
@@ -119,17 +125,23 @@ public class MqttHandler {
   }
 
   /**
-   * Sets the host (with default port) to receive messages from, and connects to it.
-   * @throws IOException if could not connect, or could not subscribe to a topic
+   * Sets the host to receive messages from, and connects to it.
+   * @param host name of the host to connect to, format is either <code>"$name"</code> or <code>"$name:$port"</code>
+   * @throws java.io.IOException if could not connect, or could not subscribe to a topic
    * @return self
    */
   public MqttHandler setHost(String host) throws java.io.IOException {
+    if (host.contains(":")) {
+      int colon_index = host.indexOf(":");
+      return setHost(host.substring(0, colon_index),
+          Integer.parseInt(host.substring(colon_index + 1)));
+    }
     return setHost(host, DEFAULT_PORT);
   }
 
   /**
    * Sets the host to receive messages from, and connects to it.
-   * @throws IOException if could not connect, or could not subscribe to a topic
+   * @throws java.io.IOException if could not connect, or could not subscribe to a topic
    * @return self
    */
   public MqttHandler setHost(String host, int port) throws java.io.IOException {
@@ -160,13 +172,17 @@ public class MqttHandler {
                             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();
-        java.util.List<java.util.function.Consumer<byte[]>> callbackList = callbacks.get(topicString);
-        if (callbackList == null || callbackList.isEmpty()) {
-          logger.debug("Got a message, but no callback to call. Forgot to subscribe?");
+        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);
         } else {
           byte[] message = body.toByteArray();
-          for (java.util.function.Consumer<byte[]> callback : callbackList) {
-            callback.accept(message);
+          for (java.util.function.BiConsumer<String, byte[]> callback : callbackList) {
+            try {
+              callback.accept(topicString, message);
+            } catch (Exception e) {
+              logger.catching(e);
+            }
           }
         }
         ack.onSuccess(null);  // always acknowledge message
@@ -188,20 +204,20 @@ public class MqttHandler {
     throwIf(error);
 
     // actually establish the connection
-    connection.connect(new org.fusesource.mqtt.client.Callback<Void>() {
+    connection.connect(new org.fusesource.mqtt.client.Callback<>() {
       @Override
       public void onSuccess(Void value) {
         if (MqttHandler.this.sendWelcomeMessage) {
           connection.publish("components",
-                             (name + " is connected").getBytes(),
-                             org.fusesource.mqtt.client.QoS.AT_LEAST_ONCE,
-                             false,
-                             new org.fusesource.mqtt.client.Callback<Void>() {
-            @Override
-            public void onSuccess(Void value) {
-              logger.debug("success sending welcome message");
-              setReady();
-            }
+              (name + " is connected").getBytes(),
+              org.fusesource.mqtt.client.QoS.AT_LEAST_ONCE,
+              false,
+              new org.fusesource.mqtt.client.Callback<>() {
+                @Override
+                public void onSuccess(Void value) {
+                  logger.debug("success sending welcome message");
+                  setReady();
+                }
 
             @Override
             public void onFailure(Throwable value) {
@@ -222,6 +238,20 @@ public class MqttHandler {
     return this;
   }
 
+  private java.util.List<java.util.function.BiConsumer<String, byte[]>> callbacksFor(String topicString) {
+    java.util.List<java.util.function.BiConsumer<String, byte[]>> result = new java.util.ArrayList<>();
+    List<BiConsumer<String, byte[]>> normalCallbackList = normalCallbacks.get(topicString);
+    if (normalCallbackList != null) {
+      result.addAll(normalCallbackList);
+    }
+    wildcardCallbacks.forEach((topicPattern, callback) -> {
+      if (topicPattern.matcher(topicString).matches()) {
+        result.addAll(callback);
+      }
+    });
+    return result;
+  }
+
   public java.net.URI getHost() {
     return host;
   }
@@ -240,61 +270,147 @@ public class MqttHandler {
     this.qos = qos;
   }
 
+  /**
+   * Establish a new connection for some topic.
+   * @param topic    the topic to create a connection for, may contain the wildcards "*" and "#"
+   * @param callback the callback to run if a new message arrives for this topic
+   * @return true if successful stored this connection, false otherwise (e.g., on failed subscribe)
+   */
   public boolean newConnection(String topic, java.util.function.Consumer<byte[]> callback) {
+    return newConnection(topic, (ignoredTopicString, bytes) -> callback.accept(bytes));
+  }
+
+  /**
+   * Establish a new connection for some topic.
+   * @param topic    the topic to create a connection for, may contain the wildcards "*" and "#"
+   * @param callback the callback to run if a new message arrives for this topic
+   * @return true if successful stored this connection, false otherwise (e.g., on failed subscribe)
+   */
+  public boolean newConnection(String topic, java.util.function.BiConsumer<String, byte[]> callback) {
     if (readyLatch.getCount() > 0) {
       System.err.println("Handler not ready");
       return false;
     }
     // register callback
     logger.debug("new connection for {}", topic);
-    if (callbacks.get(topic) == null) {
-      callbacks.put(topic, new java.util.ArrayList<>());
-
+    final boolean needSubscribe;
+    if (isWildcardTopic(topic)) {
+      String regexForTopic = topic.replace("*", "[^/]*").replace("#", ".*");
+      java.util.regex.Pattern pattern = java.util.regex.Pattern.compile(regexForTopic);
+      wildcardCallbacks.computeIfAbsent(pattern, p -> new java.util.ArrayList<>())
+                       .add(callback);
+      needSubscribe = true;
+    } else { // normal topic
+      java.util.List<java.util.function.BiConsumer<String, byte[]>> callbacksForTopic = normalCallbacks.get(topic);
+      if (callbacksForTopic == null || callbacksForTopic.isEmpty()) {
+        callbacksForTopic = new java.util.ArrayList<>();
+        normalCallbacks.put(topic, callbacksForTopic);
+        needSubscribe = true;
+      } else {
+        needSubscribe = false;
+      }
+      callbacksForTopic.add(callback);
+    }
+    if (needSubscribe) {
       // subscribe at broker
+      CountDownLatch operationFinished = new CountDownLatch(1);
+      java.util.concurrent.atomic.AtomicReference<Boolean> success = new java.util.concurrent.atomic.AtomicReference<>(true);
       org.fusesource.mqtt.client.Topic[] topicArray = { new org.fusesource.mqtt.client.Topic(topic, this.qos) };
       connection.getDispatchQueue().execute(() -> {
-        connection.subscribe(topicArray, new org.fusesource.mqtt.client.Callback<byte[]>() {
+        connection.subscribe(topicArray, new org.fusesource.mqtt.client.Callback<>() {
           @Override
           public void onSuccess(byte[] qoses) {
             logger.debug("Subscribed to {}, qoses: {}", topic, qoses);
+            operationFinished.countDown();
           }
 
           @Override
           public void onFailure(Throwable cause) {
             logger.error("Could not subscribe to {}", topic, cause);
+            success.set(false);
+            operationFinished.countDown();
           }
         });
       });
+      try {
+        operationFinished.await(2, TimeUnit.SECONDS);
+        return success.get();
+      } catch (InterruptedException e) {
+        return false;
+      }
+    } else {
+      return true;
     }
-    callbacks.get(topic).add(callback);
-    return true;
   }
 
-  public boolean disconnect(String topic, Object callback) {
-    java.util.List<java.util.function.Consumer<byte[]>> callbackList = callbacks.get(topic);
-    if (callbackList == null) {
+  private boolean isWildcardTopic(String topic) {
+    return topic.contains("*") || topic.contains("#");
+  }
+
+  public boolean disconnect(String topic, java.util.function.BiConsumer<String, byte[]> callback) {
+    boolean needUnsubscribe = false;
+    java.util.concurrent.atomic.AtomicReference<Boolean> success = new java.util.concurrent.atomic.AtomicReference<>(true);
+
+    boolean foundTopicInCallbacks = false;
+
+    // check if wildcard is to be removed
+    if (isWildcardTopic(topic)) {
+      java.util.regex.Pattern wildcardPatternToRemove = null;
+      for (java.util.Map.Entry<java.util.regex.Pattern, java.util.List<java.util.function.BiConsumer<String, byte[]>>> entry : wildcardCallbacks.entrySet()) {
+        if (entry.getKey().pattern().equals(topic)) {
+          foundTopicInCallbacks = true;
+          // if still successful, update with whether callback could be removed
+          success.compareAndSet(true, (entry.getValue().remove(callback)));
+          if (entry.getValue().isEmpty()) {
+            wildcardPatternToRemove = entry.getKey();
+            needUnsubscribe = true;
+          }
+          break;
+        }
+      }
+      ;
+      if (wildcardPatternToRemove != null) {
+        wildcardCallbacks.remove(wildcardPatternToRemove);
+      }
+    } else if (normalCallbacks.containsKey(topic)) {
+      foundTopicInCallbacks = true;
+      // if still successful, update with whether callback could be removed
+      var normalCallbackList = normalCallbacks.get(topic);
+      success.compareAndSet(true, normalCallbackList.remove(callback));
+      needUnsubscribe |= normalCallbackList.isEmpty();
+    }
+
+    if (!foundTopicInCallbacks) {
       logger.warn("Disconnect for not connected topic '{}'", topic);
       return false;
     }
-    java.util.concurrent.atomic.AtomicReference<Boolean> success = new java.util.concurrent.atomic.AtomicReference<>();
-    success.set(callbackList.remove(callback));
-    if (callbackList.isEmpty()) {
+
+    if (needUnsubscribe) {
+      java.util.concurrent.CountDownLatch operationFinished = new java.util.concurrent.CountDownLatch(1);
       // no callbacks anymore for this topic, unsubscribe from mqtt
       connection.getDispatchQueue().execute(() -> {
         org.fusesource.hawtbuf.UTF8Buffer topicBuffer = org.fusesource.hawtbuf.Buffer.utf8(topic);
         org.fusesource.hawtbuf.UTF8Buffer[] topicArray = new org.fusesource.hawtbuf.UTF8Buffer[]{topicBuffer};
-        connection.unsubscribe(topicArray, new org.fusesource.mqtt.client.Callback<Void>() {
+        connection.unsubscribe(topicArray, new org.fusesource.mqtt.client.Callback<>() {
           @Override
           public void onSuccess(Void value) {
-            // empty, all good
+            operationFinished.countDown();
           }
 
           @Override
           public void onFailure(Throwable cause) {
             success.set(false);
+            logger.warn("Could not disconnect from {}", topic, cause);
+            operationFinished.countDown();
           }
         });
       });
+      try {
+        operationFinished.await(2, java.util.concurrent.TimeUnit.SECONDS);
+      } catch (InterruptedException e) {
+        logger.catching(e);
+        success.set(false);
+      }
     }
     return success.get();
   }
@@ -323,7 +439,7 @@ public class MqttHandler {
       return;
     }
     connection.getDispatchQueue().execute(() -> {
-      connection.disconnect(new org.fusesource.mqtt.client.Callback<Void>() {
+      connection.disconnect(new org.fusesource.mqtt.client.Callback<>() {
         @Override
         public void onSuccess(Void value) {
           logger.info("Disconnected {} from {}", name, host);
@@ -347,7 +463,7 @@ public class MqttHandler {
 
   public void publish(String topic, byte[] bytes, org.fusesource.mqtt.client.QoS qos, boolean retain) {
     connection.getDispatchQueue().execute(() -> {
-      connection.publish(topic, bytes, qos, retain, new org.fusesource.mqtt.client.Callback<Void>() {
+      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);
diff --git a/ragconnect.base/src/main/resources/handleUri.mustache b/ragconnect.base/src/main/resources/handleUri.mustache
index 1a20e7c4df2a4c54ccba4c825fff55ca89a51dec..ff0ea7af7f920ac09a14de75a4afe1882540ac94 100644
--- a/ragconnect.base/src/main/resources/handleUri.mustache
+++ b/ragconnect.base/src/main/resources/handleUri.mustache
@@ -1,11 +1,23 @@
-String scheme,host, path;
+String scheme, host, path;
 java.net.URI uri;
 try {
   uri = new java.net.URI({{connectParameterName}});
   scheme = uri.getScheme();
   host = uri.getHost();
-  path = uri.getPath();
+  path = uri.getPath() + (uri.getFragment() != null ? "#" : "");
 } catch (java.net.URISyntaxException e) {
   System.err.println(e.getMessage());  // Maybe re-throw error?
   return false;
 }
+if (scheme == null || scheme.isBlank()) {
+  System.err.println("Missing or empty scheme in " + uri);
+  return false;
+}
+if (host == null || host.isBlank()) {
+  System.err.println("Missing or empty host in " + uri);
+  return false;
+}
+if (path == null || path.isBlank()) {
+  System.err.println("Missing or empty path in " + uri);
+  return false;
+}
diff --git a/ragconnect.base/src/main/resources/log4j2.xml b/ragconnect.base/src/main/resources/log4j2.xml
deleted file mode 100644
index 98cfd73c75df58d8598521bc10b043e214ec4ad8..0000000000000000000000000000000000000000
--- a/ragconnect.base/src/main/resources/log4j2.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<Configuration status="INFO">
-    <Appenders>
-        <Console name="Console" target="SYSTEM_OUT">
-            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
-        </Console>
-    </Appenders>
-    <Loggers>
-        <Root level="info">
-            <AppenderRef ref="Console"/>
-        </Root>
-    </Loggers>
-</Configuration>
\ No newline at end of file
diff --git a/ragconnect.base/src/main/resources/mappingApplication.mustache b/ragconnect.base/src/main/resources/mappingApplication.mustache
index 064004b94886c155be883e7b186557c6fa87d38c..bc59e0b318dcd430b5f21fd0a67d157b35a4bb7c 100644
--- a/ragconnect.base/src/main/resources/mappingApplication.mustache
+++ b/ragconnect.base/src/main/resources/mappingApplication.mustache
@@ -1,8 +1,11 @@
-{{lastDefinitionToType}} {{lastResult}};
+{{{lastDefinitionToType}}} {{lastResult}};
 try {
   {{#InnerMappingDefinitions}}
-  {{^last}}{{toType}} {{/last}}{{outputVarName}} = {{methodName}}({{inputVarName}});
+  {{^last}}{{{toType}}} {{/last}}{{outputVarName}} = {{methodName}}({{inputVarName}});
   {{/InnerMappingDefinitions}}
+} catch (RagConnectRejectMappingException e) {
+  // do not print message in case of rejection
+  {{preemptiveReturn}}
 } catch (Exception e) {
   e.printStackTrace();
   {{preemptiveReturn}}
diff --git a/ragconnect.base/src/main/resources/mappingDefinition.mustache b/ragconnect.base/src/main/resources/mappingDefinition.mustache
index fe26e949b7a8e446bf97947e1632624741a83726..09f161f37c3742850b09969d4eb85c7798d2d346 100644
--- a/ragconnect.base/src/main/resources/mappingDefinition.mustache
+++ b/ragconnect.base/src/main/resources/mappingDefinition.mustache
@@ -1,3 +1,3 @@
-protected static {{toType}} ASTNode.{{methodName}}({{fromType}} {{fromVariableName}}) throws Exception {
-  {{{content}}}{{!maybe print line by line to get better indentation}}
+protected static {{{toType}}} ASTNode.{{methodName}}({{{fromType}}} {{fromVariableName}}) throws Exception {
+  {{{content}}}
 }
diff --git a/ragconnect.base/src/main/resources/ragConnectVersion.properties b/ragconnect.base/src/main/resources/ragConnectVersion.properties
index 266543a066a77220bc13c577f38b08368a2f4d40..7e24342af2954e314a43f78b900f8921a74678a8 100644
--- a/ragconnect.base/src/main/resources/ragConnectVersion.properties
+++ b/ragconnect.base/src/main/resources/ragConnectVersion.properties
@@ -1,2 +1,2 @@
-#Tue Jan 19 12:08:02 CET 2021
-version=0.2.5
+#Thu Jun 24 17:13:44 CEST 2021
+version=0.3.2-alpha
diff --git a/ragconnect.base/src/main/resources/ragconnect.mustache b/ragconnect.base/src/main/resources/ragconnect.mustache
index 49d1721cbf0cf0131fe2248fe4e736906448bccf..c08529a22bfacc7ec5caa3e6e6569e51bc5182a1 100644
--- a/ragconnect.base/src/main/resources/ragconnect.mustache
+++ b/ragconnect.base/src/main/resources/ragconnect.mustache
@@ -1,16 +1,31 @@
 {{#usesMqtt}}{{> mqtt}}{{/usesMqtt}}
 {{> handler}}
-aspect ROS2RAG {
-  {{#ReceiveDefinitions}}
+aspect RagConnect {
+  {{#TokenReceiveDefinitions}}
   {{> receiveDefinition}}
-  {{/ReceiveDefinitions}}
+  {{/TokenReceiveDefinitions}}
 
-  {{#SendDefinitions}}
+  {{#TokenSendDefinitions}}
   {{> sendDefinition}}
-  {{/SendDefinitions}}
+  {{/TokenSendDefinitions}}
+
+  {{#TypeReceiveDefinitions}}
+  {{> receiveDefinition}}
+  {{/TypeReceiveDefinitions}}
+
+  {{#TypeSendDefinitions}}
+  {{> sendDefinition}}
+  {{/TypeSendDefinitions}}
+
+  class RagConnectRejectMappingException extends RuntimeException {}
+  private static void ASTNode.reject() {
+    throw new RagConnectRejectMappingException();
+  }
 
   {{#MappingDefinitions}}
+  {{#isUsed}}
   {{> mappingDefinition}}
+  {{/isUsed}}
   {{/MappingDefinitions}}
 
   {{#DependencyDefinitions}}
@@ -20,4 +35,144 @@ aspect ROS2RAG {
   {{#TokenComponents}}
   {{> tokenComponent}}
   {{/TokenComponents}}
+
+  {{> ListAspect}}
+
+  public void {{rootNodeName}}.ragconnectCheckIncremental() {
+  {{#incrementalOptionActive}}
+    // check if --tracing is active
+    trace().getReceiver();
+    // check if tracing of INC_FLUSH_ATTR is possible, i.e., if --tracing=flush
+    ASTState.Trace.Event checkTracing = ASTState.Trace.Event.INC_FLUSH_ATTR;
+    // check if --rewrite is active
+    mayHaveRewrite();
+    // check if --incremental is active
+    Object checkIncremental = inc_throwAway_visited;
+  {{/incrementalOptionActive}}
+  }
+}
+
+{{#incrementalOptionActive}}
+aspect RagConnectObserver {
+
+  class RagConnectObserver implements ASTState.Trace.Receiver {
+
+    class RagConnectObserverEntry {
+      final ConnectToken connectToken;
+      final ASTNode node;
+      final String attributeString;
+      final Runnable attributeCall;
+
+      RagConnectObserverEntry(ConnectToken connectToken, ASTNode node, String attributeString, Runnable attributeCall) {
+        this.connectToken = connectToken;
+        this.node = node;
+        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(ConnectToken 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(ConnectToken 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}}
diff --git a/ragconnect.base/src/main/resources/receiveDefinition.mustache b/ragconnect.base/src/main/resources/receiveDefinition.mustache
index 6acddb20204a43b3fac6d4287eaa5f5db7c40246..9a8e309964319ee768879aa9f0c4b6a6b9d316a4 100644
--- a/ragconnect.base/src/main/resources/receiveDefinition.mustache
+++ b/ragconnect.base/src/main/resources/receiveDefinition.mustache
@@ -1,12 +1,104 @@
+{{#typeIsList}}
+{{^UseList}}
+/* first try with resolve to type
+syn {{typeName}} {{parentTypeName}}.{{resolveInListAttributeName}}(String topic) {
+  for ({{typeName}} element : get{{entityName}}()) {
+    if (element.get{{idTokenName}}().equals(topic)) {
+      return element;
+    }
+  }
+  return null;
+}
+*/
+syn int {{parentTypeName}}.{{resolveInListAttributeName}}(String topic) {
+  for (int index = 0; index < getNum{{entityName}}(); index++) {
+    if (get{{entityName}}(index).get{{idTokenName}}().equals(topic)) {
+      return index;
+    }
+  }
+  return -1;
+}
+{{/UseList}}
+{{/typeIsList}}
+
+/**
+ * Connects the receive endpoint {{entityName}}.
+{{#typeIsList}}{{#isWithAdd}}
+ * New values are appended to the end of the list.
+{{/isWithAdd}}{{/typeIsList}}
+ * @param {{connectParameterName}} string describing protocol and path as an URI
+{{#typeIsList}}{{^UseList}}{{^isWithAdd}}
+ * @param index index of node in list to connect (the list is expected to have enough elements)
+{{/isWithAdd}}{{/UseList}}{{/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 {
+  java.util.function.BiConsumer<String, byte[]> consumer = (topic, message) -> {
+    {{> mappingApplication}}
+{{#loggingEnabledForReads}}
+    System.out.println("[Receive] " + {{connectParameterName}} + " -> {{entityName}} = " + {{lastResult}});
+{{/loggingEnabledForReads}}
+{{#isTypeEndpointDefinition}}
+    {{lastResult}}.treeResolveAll();
+ {{#typeIsList}}
+  {{#UseList}}
+   {{#isWithAdd}}
+    {{getterMethod}}().addAll({{lastResult}});
+   {{/isWithAdd}}
+   {{^isWithAdd}}
+    set{{entityName}}({{lastResult}});
+   {{/isWithAdd}}
+  {{/UseList}}
+  {{^UseList}}
+    {{lastResult}}.set{{idTokenName}}(topic);
+   {{#isWithAdd}}
+    {{getterMethod}}().add({{lastResult}});
+   {{/isWithAdd}}
+   {{^isWithAdd}}
+    set{{entityName}}({{lastResult}}, index);
+   {{/isWithAdd}}
+  {{/UseList}}
+ {{/typeIsList}}
+ {{^typeIsList}}
+    set{{entityName}}({{lastResult}});
+ {{/typeIsList}}
+{{/isTypeEndpointDefinition}}
+{{^isTypeEndpointDefinition}}
+    set{{entityName}}({{lastResult}});
+{{/isTypeEndpointDefinition}}
+  };
+  return {{internalConnectMethod}}({{connectParameterName}}, consumer);
+}
+
+{{#typeIsList}}{{^UseList}}{{^isWithAdd}}
+/**
+ * Connects the receive endpoint {{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 {
-  {{>handleUri}}
-  java.util.function.Consumer<byte[]> consumer = message -> {
+  java.util.function.BiConsumer<String, byte[]> consumer = (topic, message) -> {
     {{> mappingApplication}}
-    {{#loggingEnabledForReads}}
-    System.out.println("[Receive] " + {{connectParameterName}} + " -> {{tokenName}} = " + {{lastResult}});
-    {{/loggingEnabledForReads}}
-    set{{tokenName}}({{lastResult}});
+{{#loggingEnabledForReads}}
+    System.out.println("[Receive] " + {{connectParameterName}} + " (" + topic + ") -> {{entityName}} = " + {{lastResult}});
+{{/loggingEnabledForReads}}
+    {{lastResult}}.set{{idTokenName}}(topic);
+    int resolvedIndex = {{resolveInListAttributeName}}(topic);
+    if (resolvedIndex == -1) {
+      add{{entityName}}({{lastResult}});
+    } else {
+      set{{entityName}}({{lastResult}}, resolvedIndex);
+    }
   };
+  return {{internalConnectMethod}}({{connectParameterName}}, consumer);
+}
+{{/isWithAdd}}{{/UseList}}{{/typeIsList}}
+
+private boolean {{parentTypeName}}.{{internalConnectMethod}}(String {{connectParameterName}},
+    java.util.function.BiConsumer<String, byte[]> consumer) throws java.io.IOException {
+  {{>handleUri}}
   ConnectToken connectToken;
   switch (scheme) {
   {{#usesMqtt}}
@@ -29,7 +121,8 @@ public boolean {{parentTypeName}}.{{connectMethod}}(String {{connectParameterNam
   {{#usesRest}}
     case "rest":
       connectToken = {{restHandlerAttribute}}().newPUTConnection(uri, input -> {
-        consumer.accept(input.getBytes());
+        // TODO wildcard-topic not supported yet
+        consumer.accept("", input.getBytes());
       });
       if (connectToken == null) {
         return false;
diff --git a/ragconnect.base/src/main/resources/sendDefinition.mustache b/ragconnect.base/src/main/resources/sendDefinition.mustache
index 5ddec008088bb8ea855dab43f5a67c5117ee3950..bf05fadf4c4c8281aea696425b2982617eec2029 100644
--- a/ragconnect.base/src/main/resources/sendDefinition.mustache
+++ b/ragconnect.base/src/main/resources/sendDefinition.mustache
@@ -3,6 +3,12 @@ private byte[] {{parentTypeName}}.{{lastValue}} = null;
 
 public boolean {{parentTypeName}}.{{connectMethod}}(String {{connectParameterName}}, boolean writeCurrentValue) throws java.io.IOException {
   {{>handleUri}}
+  ConnectToken connectToken;
+  if (connectTokens.computeIfAbsent(this, astNode -> new java.util.HashMap<java.net.URI, ConnectToken>())
+                   .get(uri) != null) {
+    System.err.println("Already connected for " + uri + " on " + this + "!");
+    return true;
+  }
   switch (scheme) {
   {{#usesMqtt}}
     case "mqtt":
@@ -10,7 +16,7 @@ public boolean {{parentTypeName}}.{{connectMethod}}(String {{connectParameterNam
       final String topic = {{mqttHandlerAttribute}}().extractTopic(uri);
       {{sender}} = () -> {
         {{#loggingEnabledForWrites}}
-        System.out.println("[Send] {{tokenName}} = " + get{{tokenName}}() + " -> " + {{connectParameterName}});
+        System.out.println("[Send] {{entityName}} = " + {{getterMethod}}() + " -> " + {{connectParameterName}});
         {{/loggingEnabledForWrites}}
         handler.publish(topic, {{lastValue}});
       };
@@ -18,6 +24,7 @@ public boolean {{parentTypeName}}.{{connectMethod}}(String {{connectParameterNam
       if (writeCurrentValue) {
         {{writeMethod}}();
       }
+      connectToken = new ConnectToken(uri);
       break;
   {{/usesMqtt}}
   {{#usesJava}}
@@ -35,26 +42,40 @@ public boolean {{parentTypeName}}.{{connectMethod}}(String {{connectParameterNam
   {{/usesJava}}
   {{#usesRest}}
     case "rest":
-      ConnectToken connectToken = {{restHandlerAttribute}}().newGETConnection(uri, () -> {
+      connectToken = {{restHandlerAttribute}}().newGETConnection(uri, () -> {
         {{updateMethod}}();
         return new String({{lastValue}});
       });
       if (connectToken == null) {
         return false;
       }
-      connectTokens.computeIfAbsent(this, astNode -> new java.util.HashMap<java.net.URI, ConnectToken>())
-                   .put(uri, connectToken);
       break;
   {{/usesRest}}
     default:
       System.err.println("Unknown protocol '" + scheme + "'.");
       return false;
   }
+  connectTokens.computeIfAbsent(this, astNode -> new java.util.HashMap<java.net.URI, ConnectToken>())
+               .put(uri, connectToken);
+  {{#incrementalOptionActive}}
+  _ragConnectObserver().add(connectToken, this, "{{getterMethod}}", () -> {
+    if (this.{{updateMethod}}()) {
+      this.{{writeMethod}}();
+    }
+  });
+  {{/incrementalOptionActive}}
   return true;
 }
 
 public boolean {{parentTypeName}}.{{disconnectMethod}}(String {{connectParameterName}}) throws java.io.IOException {
   {{>handleUri}}
+  ConnectToken connectToken = connectTokens.get(this).remove(uri);
+  if (connectToken == null) {
+    System.err.println("Disconnect without connect for " + uri + " on " + this + "!");
+  }
+  {{#incrementalOptionActive}}
+  _ragConnectObserver().remove(connectToken);
+  {{/incrementalOptionActive}}
   switch (scheme) {
   {{#usesMqtt}}
     case "mqtt":
@@ -69,7 +90,7 @@ public boolean {{parentTypeName}}.{{disconnectMethod}}(String {{connectParameter
   {{/usesJava}}
   {{#usesRest}}
     case "rest":
-      {{restHandlerAttribute}}().disconnect(connectTokens.get(this).get(uri));
+      {{restHandlerAttribute}}().disconnect(connectToken);
       break;
   {{/usesRest}}
     default:
diff --git a/ragconnect.tests/build.gradle b/ragconnect.tests/build.gradle
index ccc0134eb86898cd001183828f81652f2a8c2010..c43e47044c47358624b646fec196634ac28786bf 100644
--- a/ragconnect.tests/build.gradle
+++ b/ragconnect.tests/build.gradle
@@ -1,13 +1,20 @@
 buildscript {
-    repositories.mavenCentral()
+    repositories {
+        mavenCentral()
+//        mavenLocal()
+        maven {
+            name "gitlab-maven"
+            url "https://git-st.inf.tu-dresden.de/api/v4/groups/jastadd/-/packages/maven"
+        }
+    }
     dependencies {
         classpath 'org.jastadd:jastaddgradle:1.13.3'
-        classpath fileTree(include: ['buildSrc.jar'], dir: '../libs')
+        classpath 'org.jastadd.preprocessor:testing:0.2.10'
     }
 }
 
-import org.jastadd.relast.plugin.RelastPlugin
-import org.jastadd.relast.plugin.RelastTest
+import org.jastadd.preprocessor.testing.plugin.PreprocessorPlugin
+import org.jastadd.preprocessor.testing.plugin.RagConnectTest
 
 plugins {
     id 'java'
@@ -18,23 +25,28 @@ plugins {
 }
 
 apply plugin: 'jastadd'
-apply plugin: RelastPlugin
+apply plugin: PreprocessorPlugin
 
 group = 'de.tudresden.inf.st'
 
 repositories {
     mavenCentral()
-    jcenter()
 }
 
 dependencies {
     implementation project(':ragconnect.base')
 
-    runtime group: 'org.jastadd', name: 'jastadd', version: '2.3.4'
+    runtimeOnly group: 'org.jastadd', name: 'jastadd', version: '2.3.5'
+//    runtimeOnly fileTree(include: ['jastadd2.jar'], dir: '../libs')
+
     testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.4.0'
     testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.4.0'
     testImplementation group: 'org.assertj', name: 'assertj-core', version: '3.12.1'
 
+    // jackson (for serialization of types)
+    implementation group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.12.1'
+    implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.12.1'
+
     // mqtt
     testImplementation group: 'org.fusesource.mqtt-client', name: 'mqtt-client', version: '1.15'
 
@@ -43,11 +55,19 @@ dependencies {
     testImplementation group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: '2.11.2'
     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'
 }
 
+//task helper {
+//    doLast {
+//        println(defaultOnlyRead.inputs.files.files)
+//        println(defaultOnlyRead.outputs.files.files)
+//    }
+//}
+
 test {
     useJUnitPlatform {
         excludeTags 'mqtt'
@@ -72,9 +92,21 @@ task allTests(type: Test, dependsOn: testClasses) {
     }
 }
 
-relastTest {
+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") : ''
+
+    useJUnitPlatform {
+        includeTags tags
+    }
+}
+
+preprocessorTesting {
     //noinspection GroovyAssignabilityCheck
-    compilerLocation = '../libs/relast.jar'
+    relastCompilerLocation = '../libs/relast.jar'
+//    ragconnectCompilerLocation = '../libs/ragconnect.jar'
 }
 
 File genSrc = file("src/test/java-gen")
@@ -82,259 +114,452 @@ sourceSets.test.java.srcDir genSrc
 idea.module.generatedSourceDirs += genSrc
 
 clean {
-    delete 'src/test/02-after-ragconnect/*/', 'src/test/03-after-relast/*/', 'src/test/java-gen/*/'
+    delete fileTree(dir: 'src/test/02-after-ragconnect/', exclude: '.gitignore')
+    delete fileTree(dir: 'src/test/03-after-relast/', exclude: '.gitignore')
+    delete fileTree(dir: 'src/test/java-gen/', exclude: '.gitignore')
 }
 
 // --- Test: Example ---
-task preprocessExampleTest(type: JavaExec, group: 'verification') {
-    doFirst {
-        delete 'src/test/02-after-ragconnect/example/Test.relast',
-                'src/test/02-after-ragconnect/example/MqttHandler.jadd',
-                'src/test/02-after-ragconnect/example/RagConnect.jadd'
-    }
-
-    classpath = sourceSets.main.runtimeClasspath
-    main = 'org.jastadd.ragconnect.compiler.Compiler'
-    args '--o=src/test/02-after-ragconnect/example',
-            'src/test/01-input/example/Test.relast',
-            'src/test/01-input/example/Test.connect',
-            '--rootNode=Model',
-            '--logReads', '--logWrites', '--protocols=mqtt'
-}
-
-task compileExampleTest(type: RelastTest) {
-    useJastAddNames = true
-    jastAddList = 'JastAddList'
-    relastFiles 'src/test/02-after-ragconnect/example/Test.relast',
-            'src/test/02-after-ragconnect/example/RagConnect.relast'
-    grammarName = 'src/test/03-after-relast/example/example'
-    packageName = 'example.ast'
-    moreInputFiles 'src/test/01-input/example/Test.jadd',
-            'src/test/02-after-ragconnect/example/MqttHandler.jadd',
-            'src/test/02-after-ragconnect/example/RagConnect.jadd'
+task compileExampleTest(type: RagConnectTest) {
+    ragconnect {
+        outputDir = file('src/test/02-after-ragconnect/example')
+        inputFiles = [file('src/test/01-input/example/Test.relast'),
+                      file('src/test/01-input/example/Test.connect')]
+        rootNode = 'Model'
+    }
+    relast {
+        useJastAddNames = true
+        grammarName = 'src/test/03-after-relast/example/example'
+    }
+    jastadd {
+        jastAddList = 'JastAddList'
+        packageName = 'example.ast'
+        inputFiles = [file('src/test/01-input/example/Test.jadd')]
+    }
 }
 
-compileTestJava.dependsOn compileExampleTest
-compileExampleTest.dependsOn preprocessExampleTest
-
 // --- Test: default-only-read ---
-task preprocessDefaultOnlyReadTest(type: JavaExec, group: 'verification') {
-    doFirst {
-        delete 'src/test/02-after-ragconnect/defaultOnlyRead/Test.relast',
-                'src/test/02-after-ragconnect/defaultOnlyRead/MqttHandler.jadd',
-                'src/test/02-after-ragconnect/defaultOnlyRead/RagConnect.jadd'
-    }
-
-    classpath = sourceSets.main.runtimeClasspath
-    main = 'org.jastadd.ragconnect.compiler.Compiler'
-    args '--o=src/test/02-after-ragconnect/defaultOnlyRead',
-            'src/test/01-input/defaultOnlyRead/Test.relast',
-            'src/test/01-input/defaultOnlyRead/Test.connect',
-            '--rootNode=A', '--protocols=mqtt'
+task compileDefaultOnlyRead(type: RagConnectTest) {
+    ragconnect {
+        outputDir = file('src/test/02-after-ragconnect/defaultOnlyRead')
+        inputFiles = [file('src/test/01-input/defaultOnlyRead/Test.relast'),
+                      file('src/test/01-input/defaultOnlyRead/Test.connect')]
+        rootNode = 'A'
+    }
+    relast {
+        useJastAddNames = true
+        grammarName = 'src/test/03-after-relast/defaultOnlyRead/defaultOnlyRead'
+    }
+    jastadd {
+        jastAddList = 'JastAddList'
+        packageName = 'defaultOnlyRead.ast'
+    }
 }
 
-task compileDefaultOnlyReadTest(type: RelastTest) {
-    useJastAddNames = true
-    jastAddList = 'JastAddList'
-    relastFiles 'src/test/02-after-ragconnect/defaultOnlyRead/Test.relast',
-            'src/test/02-after-ragconnect/defaultOnlyRead/RagConnect.relast'
-    grammarName = 'src/test/03-after-relast/defaultOnlyRead/defaultOnlyRead'
-    packageName = 'defaultOnlyRead.ast'
-    moreInputFiles 'src/test/02-after-ragconnect/defaultOnlyRead/MqttHandler.jadd',
-            'src/test/02-after-ragconnect/defaultOnlyRead/RagConnect.jadd'
+// --- Test: default-only-write ---
+task compileDefaultOnlyWrite(type: RagConnectTest) {
+    ragconnect {
+        outputDir = file('src/test/02-after-ragconnect/defaultOnlyWrite')
+        inputFiles = [file('src/test/01-input/defaultOnlyWrite/Test.relast'),
+                      file('src/test/01-input/defaultOnlyWrite/Test.connect')]
+        rootNode = 'A'
+    }
+    relast {
+        useJastAddNames = true
+        grammarName = 'src/test/03-after-relast/defaultOnlyWrite/defaultOnlyWrite'
+    }
+    jastadd {
+        jastAddList = 'JastAddList'
+        packageName = 'defaultOnlyWrite.ast'
+        inputFiles = [file('src/test/01-input/defaultOnlyWrite/Test.jadd')]
+    }
 }
 
-compileTestJava.dependsOn compileDefaultOnlyReadTest
-compileDefaultOnlyReadTest.dependsOn preprocessDefaultOnlyReadTest
-
-// --- Test: default-only-write ---
-task preprocessDefaultOnlyWriteTest(type: JavaExec, group: 'verification') {
-    doFirst {
-        delete 'src/test/02-after-ragconnect/defaultOnlyWrite/Test.relast',
-                'src/test/02-after-ragconnect/defaultOnlyWrite/MqttHandler.jadd',
-                'src/test/02-after-ragconnect/defaultOnlyWrite/RagConnect.jadd'
-    }
-
-    classpath = sourceSets.main.runtimeClasspath
-    main = 'org.jastadd.ragconnect.compiler.Compiler'
-    args '--o=src/test/02-after-ragconnect/defaultOnlyWrite',
-            'src/test/01-input/defaultOnlyWrite/Test.relast',
-            'src/test/01-input/defaultOnlyWrite/Test.connect',
-            '--rootNode=A', '--protocols=mqtt'
+// --- Test: read1write2 ---
+task compileRead1write2(type: RagConnectTest) {
+    ragconnect {
+        outputDir = file('src/test/02-after-ragconnect/read1write2')
+        inputFiles = [file('src/test/01-input/read1write2/Test.relast'),
+                      file('src/test/01-input/read1write2/Test.connect')]
+        rootNode = 'A'
+    }
+    relast {
+        useJastAddNames = true
+        grammarName = 'src/test/03-after-relast/read1write2/read1write2'
+    }
+    jastadd {
+        jastAddList = 'JastAddList'
+        packageName = 'read1write2.ast'
+        inputFiles = [file('src/test/01-input/read1write2/Test.jadd')]
+    }
 }
 
-task compileDefaultOnlyWriteTest(type: RelastTest) {
-    useJastAddNames = true
-    jastAddList = 'JastAddList'
-    relastFiles 'src/test/02-after-ragconnect/defaultOnlyWrite/Test.relast',
-            'src/test/02-after-ragconnect/defaultOnlyWrite/RagConnect.relast'
-    grammarName = 'src/test/03-after-relast/defaultOnlyWrite/defaultOnlyWrite'
-    packageName = 'defaultOnlyWrite.ast'
-    moreInputFiles 'src/test/01-input/defaultOnlyWrite/Test.jadd',
-            'src/test/02-after-ragconnect/defaultOnlyWrite/MqttHandler.jadd',
-            'src/test/02-after-ragconnect/defaultOnlyWrite/RagConnect.jadd'
+// --- Test: read2write1 ---
+task compileRead2write1(type: RagConnectTest) {
+    ragconnect {
+        outputDir = file('src/test/02-after-ragconnect/read2write1')
+        inputFiles = [file('src/test/01-input/read2write1/Test.relast'),
+                      file('src/test/01-input/read2write1/Test.connect')]
+        rootNode = 'A'
+    }
+    relast {
+        useJastAddNames = true
+        grammarName = 'src/test/03-after-relast/read2write1/read2write1'
+    }
+    jastadd {
+        jastAddList = 'JastAddList'
+        packageName = 'read2write1.ast'
+        inputFiles = [file('src/test/01-input/read2write1/Test.jadd')]
+    }
 }
 
-compileTestJava.dependsOn compileDefaultOnlyWriteTest
-compileDefaultOnlyWriteTest.dependsOn preprocessDefaultOnlyWriteTest
+// --- Test: via ---
+task compileVia(type: RagConnectTest) {
+    ragconnect {
+        outputDir = file('src/test/02-after-ragconnect/via')
+        inputFiles = [file('src/test/01-input/via/Test.relast'),
+                      file('src/test/01-input/via/Test.connect')]
+        rootNode = 'A'
+        protocols = ['mqtt', 'rest']
+    }
+    relast {
+        useJastAddNames = true
+        grammarName = 'src/test/03-after-relast/via/via'
+    }
+    jastadd {
+        jastAddList = 'JastAddList'
+        packageName = 'via.ast'
+        inputFiles = [file('src/test/01-input/via/Test.jadd')]
+    }
+}
 
-// --- Test: read1write2 ---
-task preprocessRead1Write2Test(type: JavaExec, group: 'verification') {
-    doFirst {
-        delete 'src/test/02-after-ragconnect/read1write2/Test.relast',
-                'src/test/02-after-ragconnect/read1write2/MqttHandler.jadd',
-                'src/test/02-after-ragconnect/read1write2/RagConnect.jadd'
-    }
-
-    classpath = sourceSets.main.runtimeClasspath
-    main = 'org.jastadd.ragconnect.compiler.Compiler'
-    args '--o=src/test/02-after-ragconnect/read1write2',
-            'src/test/01-input/read1write2/Test.relast',
-            'src/test/01-input/read1write2/Test.connect',
-            '--rootNode=A', '--protocols=mqtt'
+// --- Test: token-value-send ---
+task compileTokenValueSend(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')]
+        rootNode = 'A'
+    }
+    relast {
+        useJastAddNames = true
+        grammarName = 'src/test/03-after-relast/tokenValueSend/tokenValueSend'
+    }
+    jastadd {
+        jastAddList = 'JastAddList'
+        packageName = 'tokenValueSend.ast'
+        inputFiles = [file('src/test/01-input/tokenValueSend/Test.jadd')]
+    }
 }
 
-task compileRead1Write2Test(type: RelastTest) {
-    useJastAddNames = true
-    jastAddList = 'JastAddList'
-    relastFiles 'src/test/02-after-ragconnect/read1write2/Test.relast',
-            'src/test/02-after-ragconnect/read1write2/RagConnect.relast'
-    grammarName = 'src/test/03-after-relast/read1write2/read1write2'
-    packageName = 'read1write2.ast'
-    moreInputFiles 'src/test/01-input/read1write2/Test.jadd',
-            'src/test/02-after-ragconnect/read1write2/MqttHandler.jadd',
-            'src/test/02-after-ragconnect/read1write2/RagConnect.jadd'
+// --- Test: tutorial ---
+task compileTutorial(type: RagConnectTest) {
+    ragconnect {
+        outputDir = file('src/test/02-after-ragconnect/tutorial')
+        inputFiles = [file('src/test/01-input/tutorial/Test.relast'),
+                      file('src/test/01-input/tutorial/Test.connect')]
+        rootNode = 'A'
+    }
+    relast {
+        useJastAddNames = true
+        grammarName = 'src/test/03-after-relast/tutorial/tutorial'
+    }
+    jastadd {
+        jastAddList = 'JastAddList'
+        packageName = 'tutorial.ast'
+        inputFiles = [file('src/test/01-input/tutorial/Test.jadd')]
+    }
 }
 
-compileTestJava.dependsOn compileRead1Write2Test
-compileRead1Write2Test.dependsOn preprocessRead1Write2Test
+// --- Test: incremental ---
+task compileIncremental(type: RagConnectTest) {
+    ragconnect {
+        outputDir = file('src/test/02-after-ragconnect/incremental')
+        inputFiles = [file('src/test/01-input/incremental/Test.relast'),
+                      file('src/test/01-input/incremental/Test.connect')]
+        rootNode = 'A'
+        logWrites = true
+    }
+    relast {
+        useJastAddNames = true
+        grammarName = 'src/test/03-after-relast/incremental/incremental'
+    }
+    jastadd {
+        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']
+    }
+}
 
-// --- Test: read2write1 ---
-task preprocessRead2Write1Test(type: JavaExec, group: 'verification') {
-    doFirst {
-        delete 'src/test/02-after-ragconnect/read2write1/Test.relast',
-                'src/test/02-after-ragconnect/read2write1/MqttHandler.jadd',
-                'src/test/02-after-ragconnect/read2write1/RagConnect.jadd'
-    }
-
-    classpath = sourceSets.main.runtimeClasspath
-    main = 'org.jastadd.ragconnect.compiler.Compiler'
-    args '--o=src/test/02-after-ragconnect/read2write1',
-            'src/test/01-input/read2write1/Test.relast',
-            'src/test/01-input/read2write1/Test.connect',
-            '--rootNode=A', '--protocols=mqtt'
+// --- Test: mapping ---
+task compileMapping(type: RagConnectTest) {
+    ragconnect {
+        outputDir = file('src/test/02-after-ragconnect/mapping')
+        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
+    }
+    relast {
+        useJastAddNames = true
+        grammarName = 'src/test/03-after-relast/mapping/mapping'
+    }
+    jastadd {
+        jastAddList = 'JastAddList'
+        packageName = 'mapping.ast'
+        inputFiles = [file('src/test/01-input/mapping/Test.jadd')]
+    }
 }
 
-task compileRead2Write1Test(type: RelastTest) {
-    useJastAddNames = true
-    jastAddList = 'JastAddList'
-    relastFiles 'src/test/02-after-ragconnect/read2write1/Test.relast',
-            'src/test/02-after-ragconnect/read2write1/RagConnect.relast'
-    grammarName = 'src/test/03-after-relast/read2write1/read2write1'
-    packageName = 'read2write1.ast'
-    moreInputFiles 'src/test/01-input/read2write1/Test.jadd',
-            'src/test/02-after-ragconnect/read2write1/MqttHandler.jadd',
-            'src/test/02-after-ragconnect/read2write1/RagConnect.jadd'
+// --- Test: tree-manual ---
+task compileTreeManual(type: RagConnectTest, dependsOn: ':ragconnect.base:compileJava') {
+    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'
+    }
+    relast {
+        useJastAddNames = true
+        grammarName = 'src/test/03-after-relast/tree/tree'
+        serializer = 'jackson'
+    }
+    jastadd {
+        jastAddList = 'JastAddList'
+        packageName = 'tree.ast'
+        inputFiles = [file('src/test/01-input/tree/Test.jadd')]
+    }
 }
 
-compileTestJava.dependsOn compileRead2Write1Test
-compileRead2Write1Test.dependsOn preprocessRead2Write1Test
+// --- Test: tree-incremental ---
+task compileTreeIncremental(type: RagConnectTest) {
+    ragconnect {
+        outputDir = file('src/test/02-after-ragconnect/treeInc')
+        inputFiles = [file('src/test/01-input/tree/Test.relast'),
+                      file('src/test/01-input/tree/Test.connect')]
+        rootNode = 'Root'
+        logWrites = true
+    }
+    relast {
+        useJastAddNames = true
+        grammarName = 'src/test/03-after-relast/treeInc/treeInc'
+        serializer = 'jackson'
+    }
+    jastadd {
+        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']
+    }
+}
 
-// --- Test: via ---
-task preprocessViaTest(type: JavaExec, group: 'verification') {
-    doFirst {
-        delete 'src/test/02-after-ragconnect/via/Test.relast',
-                'src/test/02-after-ragconnect/via/MqttHandler.jadd',
-                'src/test/02-after-ragconnect/via/RestHandler.jadd',
-                'src/test/02-after-ragconnect/via/RagConnect.jadd'
-    }
-
-    classpath = sourceSets.main.runtimeClasspath
-    main = 'org.jastadd.ragconnect.compiler.Compiler'
-    args '--o=src/test/02-after-ragconnect/via',
-            'src/test/01-input/via/Test.relast',
-            'src/test/01-input/via/Test.connect',
-            '--rootNode=A',
-            '--protocols=mqtt,rest'
+// --- Test: tree-allowed-tokens ---
+task compileTreeAllowedTokens(type: RagConnectTest) {
+    ragconnect {
+        outputDir = file('src/test/02-after-ragconnect/treeAllowedTokens')
+        inputFiles = [file('src/test/01-input/treeAllowedTokens/Test.relast'),
+                      file('src/test/01-input/treeAllowedTokens/Test.connect'),
+                      file('src/test/01-input/treeAllowedTokens/TestDependencies.connect')]
+        rootNode = 'Root'
+    }
+    relast {
+        useJastAddNames = true
+        grammarName = 'src/test/03-after-relast/treeAllowedTokens/treeAllowedTokens'
+        serializer = 'jackson'
+    }
+    jastadd {
+        jastAddList = 'JastAddList'
+        packageName = 'treeAllowedTokens.ast'
+        inputFiles = [file('src/test/01-input/treeAllowedTokens/Test.jadd')]
+    }
 }
 
-task compileViaTest(type: RelastTest) {
-    useJastAddNames = true
-    jastAddList = 'JastAddList'
-    relastFiles 'src/test/02-after-ragconnect/via/Test.relast',
-            'src/test/02-after-ragconnect/via/RagConnect.relast'
-    grammarName = 'src/test/03-after-relast/via/via'
-    packageName = 'via.ast'
-    moreInputFiles 'src/test/01-input/via/Test.jadd',
-            'src/test/02-after-ragconnect/via/MqttHandler.jadd',
-            'src/test/02-after-ragconnect/via/RestHandler.jadd',
-            'src/test/02-after-ragconnect/via/RagConnect.jadd'
+// --- Test: tree-allowed-tokens-incremental ---
+task compileTreeAllowedTokensIncremental(type: RagConnectTest) {
+    ragconnect {
+        outputDir = file('src/test/02-after-ragconnect/treeAllowedTokensInc')
+        inputFiles = [file('src/test/01-input/treeAllowedTokens/Test.relast'),
+                      file('src/test/01-input/treeAllowedTokens/Test.connect')]
+        rootNode = 'Root'
+        logWrites = true
+    }
+    relast {
+        useJastAddNames = true
+        grammarName = 'src/test/03-after-relast/treeAllowedTokensInc/treeAllowedTokensInc'
+        serializer = 'jackson'
+    }
+    jastadd {
+        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']
+    }
 }
 
-compileTestJava.dependsOn compileViaTest
-compileViaTest.dependsOn preprocessViaTest
+// --- Test: list-manual ---
+task compileListManual(type: RagConnectTest, dependsOn: ':ragconnect.base:jar') {
+    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'
+    }
+    relast {
+        useJastAddNames = true
+        grammarName = 'src/test/03-after-relast/list/list'
+        serializer = 'jackson'
+    }
+    jastadd {
+        jastAddList = 'JastAddList'
+        packageName = 'list.ast'
+        inputFiles = [file('src/test/01-input/list/Test.jadd')]
+    }
+}
 
-// --- Test: token-value-send ---
-task preprocessTokenValueSendTest(type: JavaExec, group: 'verification') {
-    doFirst {
-        delete 'src/test/02-after-ragconnect/tokenValueSend/Test.relast',
-                'src/test/02-after-ragconnect/tokenValueSend/MqttHandler.jadd',
-                'src/test/02-after-ragconnect/tokenValueSend/RestHandler.jadd',
-                'src/test/02-after-ragconnect/tokenValueSend/RagConnect.jadd'
-    }
-
-    classpath = sourceSets.main.runtimeClasspath
-    main = 'org.jastadd.ragconnect.compiler.Compiler'
-    args '--o=src/test/02-after-ragconnect/tokenValueSend',
-            'src/test/01-input/tokenValueSend/Test.relast',
-            'src/test/01-input/tokenValueSend/Test.connect',
-            '--rootNode=A',
-            '--protocols=mqtt'
+// --- Test: list-incremental ---
+task compileListIncremental(type: RagConnectTest, dependsOn: ':ragconnect.base:jar') {
+    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'
+    }
+    relast {
+        useJastAddNames = true
+        grammarName = 'src/test/03-after-relast/listInc/listInc'
+        serializer = 'jackson'
+    }
+    jastadd {
+        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']
+    }
 }
 
-task compileTokenValueSendTest(type: RelastTest) {
-    useJastAddNames = true
-    jastAddList = 'JastAddList'
-    relastFiles 'src/test/02-after-ragconnect/tokenValueSend/Test.relast',
-            'src/test/02-after-ragconnect/tokenValueSend/RagConnect.relast'
-    grammarName = 'src/test/03-after-relast/tokenValueSend/tokenValueSend'
-    packageName = 'tokenValueSend.ast'
-    moreInputFiles 'src/test/01-input/tokenValueSend/Test.jadd',
-            'src/test/02-after-ragconnect/tokenValueSend/MqttHandler.jadd',
-            'src/test/02-after-ragconnect/tokenValueSend/RagConnect.jadd'
+// --- Test: singleList-manual ---
+task compileSingleListManual(type: RagConnectTest, dependsOn: ':ragconnect.base:jar') {
+    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'
+    }
+    relast {
+        useJastAddNames = true
+        grammarName = 'src/test/03-after-relast/singleList/singleList'
+        serializer = 'jackson'
+    }
+    jastadd {
+        jastAddList = 'JastAddList'
+        packageName = 'singleList.ast'
+        inputFiles = [file('src/test/01-input/singleList/Test.jadd')]
+    }
 }
 
-compileTestJava.dependsOn compileTokenValueSendTest
-compileTokenValueSendTest.dependsOn preprocessTokenValueSendTest
+// --- Test: singleList-incremental ---
+task compileSingleListIncremental(type: RagConnectTest, dependsOn: ':ragconnect.base:jar') {
+    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'
+    }
+    relast {
+        useJastAddNames = true
+        grammarName = 'src/test/03-after-relast/singleListInc/singleListInc'
+        serializer = 'jackson'
+    }
+    jastadd {
+        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']
+    }
+}
 
-// --- Test: tutorial ---
-task preprocessTutorialTest(type: JavaExec, group: 'verification') {
-    doFirst {
-        delete 'src/test/02-after-ragconnect/tutorial/Test.relast',
-                'src/test/02-after-ragconnect/tutorial/MqttHandler.jadd',
-                'src/test/02-after-ragconnect/tutorial/RagConnect.jadd'
-    }
-
-    classpath = sourceSets.main.runtimeClasspath
-    main = 'org.jastadd.ragconnect.compiler.Compiler'
-    args '--o=src/test/02-after-ragconnect/tutorial',
-            'src/test/01-input/tutorial/Test.relast',
-            'src/test/01-input/tutorial/Test.connect',
-            '--rootNode=A',
-            '--protocols=mqtt'
+// --- Test: singleListVariant-manual ---
+task compileSingleListVariantManual(type: RagConnectTest, dependsOn: ':ragconnect.base:jar') {
+    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'
+    }
+    relast {
+        useJastAddNames = true
+        grammarName = 'src/test/03-after-relast/singleListVariant/singleListVariant'
+        serializer = 'jackson'
+    }
+    jastadd {
+        jastAddList = 'JastAddList'
+        packageName = 'singleListVariant.ast'
+        inputFiles = [file('src/test/01-input/singleListVariant/Test.jadd')]
+    }
 }
 
-task compileTutorialTest(type: RelastTest) {
-    useJastAddNames = true
-    jastAddList = 'JastAddList'
-    relastFiles 'src/test/02-after-ragconnect/tutorial/Test.relast',
-            'src/test/02-after-ragconnect/tutorial/RagConnect.relast'
-    grammarName = 'src/test/03-after-relast/tutorial/tutorial'
-    packageName = 'tutorial.ast'
-    moreInputFiles 'src/test/01-input/tutorial/Test.jadd',
-            'src/test/02-after-ragconnect/tutorial/MqttHandler.jadd',
-            'src/test/02-after-ragconnect/tutorial/RagConnect.jadd'
+// --- Test: singleListVariant-incremental ---
+task compileSingleListVariantIncremental(type: RagConnectTest, dependsOn: ':ragconnect.base:jar') {
+    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'
+    }
+    relast {
+        useJastAddNames = true
+        grammarName = 'src/test/03-after-relast/singleListVariantInc/singleListVariantInc'
+        serializer = 'jackson'
+    }
+    jastadd {
+        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']
+    }
 }
 
-compileTestJava.dependsOn compileTutorialTest
-compileTutorialTest.dependsOn preprocessTutorialTest
+//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"
+//}
+//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"
+//}
+//compileSingleListVariantManual.dependsOn cleanCurrentManualTest
+//compileSingleListVariantIncremental.dependsOn cleanCurrentIncrementalTest
+
diff --git a/ragconnect.tests/src/test/01-input/defaultOnlyRead/Test.connect b/ragconnect.tests/src/test/01-input/defaultOnlyRead/Test.connect
index e274994175513ff743409068375f419b6e3141fc..69a2165b1dc0ace787a1dbe8143807c90b3197c8 100644
--- a/ragconnect.tests/src/test/01-input/defaultOnlyRead/Test.connect
+++ b/ragconnect.tests/src/test/01-input/defaultOnlyRead/Test.connect
@@ -1,4 +1,5 @@
 // --- update definitions ---
+receive NativeTypes.BooleanValue;
 receive NativeTypes.IntValue;
 receive NativeTypes.ShortValue;
 receive NativeTypes.LongValue;
@@ -7,9 +8,52 @@ receive NativeTypes.DoubleValue;
 receive NativeTypes.CharValue;
 receive NativeTypes.StringValue;
 
+receive NativeTypes.BooleanValueTransformed using BooleanTransformation;
+receive NativeTypes.IntValueTransformed using IntTransformation;
+receive NativeTypes.ShortValueTransformed using ShortTransformation;
+receive NativeTypes.LongValueTransformed using LongTransformation;
+receive NativeTypes.FloatValueTransformed using FloatTransformation;
+receive NativeTypes.DoubleValueTransformed using DoubleTransformation;
+receive NativeTypes.CharValueTransformed using CharTransformation;
+receive NativeTypes.StringValueTransformed using StringTransformation;
+
+receive BoxedTypes.BooleanValue;
 receive BoxedTypes.IntValue;
 receive BoxedTypes.ShortValue;
 receive BoxedTypes.LongValue;
 receive BoxedTypes.FloatValue;
 receive BoxedTypes.DoubleValue;
 receive BoxedTypes.CharValue;
+
+receive BoxedTypes.BooleanValueTransformed using BooleanTransformation;
+receive BoxedTypes.IntValueTransformed using IntTransformation;
+receive BoxedTypes.ShortValueTransformed using ShortTransformation;
+receive BoxedTypes.LongValueTransformed using LongTransformation;
+receive BoxedTypes.FloatValueTransformed using FloatTransformation;
+receive BoxedTypes.DoubleValueTransformed using DoubleTransformation;
+receive BoxedTypes.CharValueTransformed using CharTransformation;
+
+BooleanTransformation maps boolean b to boolean {:
+  return b;
+:}
+IntTransformation maps int i to int {:
+  return i;
+:}
+ShortTransformation maps short s to short {:
+  return s;
+:}
+LongTransformation maps long l to long {:
+  return l;
+:}
+FloatTransformation maps float f to float {:
+  return f;
+:}
+DoubleTransformation maps double d to double {:
+  return d;
+:}
+CharTransformation maps char c to char {:
+  return c;
+:}
+StringTransformation maps String s to String {:
+  return s;
+:}
diff --git a/ragconnect.tests/src/test/01-input/defaultOnlyRead/Test.relast b/ragconnect.tests/src/test/01-input/defaultOnlyRead/Test.relast
index 30eba76b346dace40c72ca4e172b88ef206ea063..060d5a5c16e793a20c79d99a267e58833b0c8ac1 100644
--- a/ragconnect.tests/src/test/01-input/defaultOnlyRead/Test.relast
+++ b/ragconnect.tests/src/test/01-input/defaultOnlyRead/Test.relast
@@ -1,3 +1,3 @@
 A ::= NativeTypes* BoxedTypes* ;
-NativeTypes ::= <IntValue:int> <ShortValue:short> <LongValue:long> <FloatValue:float> <DoubleValue:double> <CharValue:char> <StringValue:String> ;
-BoxedTypes ::= <IntValue:Integer> <ShortValue:Short> <LongValue:Long> <FloatValue:Float> <DoubleValue:Double> <CharValue:Character> ;
+NativeTypes ::= <BooleanValue:boolean> <IntValue:int> <ShortValue:short> <LongValue:long> <FloatValue:float> <DoubleValue:double> <CharValue:char> <StringValue:String> <BooleanValueTransformed:boolean> <IntValueTransformed:int> <ShortValueTransformed:short> <LongValueTransformed:long> <FloatValueTransformed:float> <DoubleValueTransformed:double> <CharValueTransformed:char> <StringValueTransformed:String> ;
+BoxedTypes ::= <BooleanValue:Boolean> <IntValue:Integer> <ShortValue:Short> <LongValue:Long> <FloatValue:Float> <DoubleValue:Double> <CharValue:Character> <BooleanValueTransformed:Boolean> <IntValueTransformed:Integer> <ShortValueTransformed:Short> <LongValueTransformed:Long> <FloatValueTransformed:Float> <DoubleValueTransformed:Double> <CharValueTransformed:Character> ;
diff --git a/ragconnect.tests/src/test/01-input/defaultOnlyWrite/Test.connect b/ragconnect.tests/src/test/01-input/defaultOnlyWrite/Test.connect
index a48f49f60f66c3358ee513e79f2bbeb43df8d5e9..44ca4ba05574a67b29268a1b4ff61185a30ea15f 100644
--- a/ragconnect.tests/src/test/01-input/defaultOnlyWrite/Test.connect
+++ b/ragconnect.tests/src/test/01-input/defaultOnlyWrite/Test.connect
@@ -1,5 +1,6 @@
 // --- update definitions ---
 // native types, synthesized
+send NativeTypesSyn.BooleanValue;
 send NativeTypesSyn.IntValue;
 send NativeTypesSyn.ShortValue;
 send NativeTypesSyn.LongValue;
@@ -8,7 +9,18 @@ send NativeTypesSyn.DoubleValue;
 send NativeTypesSyn.CharValue;
 send NativeTypesSyn.StringValue;
 
+// native types, synthesized, transformed
+send NativeTypesSyn.BooleanValueTransformed using BooleanTransformation;
+send NativeTypesSyn.IntValueTransformed using IntTransformation;
+send NativeTypesSyn.ShortValueTransformed using ShortTransformation;
+send NativeTypesSyn.LongValueTransformed using LongTransformation;
+send NativeTypesSyn.FloatValueTransformed using FloatTransformation;
+send NativeTypesSyn.DoubleValueTransformed using DoubleTransformation;
+send NativeTypesSyn.CharValueTransformed using CharTransformation;
+send NativeTypesSyn.StringValueTransformed using StringTransformation;
+
 // boxed types, synthesized
+send BoxedTypesSyn.BooleanValue;
 send BoxedTypesSyn.IntValue;
 send BoxedTypesSyn.ShortValue;
 send BoxedTypesSyn.LongValue;
@@ -16,7 +28,17 @@ send BoxedTypesSyn.FloatValue;
 send BoxedTypesSyn.DoubleValue;
 send BoxedTypesSyn.CharValue;
 
+// boxed types, synthesized, transformed
+send BoxedTypesSyn.BooleanValueTransformed using BooleanTransformation;
+send BoxedTypesSyn.IntValueTransformed using IntTransformation;
+send BoxedTypesSyn.ShortValueTransformed using ShortTransformation;
+send BoxedTypesSyn.LongValueTransformed using LongTransformation;
+send BoxedTypesSyn.FloatValueTransformed using FloatTransformation;
+send BoxedTypesSyn.DoubleValueTransformed using DoubleTransformation;
+send BoxedTypesSyn.CharValueTransformed using CharTransformation;
+
 // --- dependency definitions ---
+NativeTypesSyn.BooleanValue canDependOn NativeTypesSyn.DriverSyn as nativeBooleanDependency;
 NativeTypesSyn.IntValue canDependOn NativeTypesSyn.DriverSyn as nativeIntDependency;
 NativeTypesSyn.ShortValue canDependOn NativeTypesSyn.DriverSyn as nativeShortDependency;
 NativeTypesSyn.LongValue canDependOn NativeTypesSyn.DriverSyn as nativeLongDependency;
@@ -24,6 +46,8 @@ NativeTypesSyn.FloatValue canDependOn NativeTypesSyn.DriverSyn as nativeFloatDep
 NativeTypesSyn.DoubleValue canDependOn NativeTypesSyn.DriverSyn as nativeDoubleDependency;
 NativeTypesSyn.CharValue canDependOn NativeTypesSyn.DriverSyn as nativeCharDependency;
 NativeTypesSyn.StringValue canDependOn NativeTypesSyn.DriverSyn as nativeStringDependency;
+
+BoxedTypesSyn.BooleanValue canDependOn BoxedTypesSyn.DriverSyn as boxedBooleanDependency;
 BoxedTypesSyn.IntValue canDependOn BoxedTypesSyn.DriverSyn as boxedIntDependency;
 BoxedTypesSyn.ShortValue canDependOn BoxedTypesSyn.DriverSyn as boxedShortDependency;
 BoxedTypesSyn.LongValue canDependOn BoxedTypesSyn.DriverSyn as boxedLongDependency;
@@ -31,6 +55,23 @@ BoxedTypesSyn.FloatValue canDependOn BoxedTypesSyn.DriverSyn as boxedFloatDepend
 BoxedTypesSyn.DoubleValue canDependOn BoxedTypesSyn.DriverSyn as boxedDoubleDependency;
 BoxedTypesSyn.CharValue canDependOn BoxedTypesSyn.DriverSyn as boxedCharDependency;
 
+NativeTypesSyn.BooleanValueTransformed canDependOn NativeTypesSyn.DriverSyn as nativeBooleanTransformedDependency;
+NativeTypesSyn.IntValueTransformed canDependOn NativeTypesSyn.DriverSyn as nativeIntTransformedDependency;
+NativeTypesSyn.ShortValueTransformed canDependOn NativeTypesSyn.DriverSyn as nativeShortTransformedDependency;
+NativeTypesSyn.LongValueTransformed canDependOn NativeTypesSyn.DriverSyn as nativeLongTransformedDependency;
+NativeTypesSyn.FloatValueTransformed canDependOn NativeTypesSyn.DriverSyn as nativeFloatTransformedDependency;
+NativeTypesSyn.DoubleValueTransformed canDependOn NativeTypesSyn.DriverSyn as nativeDoubleTransformedDependency;
+NativeTypesSyn.CharValueTransformed canDependOn NativeTypesSyn.DriverSyn as nativeCharTransformedDependency;
+NativeTypesSyn.StringValueTransformed canDependOn NativeTypesSyn.DriverSyn as nativeStringTransformedDependency;
+
+BoxedTypesSyn.BooleanValueTransformed canDependOn BoxedTypesSyn.DriverSyn as boxedBooleanTransformedDependency;
+BoxedTypesSyn.IntValueTransformed canDependOn BoxedTypesSyn.DriverSyn as boxedIntTransformedDependency;
+BoxedTypesSyn.ShortValueTransformed canDependOn BoxedTypesSyn.DriverSyn as boxedShortTransformedDependency;
+BoxedTypesSyn.LongValueTransformed canDependOn BoxedTypesSyn.DriverSyn as boxedLongTransformedDependency;
+BoxedTypesSyn.FloatValueTransformed canDependOn BoxedTypesSyn.DriverSyn as boxedFloatTransformedDependency;
+BoxedTypesSyn.DoubleValueTransformed canDependOn BoxedTypesSyn.DriverSyn as boxedDoubleTransformedDependency;
+BoxedTypesSyn.CharValueTransformed canDependOn BoxedTypesSyn.DriverSyn as boxedCharTransformedDependency;
+
 
 // --- inherited attributes not supported ---
 //// native types, inherited
@@ -49,3 +90,28 @@ BoxedTypesSyn.CharValue canDependOn BoxedTypesSyn.DriverSyn as boxedCharDependen
 //send BoxedTypesInh.FloatValue;
 //send BoxedTypesInh.DoubleValue;
 //send BoxedTypesInh.CharValue;
+
+BooleanTransformation maps boolean b to boolean {:
+  return b;
+:}
+IntTransformation maps int i to int {:
+  return i;
+:}
+ShortTransformation maps short s to short {:
+  return s;
+:}
+LongTransformation maps long l to long {:
+  return l;
+:}
+FloatTransformation maps float f to float {:
+  return f;
+:}
+DoubleTransformation maps double d to double {:
+  return d;
+:}
+CharTransformation maps char c to char {:
+  return c;
+:}
+StringTransformation maps String s to String {:
+  return s;
+:}
diff --git a/ragconnect.tests/src/test/01-input/defaultOnlyWrite/Test.jadd b/ragconnect.tests/src/test/01-input/defaultOnlyWrite/Test.jadd
index 49bbf604d398f7a9d92e879b2b515c9e83dd3182..76f3f159b673f1b03c1001a2d7a251bc9ca54dfa 100644
--- a/ragconnect.tests/src/test/01-input/defaultOnlyWrite/Test.jadd
+++ b/ragconnect.tests/src/test/01-input/defaultOnlyWrite/Test.jadd
@@ -1,5 +1,6 @@
 aspect Computation {
   // native types, synthesized
+  syn boolean NativeTypesSyn.getBooleanValue() = Integer.parseInt(getDriverSyn()) > 1;
   syn int NativeTypesSyn.getIntValue() = Integer.parseInt(getDriverSyn());
   syn short NativeTypesSyn.getShortValue() = Short.parseShort(getDriverSyn());
   syn long NativeTypesSyn.getLongValue() = Long.parseLong(getDriverSyn());
@@ -8,7 +9,17 @@ aspect Computation {
   syn char NativeTypesSyn.getCharValue() = getDriverSyn().charAt(0);
   syn String NativeTypesSyn.getStringValue() = new String(getDriverSyn());
 
+  syn boolean NativeTypesSyn.getBooleanValueTransformed() = Integer.parseInt(getDriverSyn()) > 1;
+  syn int NativeTypesSyn.getIntValueTransformed() = Integer.parseInt(getDriverSyn());
+  syn short NativeTypesSyn.getShortValueTransformed() = Short.parseShort(getDriverSyn());
+  syn long NativeTypesSyn.getLongValueTransformed() = Long.parseLong(getDriverSyn());
+  syn float NativeTypesSyn.getFloatValueTransformed() = Float.parseFloat(getDriverSyn());
+  syn double NativeTypesSyn.getDoubleValueTransformed() = Double.parseDouble(getDriverSyn());
+  syn char NativeTypesSyn.getCharValueTransformed() = getDriverSyn().charAt(0);
+  syn String NativeTypesSyn.getStringValueTransformed() = new String(getDriverSyn());
+
   // boxed types, synthesized
+  syn Boolean BoxedTypesSyn.getBooleanValue() = Integer.parseInt(getDriverSyn()) > 1;
   syn Integer BoxedTypesSyn.getIntValue() = Integer.valueOf(getDriverSyn());
   syn Short BoxedTypesSyn.getShortValue() = Short.valueOf(getDriverSyn());
   syn Long BoxedTypesSyn.getLongValue() = Long.valueOf(getDriverSyn());
@@ -16,6 +27,14 @@ aspect Computation {
   syn Double BoxedTypesSyn.getDoubleValue() = Double.valueOf(getDriverSyn());
   syn Character BoxedTypesSyn.getCharValue() = getDriverSyn().charAt(0);
 
+  syn Boolean BoxedTypesSyn.getBooleanValueTransformed() = Integer.parseInt(getDriverSyn()) > 1;
+  syn Integer BoxedTypesSyn.getIntValueTransformed() = Integer.valueOf(getDriverSyn());
+  syn Short BoxedTypesSyn.getShortValueTransformed() = Short.valueOf(getDriverSyn());
+  syn Long BoxedTypesSyn.getLongValueTransformed() = Long.valueOf(getDriverSyn());
+  syn Float BoxedTypesSyn.getFloatValueTransformed() = Float.valueOf(getDriverSyn());
+  syn Double BoxedTypesSyn.getDoubleValueTransformed() = Double.valueOf(getDriverSyn());
+  syn Character BoxedTypesSyn.getCharValueTransformed() = getDriverSyn().charAt(0);
+
 // --- inherited attributes not supported ---
 //  // native types, inherited
 //  inh int NativeTypesInh.getIntValue();
diff --git a/ragconnect.tests/src/test/01-input/defaultOnlyWrite/Test.relast b/ragconnect.tests/src/test/01-input/defaultOnlyWrite/Test.relast
index e0fd7829b88b8fac04b64393e0262c69b4ac6a62..b9203c6ac13736308216c42326551e60dcd6c84c 100644
--- a/ragconnect.tests/src/test/01-input/defaultOnlyWrite/Test.relast
+++ b/ragconnect.tests/src/test/01-input/defaultOnlyWrite/Test.relast
@@ -1,8 +1,8 @@
 A ::= NativeTypesSyn* BoxedTypesSyn* <DriverInh:String>;
 // native types, synthesized
-NativeTypesSyn ::= <DriverSyn:String> /<IntValue:int>/ /<ShortValue:short>/ /<LongValue:long>/ /<FloatValue:float>/ /<DoubleValue:double>/ /<CharValue:char>/ /<StringValue:String>/ ;
+NativeTypesSyn ::= <DriverSyn:String> /<BooleanValue:boolean>/ /<IntValue:int>/ /<ShortValue:short>/ /<LongValue:long>/ /<FloatValue:float>/ /<DoubleValue:double>/ /<CharValue:char>/ /<StringValue:String>/ /<BooleanValueTransformed:boolean>/ /<IntValueTransformed:int>/ /<ShortValueTransformed:short>/ /<LongValueTransformed:long>/ /<FloatValueTransformed:float>/ /<DoubleValueTransformed:double>/ /<CharValueTransformed:char>/ /<StringValueTransformed:String>/ ;
 // boxed types, synthesized
-BoxedTypesSyn ::= <DriverSyn:String> /<IntValue:Integer>/ /<ShortValue:Short>/ /<LongValue:Long>/ /<FloatValue:Float>/ /<DoubleValue:Double>/ /<CharValue:Character>/ ;
+BoxedTypesSyn ::= <DriverSyn:String> /<BooleanValue:Boolean>/ /<IntValue:Integer>/ /<ShortValue:Short>/ /<LongValue:Long>/ /<FloatValue:Float>/ /<DoubleValue:Double>/ /<CharValue:Character>/ /<BooleanValueTransformed:Boolean>/ /<IntValueTransformed:Integer>/ /<ShortValueTransformed:Short>/ /<LongValueTransformed:Long>/ /<FloatValueTransformed:Float>/ /<DoubleValueTransformed:Double>/ /<CharValueTransformed:Character>/ ;
 
 // --- inherited attributes not supported ---
 //// native types, inherited
diff --git a/ragconnect.tests/src/test/01-input/errors/Errors.expected b/ragconnect.tests/src/test/01-input/errors/Errors.expected
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/ragconnect.tests/src/test/01-input/errors/Part.expected b/ragconnect.tests/src/test/01-input/errors/Part.expected
new file mode 100644
index 0000000000000000000000000000000000000000..70ea4a4baeaffb5b35df672c03e90821eb3cec82
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/errors/Part.expected
@@ -0,0 +1,11 @@
+Part1.connect Line 3, column 1: Receive definition already defined for DoubledValue
+Part1.connect Line 4, column 1: Receive definition already defined for DoubledValue
+Part1.connect Line 10, column 1: Receiving target token must not be an NTA token!
+Part1.connect Line 13, column 1: No suitable default mapping found for type java.util.List
+Part1.connect Line 13, column 1: to-type of last mapping (java.util.List) not assignable to type of the Token (String)!
+Part1.connect Line 16, column 1: to-type of last mapping (List) not assignable to type of the Token (String)!
+Part1.connect Line 19, column 1: to-type of last mapping (int) not assignable to type of the Token (String)!
+Part2.connect Line 5, column 1: Send definition already defined for DoubledValue
+Part2.connect Line 6, column 1: Send definition already defined for DoubledValue
+Part2.connect Line 17, column 1: The name of a dependency definition must not be equal to a list-node on the source
+Part2.connect Line 22, column 1: Dependency definition already defined for D with name DoubledValue
diff --git a/ragconnect.tests/src/test/01-input/errors/Part1.connect b/ragconnect.tests/src/test/01-input/errors/Part1.connect
new file mode 100644
index 0000000000000000000000000000000000000000..a1be3918b36ee19561a278639555db14efb8da06
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/errors/Part1.connect
@@ -0,0 +1,33 @@
+// --- update receive definitions ---
+// Error: there must not be two receive definitions for the same token
+receive B.DoubledValue ;
+receive B.DoubledValue using IntToInt ;
+
+// NOT HANDLED \\ Error: the token must be resolvable within the parent type
+// NOT HANDLED \\ receive B.NonExisting ;
+
+// Error: the Token must not be a TokenNTA (i.e., check for !Token.getNTA())
+receive B.ErrorNTA ;
+
+// Error: from-type of first mapping must be byte[] or a supported primitive type
+receive B.ErrorTypeOfFirstMapping using ListToList ;
+
+// Error: to-type of last mapping must be type of the Token
+receive B.ErrorTypeOfLastMapping using StringToList ;
+
+// Error: types of mappings must match (modulo inheritance)
+receive B.ErrorTypeMismatch using StringToList, IntToInt ;
+
+// --- update send definitions ---
+// NOT HANDLED \\ Error: the token must be resolvable within the parent type
+// NOT HANDLED \\ receive C.NonExisting ;
+
+// Error: Token must be a TokenNTA (i.e., check for Token.getNTA())
+send C.ErrorNotNTA ;
+
+// Error: from-type of first mapping must be type of Token
+send C.ErrorTypeOfFirstMapping using IntToInt ;
+
+// Error: to-type of last mapping must be byte[] or a supported primitive type
+send C.ErrorTypeOfLastMapping1 using StringToList ;
+send C.ErrorTypeOfLastMapping2 ;
diff --git a/ragconnect.tests/src/test/01-input/errors/Part2.connect b/ragconnect.tests/src/test/01-input/errors/Part2.connect
new file mode 100644
index 0000000000000000000000000000000000000000..4d1148260a36855528b37f8b45020e076ff9ff1e
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/errors/Part2.connect
@@ -0,0 +1,38 @@
+// Error: types of mappings must match (modulo inheritance)
+send C.ErrorTypeMismatch using StringToList, IntToInt ;
+
+// Error: no more than one send mapping for each TokenComponent
+send C.DoubledValue ;
+send C.DoubledValue using IntToInt ;
+
+// --- dependency definitions ---
+// NOT HANDLED \\ Error: Both, source and target must be resolvable within the parent type
+// NOT HANDLED \\ D.SourceNonExistingTarget canDependOn D.NonExisting as NonExistingTarget ;
+// NOT HANDLED \\ D.NonExisting canDependOn D.TargetNonExistingSource as NonExistingSource ;
+
+// Error: There must be a send update definition for the target token
+D.SourceNoWriteDef canDependOn D.TargetNoWriteDef as NoWriteDef ;
+
+// Error: The name of a dependency definition must not be equal to a list-node on the source
+D.SourceSameAsListNode canDependOn D.TargetSameAsListNode as MyList ;
+send D.TargetSameAsListNode;
+
+// Error: There must not be two dependency definitions with the same name
+D.SourceDoubledValue canDependOn D.TargetDoubledValue as DoubledValue ;
+D.SourceDoubledValue canDependOn D.TargetDoubledValue as DoubledValue ;
+send D.TargetDoubledValue;
+
+// --- mapping definitions ---
+ListToList maps java.util.List<String> list to java.util.List<String> {:
+  return list;
+:}
+
+StringToList maps String s to List<String> {:
+  java.util.List<String> result = new java.util.ArrayList<>();
+  result.add(s);
+  return result;
+:}
+
+IntToInt maps int number to int {:
+  return number + 1;
+:}
diff --git a/ragconnect.tests/src/test/01-input/errors/Errors.connect b/ragconnect.tests/src/test/01-input/errors/Standard.connect
similarity index 100%
rename from ragconnect.tests/src/test/01-input/errors/Errors.connect
rename to ragconnect.tests/src/test/01-input/errors/Standard.connect
diff --git a/ragconnect.tests/src/test/01-input/errors/Standard.expected b/ragconnect.tests/src/test/01-input/errors/Standard.expected
new file mode 100644
index 0000000000000000000000000000000000000000..3ee660570525834a63c41f26820966a7504f7789
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/errors/Standard.expected
@@ -0,0 +1,11 @@
+Standard.connect Line 3, column 1: Receive definition already defined for DoubledValue
+Standard.connect Line 4, column 1: Receive definition already defined for DoubledValue
+Standard.connect Line 10, column 1: Receiving target token must not be an NTA token!
+Standard.connect Line 13, column 1: No suitable default mapping found for type java.util.List
+Standard.connect Line 13, column 1: to-type of last mapping (java.util.List) not assignable to type of the Token (String)!
+Standard.connect Line 16, column 1: to-type of last mapping (List) not assignable to type of the Token (String)!
+Standard.connect Line 19, column 1: to-type of last mapping (int) not assignable to type of the Token (String)!
+Standard.connect Line 39, column 1: Send definition already defined for DoubledValue
+Standard.connect Line 40, column 1: Send definition already defined for DoubledValue
+Standard.connect Line 51, column 1: The name of a dependency definition must not be equal to a list-node on the source
+Standard.connect Line 56, column 1: Dependency definition already defined for D with name DoubledValue
diff --git a/ragconnect.tests/src/test/01-input/incremental/README.md b/ragconnect.tests/src/test/01-input/incremental/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..8fb46cc058138cbedeeda5eaa1a40ab97e088e28
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/incremental/README.md
@@ -0,0 +1,3 @@
+# Tutorial
+
+Idea: Test the example from the [documentation](https://jastadd.pages.st.inf.tu-dresden.de/ragconnect/using.html) with activated incremental dependency tracking
diff --git a/ragconnect.tests/src/test/01-input/incremental/Test.connect b/ragconnect.tests/src/test/01-input/incremental/Test.connect
new file mode 100644
index 0000000000000000000000000000000000000000..8135be4e3afc9801b45d858ce9bda9016351e4a4
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/incremental/Test.connect
@@ -0,0 +1,13 @@
+// endpoint definitions
+receive A.Input ;
+send A.OutputOnA ;
+send B.OutputOnB using Transformation ;
+
+// mapping definitions
+Transformation maps String s to String {:
+  return s + "Postfix";
+:}
+
+// dependency definitions
+A.OutputOnA canDependOn A.Input as dependencyA ;
+B.OutputOnB canDependOn A.Input as dependencyB ;
diff --git a/ragconnect.tests/src/test/01-input/incremental/Test.jadd b/ragconnect.tests/src/test/01-input/incremental/Test.jadd
new file mode 100644
index 0000000000000000000000000000000000000000..4cb9dbf4def3102924b530c58ece410e73fa255b
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/incremental/Test.jadd
@@ -0,0 +1,7 @@
+aspect Computation {
+  syn String A.getOutputOnA() = "a" + getInput();
+
+  syn String B.getOutputOnB() = "b" + input();
+  inh String B.input();
+  eq A.getB().input() = getInput();
+}
diff --git a/ragconnect.tests/src/test/01-input/incremental/Test.relast b/ragconnect.tests/src/test/01-input/incremental/Test.relast
new file mode 100644
index 0000000000000000000000000000000000000000..9009e2787814bb8020d1459fc5d9758f9cc1ca06
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/incremental/Test.relast
@@ -0,0 +1,2 @@
+A ::= <Input:String> /<OutputOnA:String>/ B* ;
+B ::= /<OutputOnB:String>/ ;
diff --git a/ragconnect.tests/src/test/01-input/list/README.md b/ragconnect.tests/src/test/01-input/list/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..7634a3d2069a10b5c07bef272f757bd5ee2df8c9
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/list/README.md
@@ -0,0 +1,34 @@
+# List
+
+Idea: send and receive lists of subtrees.
+Once without incremental evaluation (i.e., using manual dependencies), and the other time with incremental evaluation
+
+## Execution-Model
+
+```
+SenderRoot                                    ReceiverRoot
+|- A* ---( mqtt: a ) ---+------> A* --------------------|
+|                       \------> WidthAddFromA:A* ------|   
+|- SingleA:A*                ,-> FromSingleA:A* --------|
+     \---( mqtt: single-a ) -+-> WithAddFromSingleA:A* -|
+```
+
+## Execution-Trace (SendInitialValue)
+
+| Input | # | A* | WidthAddFromA | FromSingleA | WithAddFromSingleA:A |
+|---|---|---|---|---|---|
+| 0 | 1 | [] | [0] | [] | [0] |
+| 1 | 2 | [1] | [1] | [1] | [0,1] |
+| 1 | 2 | [1] | [1] | [1] | [0,1] |
+| 2 | 3 | [1,2] | [2] | [1,1,2] | [0,1,2] |
+| 3 | 4 | [1,2,3] | [3] | [1,1,2,1,2,3] | [0,1,2,3] |
+
+## Execution-Trace (OnlyUpdate)
+
+| Input | # | A* | WidthAddFromA | FromSingleA | WithAddFromSingleA:A |
+|---|---|---|---|---|---|
+| - | 0 | [] | [] | [] | [] |
+| 1 | 1 | [1] | [1] | [1] | [1] |
+| 1 | 1 | [1] | [1] | [1] | [1] |
+| 2 | 2 | [1,2] | [2] | [1,1,2] | [1,2] |
+| 3 | 3 | [1,2,3] | [3] | [1,1,2,1,2,3] | [1,2,3] |
diff --git a/ragconnect.tests/src/test/01-input/list/Test.connect b/ragconnect.tests/src/test/01-input/list/Test.connect
new file mode 100644
index 0000000000000000000000000000000000000000..c0efaea9d5ad307c0c43b7d577b1cff5884c0d8b
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/list/Test.connect
@@ -0,0 +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 ;
diff --git a/ragconnect.tests/src/test/01-input/list/Test.jadd b/ragconnect.tests/src/test/01-input/list/Test.jadd
new file mode 100644
index 0000000000000000000000000000000000000000..9d8ab5395a6c58e790023f902e53bae5763de76e
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/list/Test.jadd
@@ -0,0 +1,37 @@
+aspect Computation {
+  syn JastAddList<A> SenderRoot.getAList() {
+    var result = new JastAddList<A>();
+    for (int i = 1; i <= getInput(); i++) {
+      A a = new A().setID(i);
+      B b = new B().setID(i + 1);
+      a.addB(b);
+      result.addChild(a);
+    }
+    return result;
+  }
+  syn JastAddList<A> SenderRoot.getSingleAList() {
+    var result = new JastAddList<A>();
+    A a = new A().setID(getInput());
+    result.addChild(a);
+    return result;
+  }
+
+  syn boolean ASTNode.isNameable() = false;
+  eq Nameable.isNameable() = true;
+}
+
+aspect Testing {
+  class ReceiverRoot implements org.jastadd.ragconnect.tests.list.AbstractListTest.TestWrapperReceiverRoot {}
+  class A implements org.jastadd.ragconnect.tests.list.AbstractListTest.TestWrapperA {}
+  class B implements org.jastadd.ragconnect.tests.list.AbstractListTest.TestWrapperB {}
+  class JastAddList<T> implements org.jastadd.ragconnect.tests.list.AbstractListTest.TestWrapperJastAddList<T> {}
+}
+
+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 Nameable.customID() {
+    return getClass().getSimpleName() + getID();
+  }
+}
diff --git a/ragconnect.tests/src/test/01-input/list/Test.relast b/ragconnect.tests/src/test/01-input/list/Test.relast
new file mode 100644
index 0000000000000000000000000000000000000000..2680b5cd43fc68d144386fc4494a52cc1ed5e002
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/list/Test.relast
@@ -0,0 +1,8 @@
+Root ::= SenderRoot* ReceiverRoot* ;
+
+Nameable ::= <ID:int> ;
+SenderRoot : Nameable ::= <Input:int> /A*/ /SingleA:A*/ ;
+
+ReceiverRoot : Nameable ::= A* FromSingleA:A* WithAddFromA:A* WithAddFromSingleA:A* ;
+A : Nameable ::= B* ;
+B : Nameable ;
diff --git a/ragconnect.tests/src/test/01-input/list/TestDependencies.connect b/ragconnect.tests/src/test/01-input/list/TestDependencies.connect
new file mode 100644
index 0000000000000000000000000000000000000000..bce44247ea8db263a29ce66f08bb10346c3c4245
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/list/TestDependencies.connect
@@ -0,0 +1,2 @@
+SenderRoot.A canDependOn SenderRoot.Input as InputDependencyToA ;
+SenderRoot.SingleA canDependOn SenderRoot.Input as InputDependencyToSingleA ;
diff --git a/ragconnect.tests/src/test/01-input/mapping/README.md b/ragconnect.tests/src/test/01-input/mapping/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..4414f2eca557de36c2f3f0ff76f367d8fec9e20b
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/mapping/README.md
@@ -0,0 +1,3 @@
+# Default Mapping
+
+Idea: Check different numbers of sequential mappings
diff --git a/ragconnect.tests/src/test/01-input/mapping/Test.connect b/ragconnect.tests/src/test/01-input/mapping/Test.connect
new file mode 100644
index 0000000000000000000000000000000000000000..10f7c839afa6742ed20484e92c1108c6b6d3c1ca
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/mapping/Test.connect
@@ -0,0 +1,53 @@
+receive NativeTypes.IntValue using String2Int ;
+receive NativeTypes.ShortValue using String2Int, Int2Short ;
+receive NativeTypes.LongValue using String2Int, Int2Short, Short2Long ;
+receive NativeTypes.FloatValue using String2Int, Int2Short, Short2Long, Long2Float ;
+receive NativeTypes.DoubleValue using String2Int, Int2Short, Short2Long, Long2Float, Float2Double ;
+receive NativeTypes.CharValue using String2Int, Int2Short, Short2Long, Long2Float, Float2Double, Double2Char ;
+receive NativeTypes.BooleanValue using String2Int, Int2Short, Short2Long, Long2Float, Float2Double, Double2Char, Char2Boolean ;
+
+send NativeTypes.WriteIntValue using String2Int ;
+send NativeTypes.WriteShortValue using String2Int, Int2Short ;
+send NativeTypes.WriteLongValue using String2Int, Int2Short, Short2Long ;
+send NativeTypes.WriteFloatValue using String2Int, Int2Short, Short2Long, Long2Float ;
+send NativeTypes.WriteDoubleValue using String2Int, Int2Short, Short2Long, Long2Float, Float2Double ;
+send NativeTypes.WriteCharValue using String2Int, Int2Short, Short2Long, Long2Float, Float2Double, Double2Char ;
+send NativeTypes.WriteBooleanValue using String2Int, Int2Short, Short2Long, Long2Float, Float2Double, Double2Char, Char2Boolean ;
+
+receive BoxedTypes.IntValue using String2Int ;
+receive BoxedTypes.ShortValue using String2Int, Int2Short ;
+receive BoxedTypes.LongValue using String2Int, Int2Short, Short2Long ;
+receive BoxedTypes.FloatValue using String2Int, Int2Short, Short2Long, Long2Float ;
+receive BoxedTypes.DoubleValue using String2Int, Int2Short, Short2Long, Long2Float, Float2Double ;
+receive BoxedTypes.CharValue using String2Int, Int2Short, Short2Long, Long2Float, Float2Double, Double2Char ;
+receive BoxedTypes.BooleanValue using String2Int, Int2Short, Short2Long, Long2Float, Float2Double, Double2Char, Char2Boolean ;
+
+String2Int maps String s to int {:
+  return Integer.parseInt(s);
+:}
+Int2Short maps int i to short {:
+  return (short) i;
+:}
+Short2Long maps short s to long {:
+  return (long) s;
+:}
+Long2Float maps long l to float {:
+  return (float) (l + 0.01);
+:}
+Float2Double maps float f to double {:
+  return (double) f;
+:}
+Double2Char maps double d to char {:
+  return (char) ((int) d);
+:}
+Char2Boolean maps char c to boolean {:
+  return c > 21;
+:}
+
+NativeTypes.WriteIntValue canDependOn NativeTypes.Driver as nativeIntDependency;
+NativeTypes.WriteShortValue canDependOn NativeTypes.Driver as nativeShortDependency;
+NativeTypes.WriteLongValue canDependOn NativeTypes.Driver as nativeLongDependency;
+NativeTypes.WriteFloatValue canDependOn NativeTypes.Driver as nativeFloatDependency;
+NativeTypes.WriteDoubleValue canDependOn NativeTypes.Driver as nativeDoubleDependency;
+NativeTypes.WriteCharValue canDependOn NativeTypes.Driver as nativeCharDependency;
+NativeTypes.WriteBooleanValue canDependOn NativeTypes.Driver as nativeBooleanDependency;
diff --git a/ragconnect.tests/src/test/01-input/mapping/Test.jadd b/ragconnect.tests/src/test/01-input/mapping/Test.jadd
new file mode 100644
index 0000000000000000000000000000000000000000..1ac351565b5f370453e431847f3c42237ec93861
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/mapping/Test.jadd
@@ -0,0 +1,10 @@
+aspect Computation {
+  // native types, synthesized
+  syn String NativeTypes.getWriteIntValue() = getDriver();
+  syn String NativeTypes.getWriteShortValue() = getDriver();
+  syn String NativeTypes.getWriteLongValue() = getDriver();
+  syn String NativeTypes.getWriteFloatValue() = getDriver();
+  syn String NativeTypes.getWriteDoubleValue() = getDriver();
+  syn String NativeTypes.getWriteCharValue() = getDriver();
+  syn String NativeTypes.getWriteBooleanValue() = getDriver();
+}
diff --git a/ragconnect.tests/src/test/01-input/mapping/Test.relast b/ragconnect.tests/src/test/01-input/mapping/Test.relast
new file mode 100644
index 0000000000000000000000000000000000000000..718d16768e344af165efc4cfcd1653f9a92f6b02
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/mapping/Test.relast
@@ -0,0 +1,3 @@
+A ::= NativeTypes BoxedTypes ;
+NativeTypes ::= <IntValue:int> <ShortValue:short> <LongValue:long> <FloatValue:float> <DoubleValue:double> <CharValue:char> <BooleanValue:boolean> <Driver:String> /<WriteIntValue:String>/ /<WriteShortValue:String>/ /<WriteLongValue:String>/ /<WriteFloatValue:String>/ /<WriteDoubleValue:String>/ /<WriteCharValue:String>/ /<WriteBooleanValue:String>/;
+BoxedTypes ::= <IntValue:Integer> <ShortValue:Short> <LongValue:Long> <FloatValue:Float> <DoubleValue:Double> <CharValue:Character> <BooleanValue:Boolean> ;
diff --git a/ragconnect.tests/src/test/01-input/regression-tests/issue27/.gitignore b/ragconnect.tests/src/test/01-input/regression-tests/issue27/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..607b7610731b1ddf9dc05e605bd374a4b59ea360
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/regression-tests/issue27/.gitignore
@@ -0,0 +1 @@
+/*.noNewLine.*
diff --git a/ragconnect.tests/src/test/01-input/regression-tests/issue27/README.md b/ragconnect.tests/src/test/01-input/regression-tests/issue27/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..96595af1b32ba8487f2e8e663a944b0be52dff28
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/regression-tests/issue27/README.md
@@ -0,0 +1,3 @@
+# Issue27
+
+Regression test for failing parser when missing newline at end of specification.
diff --git a/ragconnect.tests/src/test/01-input/regression-tests/issue27/Test.connect b/ragconnect.tests/src/test/01-input/regression-tests/issue27/Test.connect
new file mode 100644
index 0000000000000000000000000000000000000000..28827bb62d7b801cf2f8bc605afc90c122f0cb6a
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/regression-tests/issue27/Test.connect
@@ -0,0 +1 @@
+receive A.Name ;
diff --git a/ragconnect.tests/src/test/01-input/regression-tests/issue27/Test.relast b/ragconnect.tests/src/test/01-input/regression-tests/issue27/Test.relast
new file mode 100644
index 0000000000000000000000000000000000000000..4c479a5756a37e42c74459c22006912445baa9b3
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/regression-tests/issue27/Test.relast
@@ -0,0 +1 @@
+A ::= <Name:String> ;
diff --git a/ragconnect.tests/src/test/01-input/singleList/README.md b/ragconnect.tests/src/test/01-input/singleList/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..6a76bb4d8457d1f246edaeb96d6d72048ef4ced1
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/singleList/README.md
@@ -0,0 +1,47 @@
+# Single List
+
+Idea: send and receive single values for lists of subtrees.
+Once without incremental evaluation (i.e., using manual dependencies), and the other time with incremental evaluation
+
+## Execution-Model
+
+```
+SenderRoot                         ReceiverRoot
+|- A1 --( a/1 ) --\                          |
+|- A2 --( a/2 ) --+=\  /--> A* --------------|
+|- A3 --( a/3 ) ----+==+--> WithAdd:A* ------|
+|- A4 --( a/4 ) --+=/                        |
+|- IO --( a/5 ) --/                          |
+                       /--> UsingWc:A* ------|
+              ( a/# ) -+--> UsingWcWithA:A* -|
+```
+
+## Computation
+
+A _n_ = Input _n_ + 1, e.g., A1 = Input1 + 1
+
+## 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] |
+
+*: (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] |
+
+*: (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
new file mode 100644
index 0000000000000000000000000000000000000000..21b2796544ae65e96da5b03f088e5f7966620a7f
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/singleList/Test.connect
@@ -0,0 +1,14 @@
+send tree SenderRoot.A1 ;
+send tree SenderRoot.A2 ;
+send tree SenderRoot.A3 ;
+send tree 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 ;
+
+IntToA maps int i to A {:
+    return new A().setID(i);
+:}
diff --git a/ragconnect.tests/src/test/01-input/singleList/Test.jadd b/ragconnect.tests/src/test/01-input/singleList/Test.jadd
new file mode 100644
index 0000000000000000000000000000000000000000..005b419bb1f625a460e219079dcd3a5361b5844d
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/singleList/Test.jadd
@@ -0,0 +1,25 @@
+aspect Computation {
+  syn A SenderRoot.getA1() = new A().setID(getInput1() + 1);
+  syn A SenderRoot.getA2() = new A().setID(getInput2() + 2);
+  syn A SenderRoot.getA3() = new A().setID(getInput3() + 3);
+  syn A SenderRoot.getA4() = new A().setID(getInput4() + 4);
+
+  syn boolean ASTNode.isNameable() = false;
+  eq Nameable.isNameable() = true;
+}
+
+aspect Testing {
+  class SenderRoot implements org.jastadd.ragconnect.tests.singleList.AbstractSingleListTest.TestWrapperSenderRoot {}
+  class ReceiverRoot implements org.jastadd.ragconnect.tests.singleList.AbstractSingleListTest.TestWrapperReceiverRoot {}
+  class A implements org.jastadd.ragconnect.tests.singleList.AbstractSingleListTest.TestWrapperA {}
+  class JastAddList<T> implements org.jastadd.ragconnect.tests.singleList.AbstractSingleListTest.TestWrapperJastAddList<T> {}
+}
+
+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 Nameable.customID() {
+    return getClass().getSimpleName() + getID();
+  }
+}
diff --git a/ragconnect.tests/src/test/01-input/singleList/Test.relast b/ragconnect.tests/src/test/01-input/singleList/Test.relast
new file mode 100644
index 0000000000000000000000000000000000000000..0d97bb6c6b2fa4be6a4628b5e7e45926817364d0
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/singleList/Test.relast
@@ -0,0 +1,11 @@
+Root ::= SenderRoot* ReceiverRoot* ;
+
+Nameable ::= <ID:int> ;
+SenderRoot : Nameable ::= <Input1:int> /A1:A/
+                          <Input2:int> /A2:A/
+                          <Input3:int> /A3:A/
+                          <Input4:int> /A4:A/
+                          <InOutput:int> ;
+
+ReceiverRoot : Nameable ::= A* UsingWildcardA:A* WithAddA:A* UsingWildcardWithAddA:A* ;
+A : Nameable ;
diff --git a/ragconnect.tests/src/test/01-input/singleList/TestDependencies.connect b/ragconnect.tests/src/test/01-input/singleList/TestDependencies.connect
new file mode 100644
index 0000000000000000000000000000000000000000..f1429b6aa1cee28ca3d2a1421060af74a28fbcf8
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/singleList/TestDependencies.connect
@@ -0,0 +1,4 @@
+SenderRoot.A1 canDependOn SenderRoot.Input1 as InputDependencyToA1 ;
+SenderRoot.A2 canDependOn SenderRoot.Input2 as InputDependencyToA2 ;
+SenderRoot.A3 canDependOn SenderRoot.Input3 as InputDependencyToA3 ;
+SenderRoot.A4 canDependOn SenderRoot.Input4 as InputDependencyToA4 ;
diff --git a/ragconnect.tests/src/test/01-input/singleListVariant/README.md b/ragconnect.tests/src/test/01-input/singleListVariant/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..4004ac42c136c25f2447efe5c1161c98754f32c1
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/singleListVariant/README.md
@@ -0,0 +1,61 @@
+# Single List
+
+Idea: send and receive single values for lists of subtrees.
+Test different variants of the structure/shape of the send/received value.
+
+## Execution-Model
+
+TODO: check again (old model copied from `singleList`)
+
+```
+SenderRoot/ReceiverRoot
+  |- T_Empty       ::= /* empty */ ;
+  |- T_Token       ::= <Value:String> ;
+  |- T_OneChild    ::= Other ;
+  |- T_OneOpt      ::= [Other] ;
+  |- T_OneList     ::= Other* ;
+  |- T_TwoChildren ::= Left:Other Right:Other ;
+  |- T_OneOfEach   ::= First:Other [Second:Other] Third:Other* <Fourth:String> ;
+  |- abstract T_Abstract ::= <ValueAbstract> ;
+```
+
+## Computation
+
+```
+T.ID = Input
+T.token = Input
+T.Other.ID = Input + 1
+```
+
+## Execution-Trace (SendInitialValue)
+
+Inputs:
+
+- 1
+- 1
+- 2
+- 3
+
+| 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] |
+
+*: (1:0, 2:0, 3:0, 4:0, 5:0)
diff --git a/ragconnect.tests/src/test/01-input/singleListVariant/Test.connect b/ragconnect.tests/src/test/01-input/singleListVariant/Test.connect
new file mode 100644
index 0000000000000000000000000000000000000000..23365109901425e8a6060c3ef3c2d8fe4cfc7656
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/singleListVariant/Test.connect
@@ -0,0 +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 ;
+
+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 tree 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 ;
diff --git a/ragconnect.tests/src/test/01-input/singleListVariant/Test.jadd b/ragconnect.tests/src/test/01-input/singleListVariant/Test.jadd
new file mode 100644
index 0000000000000000000000000000000000000000..10ed1174a4cbb99e46cd5ba582df829d547bd446
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/singleListVariant/Test.jadd
@@ -0,0 +1,75 @@
+aspect Computation {
+  syn T_Empty SenderRoot.getT_Empty() = new T_Empty().setID(getInput());
+  syn T_Token SenderRoot.getT_Token() = new T_Token().setID(getInput())
+    .setValue(Integer.toString(getInput()));
+  syn T_OneChild SenderRoot.getT_OneChild() {
+    T_OneChild result = new T_OneChild().setID(getInput());
+    result.setOther(createOther());
+    return result;
+  }
+  syn T_OneOpt SenderRoot.getT_OneOpt() {
+    T_OneOpt result = new T_OneOpt().setID(getInput());
+    if (getShouldSetOptAndList()) {
+      result.setOther(createOther());
+    }
+    return result;
+  }
+  syn T_OneList SenderRoot.getT_OneList() {
+    T_OneList result = new T_OneList().setID(getInput());
+    if (getShouldSetOptAndList()) {
+      result.addOther(createOther());
+    }
+    return result;
+  }
+  syn T_TwoChildren SenderRoot.getT_TwoChildren() {
+    T_TwoChildren result = new T_TwoChildren().setID(getInput());
+    result.setLeft(createOther());
+    result.setRight(createOther());
+    return result;
+  }
+  syn T_OneOfEach SenderRoot.getT_OneOfEach() {
+    T_OneOfEach result = new T_OneOfEach().setID(getInput());
+    result.setFirst(createOther());
+    if (getShouldSetOptAndList()) {
+      result.setSecond(createOther());
+      result.addThird(createOther());
+    }
+    result.setFourth(Integer.toString(getInput()));
+    return result;
+  }
+  syn T_Abstract SenderRoot.getT_Abstract() = new T_SubClass()
+    .setValueSub(Integer.toString(getInput()))
+    .setID(getInput())
+    .setValueAbstract(Integer.toString(getInput()));
+
+  private Other SenderRoot.createOther() {
+    return new Other().setID(getInput() + 1);
+  }
+
+  syn boolean ASTNode.isNameable() = false;
+  eq Nameable.isNameable() = true;
+}
+
+aspect Testing {
+  class SenderRoot implements org.jastadd.ragconnect.tests.singleListVariant.AbstractSingleListVariantTest.TestWrapperSenderRoot {}
+  class ReceiverRoot implements org.jastadd.ragconnect.tests.singleListVariant.AbstractSingleListVariantTest.TestWrapperReceiverRoot {}
+  class Other implements org.jastadd.ragconnect.tests.singleListVariant.AbstractSingleListVariantTest.TestWrapperOther {}
+  class T_Empty  implements org.jastadd.ragconnect.tests.singleListVariant.AbstractSingleListVariantTest.TestWrapperT_Empty {}
+  class T_Token  implements org.jastadd.ragconnect.tests.singleListVariant.AbstractSingleListVariantTest.TestWrapperT_Token {}
+  class T_OneChild  implements org.jastadd.ragconnect.tests.singleListVariant.AbstractSingleListVariantTest.TestWrapperT_OneChild {}
+  class T_OneOpt  implements org.jastadd.ragconnect.tests.singleListVariant.AbstractSingleListVariantTest.TestWrapperT_OneOpt {}
+  class T_OneList  implements org.jastadd.ragconnect.tests.singleListVariant.AbstractSingleListVariantTest.TestWrapperT_OneList {}
+  class T_TwoChildren  implements org.jastadd.ragconnect.tests.singleListVariant.AbstractSingleListVariantTest.TestWrapperT_TwoChildren {}
+  class T_OneOfEach  implements org.jastadd.ragconnect.tests.singleListVariant.AbstractSingleListVariantTest.TestWrapperT_OneOfEach {}
+  class T_Abstract  implements org.jastadd.ragconnect.tests.singleListVariant.AbstractSingleListVariantTest.TestWrapperT_Abstract {}
+  class JastAddList<T> implements org.jastadd.ragconnect.tests.singleListVariant.AbstractSingleListVariantTest.TestWrapperJastAddList<T> {}
+}
+
+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 Nameable.customID() {
+    return getClass().getSimpleName() + getID();
+  }
+}
diff --git a/ragconnect.tests/src/test/01-input/singleListVariant/Test.relast b/ragconnect.tests/src/test/01-input/singleListVariant/Test.relast
new file mode 100644
index 0000000000000000000000000000000000000000..4ac99ae124e19d182d9ad23b4c4ae65980272013
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/singleListVariant/Test.relast
@@ -0,0 +1,44 @@
+Root ::= SenderRoot* ReceiverRoot* ;
+
+Nameable ::= <ID:int> ;
+SenderRoot : Nameable ::= <Input:int> <ShouldSetOptAndList:boolean>
+/T_Empty/
+/T_Token/
+/T_OneChild/
+/T_OneOpt/
+/T_OneList/
+/T_TwoChildren/
+/T_OneOfEach/
+/T_Abstract/
+;
+
+ReceiverRoot : Nameable ::=
+T_Empty*
+T_Token*
+T_OneChild*
+T_OneOpt*
+T_OneList*
+T_TwoChildren*
+T_OneOfEach*
+T_Abstract*
+MyEmpty:T_Empty*
+EmptyWithAdd:T_Empty*
+TokenWithAdd:T_Token*
+OneChildWithAdd:T_OneChild*
+OneOptWithAdd:T_OneOpt*
+OneListWithAdd:T_OneList*
+TwoChildrenWithAdd:T_TwoChildren*
+OneOfEachWithAdd:T_OneOfEach*
+AbstractWithAdd:T_Abstract*
+;
+
+T_Empty : Nameable       ::= /* empty */ ;
+T_Token : Nameable       ::= <Value:String> ;
+T_OneChild : Nameable    ::= Other ;
+T_OneOpt : Nameable      ::= [Other] ;
+T_OneList : Nameable     ::= Other* ;
+T_TwoChildren : Nameable ::= Left:Other Right:Other ;
+T_OneOfEach : Nameable   ::= First:Other [Second:Other] Third:Other* <Fourth:String> ;
+abstract T_Abstract : Nameable ::= <ValueAbstract>;
+T_SubClass : T_Abstract  ::= <ValueSub> ;
+Other : Nameable ;
diff --git a/ragconnect.tests/src/test/01-input/singleListVariant/TestDependencies.connect b/ragconnect.tests/src/test/01-input/singleListVariant/TestDependencies.connect
new file mode 100644
index 0000000000000000000000000000000000000000..2ac87895268cfa631fcfee65a8c2c90f4c13b3c7
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/singleListVariant/TestDependencies.connect
@@ -0,0 +1,16 @@
+SenderRoot.T_Empty canDependOn SenderRoot.Input as InputDependencyToT_Empty ;
+SenderRoot.T_Token canDependOn SenderRoot.Input as InputDependencyToT_Token ;
+SenderRoot.T_OneChild canDependOn SenderRoot.Input as InputDependencyToT_OneChild ;
+
+SenderRoot.T_OneOpt canDependOn SenderRoot.ShouldSetOptAndList as ShouldSetOptAndListDependencyToT_OneOpt ;
+SenderRoot.T_OneOpt canDependOn SenderRoot.Input as InputDependencyToT_OneOpt ;
+
+SenderRoot.T_OneList canDependOn SenderRoot.ShouldSetOptAndList as ShouldSetOptAndListDependencyToT_OneList ;
+SenderRoot.T_OneList canDependOn SenderRoot.Input as InputDependencyToT_OneList ;
+
+SenderRoot.T_TwoChildren canDependOn SenderRoot.Input as InputDependencyToT_TwoChildren ;
+
+SenderRoot.T_OneOfEach canDependOn SenderRoot.ShouldSetOptAndList as ShouldSetOptAndListDependencyToT_OneOfEach ;
+SenderRoot.T_OneOfEach canDependOn SenderRoot.Input as InputDependencyToT_OneOfEach ;
+
+SenderRoot.T_Abstract canDependOn SenderRoot.Input as InputDependencyToT_Abstract ;
diff --git a/ragconnect.tests/src/test/01-input/tree/README.md b/ragconnect.tests/src/test/01-input/tree/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..060a0e39663f01d33bffb960c316c7de8da59f68
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/tree/README.md
@@ -0,0 +1,4 @@
+# Tree
+
+Idea: send and receive subtrees, test different relations within the subtree which was sent.
+Once without incremental evaluation (i.e., using manual dependencies), and the other time with incremental evaluation
diff --git a/ragconnect.tests/src/test/01-input/tree/Test.connect b/ragconnect.tests/src/test/01-input/tree/Test.connect
new file mode 100644
index 0000000000000000000000000000000000000000..857f6297f12249451b026051201ebfc004c2f6a6
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/tree/Test.connect
@@ -0,0 +1,2 @@
+send tree SenderRoot.Alfa ;
+receive tree ReceiverRoot.Alfa ;
diff --git a/ragconnect.tests/src/test/01-input/tree/Test.jadd b/ragconnect.tests/src/test/01-input/tree/Test.jadd
new file mode 100644
index 0000000000000000000000000000000000000000..475e20f88bbe93daadb59f153b49f4e8d5ac99b4
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/tree/Test.jadd
@@ -0,0 +1,81 @@
+aspect Computation {
+  syn Alfa SenderRoot.getAlfa() {
+    Alfa result = new Alfa();
+    for (int i = 0; i < 4; i++) {
+      result.addBravo(new Bravo().setID(i));
+      result.addCharlie(new Charlie().setID(i));
+      result.addDelta(new Delta().setID(i));
+    }
+    Bravo inputBravo = result.getBravo(getInput());
+    Charlie inputCharlie = result.getCharlie(getInput());
+    Delta inputDelta = result.getDelta(getInput());
+
+    // rel Alfa.MyBravo -> Bravo ;
+    result.setMyBravo(inputBravo);
+    // rel Alfa.OptionalBravo? -> Bravo ;
+    result.setOptionalBravo(inputBravo);
+    // rel Alfa.MultiBravo* -> Bravo ;
+    result.addMultiBravo(inputBravo);
+    result.addMultiBravo(result.getBravo(getInput() + 1));
+
+    // rel Charlie.MyAlfa? -> Alfa ;
+    inputCharlie.setMyAlfa(result);
+
+    // rel Alfa.SingleBi1Delta <-> Delta.SingleBack1Alfa? ;
+    result.setSingleBi1Delta(inputDelta);
+    // rel Alfa.MultiBi2Delta* <-> Delta.SingleBack2Alfa? ;
+    result.addMultiBi2Delta(inputDelta);
+    result.addMultiBi2Delta(result.getDelta(getInput() + 1));
+    // rel Alfa.MultiBi3Delta* <-> Delta.MultiBack3Alfa* ;
+    result.addMultiBi3Delta(inputDelta);
+    result.addMultiBi3Delta(result.getDelta(getInput() + 1));
+
+    // rel Alfa.Myself -> Alfa ;
+    result.setMyself(result);
+
+    // rel Bravo.MyCharlie -> Charlie ;
+    for (int i = 0; i < 4; i++) {
+      if (i == getInput()) {
+        result.getBravo(i).setMyCharlie(inputCharlie);
+      } else {
+        result.getBravo(i).setMyCharlie(result.getCharlie(0));
+      }
+    }
+    // rel Bravo.OptionalCharlie? -> Charlie ;
+    inputBravo.setOptionalCharlie(inputCharlie);
+    // rel Bravo.MultiCharlie* -> Charlie ;
+    inputBravo.addMultiCharlie(inputCharlie);
+    inputBravo.addMultiCharlie(result.getCharlie(getInput() + 1));
+
+    // rel Bravo.SingleBi1Delta? <-> Delta.SingleBack1Bravo? ;
+    inputBravo.setSingleBi1Delta(inputDelta);
+    // rel Bravo.MultiBi2Delta* <-> Delta.SingleBack2Bravo? ;
+    inputBravo.addMultiBi2Delta(inputDelta);
+    inputBravo.addMultiBi2Delta(result.getDelta(getInput() + 1));
+    // rel Bravo.MultiBi3Delta* <-> Delta.MultiBack3Bravo* ;
+    inputBravo.addMultiBi3Delta(inputDelta);
+    inputBravo.addMultiBi3Delta(result.getDelta(getInput() + 1));
+    result.getBravo(getInput() + 1).addMultiBi3Delta(inputDelta);
+    result.getBravo(getInput() + 1).addMultiBi3Delta(result.getDelta(getInput() + 1));
+
+    return result;
+  }
+
+  syn boolean ASTNode.isNameable() = false;
+  eq Nameable.isNameable() = true;
+}
+
+aspect Testing {
+  class ReceiverRoot implements org.jastadd.ragconnect.tests.tree.AbstractTreeTest.TestWrapperReceiverRoot {}
+  class Alfa implements org.jastadd.ragconnect.tests.tree.AbstractTreeTest.TestWrapperAlfa {}
+  class Bravo implements org.jastadd.ragconnect.tests.tree.AbstractTreeTest.TestWrapperBravo {}
+  class Charlie implements org.jastadd.ragconnect.tests.tree.AbstractTreeTest.TestWrapperCharlie {}
+  class Delta implements org.jastadd.ragconnect.tests.tree.AbstractTreeTest.TestWrapperDelta {}
+}
+
+aspect NameResolution {
+  @Override
+  protected String Nameable.customID() {
+    return getClass().getSimpleName() + getID();
+  }
+}
diff --git a/ragconnect.tests/src/test/01-input/tree/Test.relast b/ragconnect.tests/src/test/01-input/tree/Test.relast
new file mode 100644
index 0000000000000000000000000000000000000000..f65444660383fa1f44d3b4519f01d16098164bf3
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/tree/Test.relast
@@ -0,0 +1,36 @@
+Root ::= SenderRoot* ReceiverRoot* ;
+
+Nameable ::= <ID:int> ;
+SenderRoot : Nameable ::= <Input:int> /Alfa/ ;
+
+ReceiverRoot : Nameable ::= Alfa ;
+Alfa : Nameable ::= <Value:String> Bravo* Charlie* Delta* ;
+
+Bravo : Nameable ;
+Charlie : Nameable ;
+Delta : Nameable ;
+
+rel Alfa.Myself -> Alfa ;
+
+// Alfa -> Bravo
+rel Alfa.MyBravo -> Bravo ;
+rel Alfa.OptionalBravo? -> Bravo ;
+rel Alfa.MultiBravo* -> Bravo ;
+
+// Charlie -> Alfa
+rel Charlie.MyAlfa? -> Alfa ;
+
+// Alfa <-> Delta
+rel Alfa.SingleBi1Delta <-> Delta.SingleBack1Alfa? ;
+rel Alfa.MultiBi2Delta* <-> Delta.SingleBack2Alfa? ;
+rel Alfa.MultiBi3Delta* <-> Delta.MultiBack3Alfa* ;
+
+// Bravo -> Charlie
+rel Bravo.MyCharlie -> Charlie ;
+rel Bravo.OptionalCharlie? -> Charlie ;
+rel Bravo.MultiCharlie* -> Charlie ;
+
+// Bravo <-> Delta
+rel Bravo.SingleBi1Delta? <-> Delta.SingleBack1Bravo? ;
+rel Bravo.MultiBi2Delta* <-> Delta.SingleBack2Bravo? ;
+rel Bravo.MultiBi3Delta* <-> Delta.MultiBack3Bravo* ;
diff --git a/ragconnect.tests/src/test/01-input/tree/TestDependencies.connect b/ragconnect.tests/src/test/01-input/tree/TestDependencies.connect
new file mode 100644
index 0000000000000000000000000000000000000000..aa5df696a54404332115d0069279a07f886f2e22
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/tree/TestDependencies.connect
@@ -0,0 +1 @@
+SenderRoot.Alfa canDependOn SenderRoot.Input as InputDependency ;
diff --git a/ragconnect.tests/src/test/01-input/treeAllowedTokens/README.md b/ragconnect.tests/src/test/01-input/treeAllowedTokens/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..c215115106a71daf358568368426b33421cfdd65
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/treeAllowedTokens/README.md
@@ -0,0 +1,4 @@
+# Tree Allowed Tokens
+
+Idea: send and receive subtrees, test different token types within the subtree which was sent.
+Once without incremental evaluation (i.e., using manual dependencies), and the other time with incremental evaluation
diff --git a/ragconnect.tests/src/test/01-input/treeAllowedTokens/Test.connect b/ragconnect.tests/src/test/01-input/treeAllowedTokens/Test.connect
new file mode 100644
index 0000000000000000000000000000000000000000..d872ce56c59097e678e6cff0b25ec422130b6129
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/treeAllowedTokens/Test.connect
@@ -0,0 +1,26 @@
+send tree SenderRoot.Alfa ;
+receive tree 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 ;
+
+Alfa2String maps Alfa alfa to String {:
+  StringBuilder sb = new StringBuilder();
+  sb.append(alfa.getID()).append(":").append(alfa.getStringValue()).append(";");
+  return sb.toString();
+:}
+
+String2Alfa maps String s to Alfa {:
+  int colonIndex = s.indexOf(":");
+  int id = Integer.parseInt(s.substring(0, colonIndex));
+  String value = s.substring(colonIndex + 1, s.length() - 1);
+  Alfa result = new Alfa();
+  result.setID(id);
+  result.setStringValue(value);
+  return result;
+:}
diff --git a/ragconnect.tests/src/test/01-input/treeAllowedTokens/Test.jadd b/ragconnect.tests/src/test/01-input/treeAllowedTokens/Test.jadd
new file mode 100644
index 0000000000000000000000000000000000000000..5ac99ca749c7f962416f4c34bb8544a2fdd2e38f
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/treeAllowedTokens/Test.jadd
@@ -0,0 +1,48 @@
+aspect Computation {
+  syn Alfa SenderRoot.getAlfa() {
+    Alfa result = new Alfa();
+
+    // Inputs: <Flag:boolean> <Input1WhenFlagIsTrue:int> <Input1WhenFlagIsFalse:int> <Input2:String> <Input3:double>
+
+    result.setBooleanValue(getFlag()); // boolean
+    result.setIntValue(getFlag() ? getInput1WhenFlagIsTrue() : getInput1WhenFlagIsFalse()); // int
+    result.setShortValue((short) (getFlag() ? getInput1WhenFlagIsTrue() : getInput1WhenFlagIsFalse())); // short
+    result.setLongValue(getFlag() ? getInput1WhenFlagIsTrue() : getInput1WhenFlagIsFalse()); // long
+    result.setFloatValue((float) getInput3()); // float
+    result.setDoubleValue(getInput3()); // double
+    result.setCharValue(getInput2().charAt(0)); // char
+    result.setStringValue(getInput2()); // String
+    // needed format: 2011-12-03T10:15:30Z
+    result.setInstantValue(java.time.Instant.parse(getInput2())); // java.time.Instant
+    // setting days
+    result.setPeriodValue(java.time.Period.of(0, 0, getFlag() ? getInput1WhenFlagIsTrue() : getInput1WhenFlagIsFalse())); // java.time.Period
+    result.setEnumValue(getFlag() ? MyEnum.TRUE : MyEnum.FALSE); // MyEnum
+
+    return result;
+  }
+
+  syn Alfa SenderRoot.getAlfaPrimitive() {
+    Alfa result = new Alfa();
+    result.setStringValue(getInput2());
+    return result;
+  }
+
+  syn boolean ASTNode.isNameable() = false;
+  eq Nameable.isNameable() = true;
+}
+
+aspect Enum {
+  public enum MyEnum { FALSE, TRUE; }
+}
+aspect Testing {
+  class ReceiverRoot implements org.jastadd.ragconnect.tests.treeAllowedTokens.AbstractTreeAllowedTokensTest.TestWrapperReceiverRoot {}
+  class Alfa implements org.jastadd.ragconnect.tests.treeAllowedTokens.AbstractTreeAllowedTokensTest.TestWrapperAlfa {}
+}
+
+aspect NameResolution {
+  // this should most likely be generated
+  @Override
+  protected String Alfa.customID() {
+    return "0";
+  }
+}
diff --git a/ragconnect.tests/src/test/01-input/treeAllowedTokens/Test.relast b/ragconnect.tests/src/test/01-input/treeAllowedTokens/Test.relast
new file mode 100644
index 0000000000000000000000000000000000000000..591ebb22441a29b5a8561b0ea539367a44d9d2e8
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/treeAllowedTokens/Test.relast
@@ -0,0 +1,8 @@
+Root ::= SenderRoot* ReceiverRoot* ;
+
+Nameable ::= <ID:int> ;
+SenderRoot : Nameable ::= <Flag:boolean> <Input1WhenFlagIsTrue:int> <Input1WhenFlagIsFalse:int> <Input2:String> <Input3:double> /Alfa/ /AlfaPrimitive:Alfa/ ;
+
+ReceiverRoot : Nameable ::= Alfa AlfaPrimitive:Alfa ;
+
+Alfa : Nameable ::= <BooleanValue:boolean> <IntValue:int> <ShortValue:short> <LongValue:long> <FloatValue:float> <DoubleValue:double> <CharValue:char> <StringValue:String> <InstantValue:java.time.Instant> <PeriodValue:java.time.Period> <EnumValue:MyEnum> ;
diff --git a/ragconnect.tests/src/test/01-input/treeAllowedTokens/TestDependencies.connect b/ragconnect.tests/src/test/01-input/treeAllowedTokens/TestDependencies.connect
new file mode 100644
index 0000000000000000000000000000000000000000..afa4a832ca4bde7ca584c8b1d8f0fa69ee0955a8
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/treeAllowedTokens/TestDependencies.connect
@@ -0,0 +1,7 @@
+SenderRoot.Alfa canDependOn SenderRoot.Flag as FlagDependency ;
+SenderRoot.Alfa canDependOn SenderRoot.Input1WhenFlagIsTrue as Input1WhenFlagIsTrueDependency ;
+SenderRoot.Alfa canDependOn SenderRoot.Input1WhenFlagIsFalse as Input1WhenFlagIsFalseDependency ;
+SenderRoot.Alfa canDependOn SenderRoot.Input2 as Input2Dependency ;
+SenderRoot.Alfa canDependOn SenderRoot.Input3 as Input3Dependency ;
+
+SenderRoot.AlfaPrimitive canDependOn SenderRoot.Input2 as PrimitiveInput2Dependency ;
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 36baf2cf5ffda1fbb6225daf8f054a40c3a2c331..330c950b8114cb1364ea30a86cd76c7dc81eb4a2 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,10 +1,7 @@
 package org.jastadd.ragconnect.tests;
 
 import defaultOnlyRead.ast.MqttHandler;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.Tag;
-import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.*;
 
 import java.io.IOException;
 import java.util.concurrent.TimeUnit;
@@ -17,21 +14,23 @@ import java.util.concurrent.TimeUnit;
 @Tag("mqtt")
 public abstract class AbstractMqttTest {
 
-  static boolean checkDone = false;
-  static Boolean checkResult;
+  private static boolean checkDone = false;
+  protected static MqttHandler publisher;
 
   @BeforeAll
-  public static void checkMqttConnection() {
+  public static void createPublishAndOnceCheckMqttConnection() {
+    boolean checkResult;
+    try {
+      publisher = new MqttHandler("Publisher")
+          .dontSendWelcomeMessage()
+          .setHost(TestUtils.getMqttHost());
+      checkResult = true;
+    } catch (IOException e) {
+      checkResult = false;
+    }
     if (!checkDone) {
       checkDone = true;
-      try {
-        checkResult = new MqttHandler()
-            .dontSendWelcomeMessage()
-            .setHost(TestUtils.getMqttHost())
-            .waitUntilReady(2, TimeUnit.SECONDS);
-      } catch (IOException e) {
-        checkResult = false;
-      }
+      checkResult &= publisher.waitUntilReady(2, TimeUnit.SECONDS);
     }
     if (!checkResult) {
       throw new IllegalStateException("Mqtt Broker not ready!");
@@ -52,6 +51,10 @@ public abstract class AbstractMqttTest {
     communicateSendInitialValue();
   }
 
+  /**
+   * Actual test code for communication when sending initial value.
+   * @throws InterruptedException because of TestUtils.waitForMqtt()
+   */
   protected abstract void communicateSendInitialValue() throws InterruptedException;
 
   @Tag("mqtt")
@@ -63,9 +66,15 @@ public abstract class AbstractMqttTest {
     communicateOnlyUpdatedValue();
   }
 
+  /**
+   * Actual test code for communication without sending any value upon connecting.
+   * @throws InterruptedException because of TestUtils.waitForMqtt()
+   */
   protected abstract void communicateOnlyUpdatedValue() throws InterruptedException;
 
-
+  /**
+   * Create the model, and set required default values.
+   */
   protected abstract void createModel();
 
   /**
@@ -79,9 +88,9 @@ public abstract class AbstractMqttTest {
    *
    * And then add dependencies, initialise receiver, add connections to those receivers,
    * and finally call generated connect* methods on model elements.
-   * @param writeCurrentValue
+   * @param writeCurrentValue if the initial/current value shall be sent upon connecting
    */
-  protected abstract void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException;
+  protected abstract void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException, InterruptedException;
 
   @AfterEach
   public void alwaysCloseConnections() {
@@ -101,4 +110,11 @@ public abstract class AbstractMqttTest {
    */
   protected abstract void closeConnections();
 
+  @AfterAll
+  public static void closePublisher() {
+    if (publisher != null) {
+      publisher.close();
+    }
+  }
+
 }
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 65302cfa47f0cd83788385ab0d644d2627868a9c..d9cf9d92a456a02cd2acba9e5f8990527d5842e9 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/DefaultOnlyReadTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/DefaultOnlyReadTest.java
@@ -2,14 +2,15 @@ package org.jastadd.ragconnect.tests;
 
 import defaultOnlyRead.ast.A;
 import defaultOnlyRead.ast.BoxedTypes;
-import defaultOnlyRead.ast.MqttHandler;
 import defaultOnlyRead.ast.NativeTypes;
+import org.junit.jupiter.api.Test;
 
 import java.io.IOException;
-import java.nio.ByteBuffer;
+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.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
@@ -20,7 +21,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
  */
 public class DefaultOnlyReadTest extends AbstractMqttTest {
 
-  private static final String TOPIC_NATIVE_INT = "native/int";
+  private static final String TOPIC_NATIVE_INT = "native/boolean";
+  private static final String TOPIC_NATIVE_BOOLEAN = "native/int";
   private static final String TOPIC_NATIVE_SHORT = "native/short";
   private static final String TOPIC_NATIVE_LONG = "native/long";
   private static final String TOPIC_NATIVE_FLOAT = "native/float";
@@ -28,6 +30,7 @@ public class DefaultOnlyReadTest extends AbstractMqttTest {
   private static final String TOPIC_NATIVE_CHAR = "native/char";
   private static final String TOPIC_NATIVE_STRING = "native/string";
 
+  private static final String TOPIC_BOXED_BOOLEAN = "boxed/boolean";
   private static final String TOPIC_BOXED_INTEGER = "boxed/Integer";
   private static final String TOPIC_BOXED_SHORT = "boxed/Short";
   private static final String TOPIC_BOXED_LONG = "boxed/Long";
@@ -40,42 +43,74 @@ public class DefaultOnlyReadTest extends AbstractMqttTest {
   private NativeTypes floats;
   private NativeTypes chars;
   private BoxedTypes allBoxed;
-  private MqttHandler sender;
+
+  @Test
+  public void checkNotJacksonReference() {
+    testJaddContainReferenceToJackson(
+        Paths.get("src", "test",
+            "02-after-ragconnect", "defaultOnlyRead", "RagConnect.jadd"), false);
+  }
 
   @Override
-  public void closeConnections() {
-    if (sender != null) {
-      sender.close();
-    }
-    if (model != null) {
-      model.ragconnectCloseConnections();
-    }
+  protected void createModel() {
+    model = new A();
+    integers = new NativeTypes();
+    model.addNativeTypes(integers);
+    floats = new NativeTypes();
+    model.addNativeTypes(floats);
+    chars = new NativeTypes();
+    model.addNativeTypes(chars);
+    allBoxed = new BoxedTypes();
+    model.addBoxedTypes(allBoxed);
   }
 
   @Override
   protected void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException {
     model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS);
 
-    integers.connectIntValue(mqttUri(TOPIC_NATIVE_INT));
-    integers.connectShortValue(mqttUri(TOPIC_NATIVE_SHORT));
-    integers.connectLongValue(mqttUri(TOPIC_NATIVE_LONG));
-    floats.connectFloatValue(mqttUri(TOPIC_NATIVE_FLOAT));
-    floats.connectDoubleValue(mqttUri(TOPIC_NATIVE_DOUBLE));
-    chars.connectCharValue(mqttUri(TOPIC_NATIVE_CHAR));
-    chars.connectStringValue(mqttUri(TOPIC_NATIVE_STRING));
-    allBoxed.connectIntValue(mqttUri(TOPIC_BOXED_INTEGER));
-    allBoxed.connectShortValue(mqttUri(TOPIC_BOXED_SHORT));
-    allBoxed.connectLongValue(mqttUri(TOPIC_BOXED_LONG));
-    allBoxed.connectFloatValue(mqttUri(TOPIC_BOXED_FLOAT));
-    allBoxed.connectDoubleValue(mqttUri(TOPIC_BOXED_DOUBLE));
-    allBoxed.connectCharValue(mqttUri(TOPIC_BOXED_CHARACTER));
-
-    sender = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost());
-    assertTrue(sender.waitUntilReady(2, TimeUnit.SECONDS));
+    assertTrue(integers.connectBooleanValue(mqttUri(TOPIC_NATIVE_BOOLEAN)));
+    assertTrue(integers.connectIntValue(mqttUri(TOPIC_NATIVE_INT)));
+    assertTrue(integers.connectShortValue(mqttUri(TOPIC_NATIVE_SHORT)));
+    assertTrue(integers.connectLongValue(mqttUri(TOPIC_NATIVE_LONG)));
+    assertTrue(floats.connectFloatValue(mqttUri(TOPIC_NATIVE_FLOAT)));
+    assertTrue(floats.connectDoubleValue(mqttUri(TOPIC_NATIVE_DOUBLE)));
+    assertTrue(chars.connectCharValue(mqttUri(TOPIC_NATIVE_CHAR)));
+    assertTrue(chars.connectStringValue(mqttUri(TOPIC_NATIVE_STRING)));
+
+    assertTrue(integers.connectBooleanValueTransformed(mqttUri(TOPIC_NATIVE_BOOLEAN)));
+    assertTrue(integers.connectIntValueTransformed(mqttUri(TOPIC_NATIVE_INT)));
+    assertTrue(integers.connectShortValueTransformed(mqttUri(TOPIC_NATIVE_SHORT)));
+    assertTrue(integers.connectLongValueTransformed(mqttUri(TOPIC_NATIVE_LONG)));
+    assertTrue(floats.connectFloatValueTransformed(mqttUri(TOPIC_NATIVE_FLOAT)));
+    assertTrue(floats.connectDoubleValueTransformed(mqttUri(TOPIC_NATIVE_DOUBLE)));
+    assertTrue(chars.connectCharValueTransformed(mqttUri(TOPIC_NATIVE_CHAR)));
+    assertTrue(chars.connectStringValueTransformed(mqttUri(TOPIC_NATIVE_STRING)));
+
+    assertTrue(allBoxed.connectBooleanValue(mqttUri(TOPIC_BOXED_BOOLEAN)));
+    assertTrue(allBoxed.connectIntValue(mqttUri(TOPIC_BOXED_INTEGER)));
+    assertTrue(allBoxed.connectShortValue(mqttUri(TOPIC_BOXED_SHORT)));
+    assertTrue(allBoxed.connectLongValue(mqttUri(TOPIC_BOXED_LONG)));
+    assertTrue(allBoxed.connectFloatValue(mqttUri(TOPIC_BOXED_FLOAT)));
+    assertTrue(allBoxed.connectDoubleValue(mqttUri(TOPIC_BOXED_DOUBLE)));
+    assertTrue(allBoxed.connectCharValue(mqttUri(TOPIC_BOXED_CHARACTER)));
+
+    assertTrue(allBoxed.connectBooleanValueTransformed(mqttUri(TOPIC_BOXED_BOOLEAN)));
+    assertTrue(allBoxed.connectIntValueTransformed(mqttUri(TOPIC_BOXED_INTEGER)));
+    assertTrue(allBoxed.connectShortValueTransformed(mqttUri(TOPIC_BOXED_SHORT)));
+    assertTrue(allBoxed.connectLongValueTransformed(mqttUri(TOPIC_BOXED_LONG)));
+    assertTrue(allBoxed.connectFloatValueTransformed(mqttUri(TOPIC_BOXED_FLOAT)));
+    assertTrue(allBoxed.connectDoubleValueTransformed(mqttUri(TOPIC_BOXED_DOUBLE)));
+    assertTrue(allBoxed.connectCharValueTransformed(mqttUri(TOPIC_BOXED_CHARACTER)));
+  }
+
+  @Override
+  protected void communicateSendInitialValue() {
+    // empty
   }
 
   @Override
   protected void communicateOnlyUpdatedValue() throws InterruptedException {
+    final boolean expectedBooleanValue = true;
     final int expectedIntValue = 1;
     final short expectedShortValue = 2;
     final long expectedLongValue = 3L;
@@ -84,23 +119,26 @@ public class DefaultOnlyReadTest extends AbstractMqttTest {
     final char expectedCharValue = 'c';
     final String expectedStringValue = "6.3";
 
-    sender.publish(TOPIC_NATIVE_INT, ByteBuffer.allocate(4).putInt(expectedIntValue).array());
-    sender.publish(TOPIC_NATIVE_SHORT, ByteBuffer.allocate(2).putShort(expectedShortValue).array());
-    sender.publish(TOPIC_NATIVE_LONG, ByteBuffer.allocate(8).putLong(expectedLongValue).array());
-    sender.publish(TOPIC_NATIVE_FLOAT, ByteBuffer.allocate(4).putFloat(expectedFloatValue).array());
-    sender.publish(TOPIC_NATIVE_DOUBLE, ByteBuffer.allocate(8).putDouble(expectedDoubleValue).array());
-    sender.publish(TOPIC_NATIVE_CHAR, ByteBuffer.allocate(2).putChar(expectedCharValue).array());
-    sender.publish(TOPIC_NATIVE_STRING, expectedStringValue.getBytes());
-
-    sender.publish(TOPIC_BOXED_INTEGER, ByteBuffer.allocate(4).putInt(expectedIntValue).array());
-    sender.publish(TOPIC_BOXED_SHORT, ByteBuffer.allocate(2).putShort(expectedShortValue).array());
-    sender.publish(TOPIC_BOXED_LONG, ByteBuffer.allocate(8).putLong(expectedLongValue).array());
-    sender.publish(TOPIC_BOXED_FLOAT, ByteBuffer.allocate(4).putFloat(expectedFloatValue).array());
-    sender.publish(TOPIC_BOXED_DOUBLE, ByteBuffer.allocate(8).putDouble(expectedDoubleValue).array());
-    sender.publish(TOPIC_BOXED_CHARACTER, ByteBuffer.allocate(2).putChar(expectedCharValue).array());
+    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));
 
     TestUtils.waitForMqtt();
 
+    assertEquals(expectedBooleanValue, integers.getBooleanValue());
     assertEquals(expectedIntValue, integers.getIntValue());
     assertEquals(expectedShortValue, integers.getShortValue());
     assertEquals(expectedLongValue, integers.getLongValue());
@@ -109,30 +147,37 @@ public class DefaultOnlyReadTest extends AbstractMqttTest {
     assertEquals(expectedCharValue, chars.getCharValue());
     assertEquals(expectedStringValue, chars.getStringValue());
 
+    assertEquals(expectedBooleanValue, integers.getBooleanValueTransformed());
+    assertEquals(expectedIntValue, integers.getIntValueTransformed());
+    assertEquals(expectedShortValue, integers.getShortValueTransformed());
+    assertEquals(expectedLongValue, integers.getLongValueTransformed());
+    assertEquals(expectedFloatValue, floats.getFloatValueTransformed(), TestUtils.DELTA);
+    assertEquals(expectedDoubleValue, floats.getDoubleValueTransformed(), TestUtils.DELTA);
+    assertEquals(expectedCharValue, chars.getCharValueTransformed());
+    assertEquals(expectedStringValue, chars.getStringValueTransformed());
+
+    assertEquals(expectedBooleanValue, allBoxed.getBooleanValue());
     assertEquals(expectedIntValue, allBoxed.getIntValue().intValue());
     assertEquals(expectedShortValue, allBoxed.getShortValue().shortValue());
     assertEquals(expectedLongValue, allBoxed.getLongValue().longValue());
     assertEquals(expectedFloatValue, allBoxed.getFloatValue(), TestUtils.DELTA);
     assertEquals(expectedDoubleValue, allBoxed.getDoubleValue(), TestUtils.DELTA);
     assertEquals(expectedCharValue, allBoxed.getCharValue().charValue());
-  }
 
-  @Override
-  protected void communicateSendInitialValue() {
-    // empty
+    assertEquals(expectedBooleanValue, allBoxed.getBooleanValueTransformed());
+    assertEquals(expectedIntValue, allBoxed.getIntValueTransformed().intValue());
+    assertEquals(expectedShortValue, allBoxed.getShortValueTransformed().shortValue());
+    assertEquals(expectedLongValue, allBoxed.getLongValueTransformed().longValue());
+    assertEquals(expectedFloatValue, allBoxed.getFloatValueTransformed(), TestUtils.DELTA);
+    assertEquals(expectedDoubleValue, allBoxed.getDoubleValueTransformed(), TestUtils.DELTA);
+    assertEquals(expectedCharValue, allBoxed.getCharValueTransformed().charValue());
   }
 
   @Override
-  protected void createModel() {
-    model = new A();
-    integers = new NativeTypes();
-    model.addNativeTypes(integers);
-    floats = new NativeTypes();
-    model.addNativeTypes(floats);
-    chars = new NativeTypes();
-    model.addNativeTypes(chars);
-    allBoxed = new BoxedTypes();
-    model.addBoxedTypes(allBoxed);
+  public void closeConnections() {
+    if (model != null) {
+      model.ragconnectCloseConnections();
+    }
   }
 
 }
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 c312d4c534d624f0a6c2ee891eba36ddb63be584..d8ce0c083148b671590c6129d16f8d3ed071e313 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/DefaultOnlyWriteTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/DefaultOnlyWriteTest.java
@@ -4,11 +4,13 @@ import defaultOnlyWrite.ast.A;
 import defaultOnlyWrite.ast.BoxedTypesSyn;
 import defaultOnlyWrite.ast.MqttHandler;
 import defaultOnlyWrite.ast.NativeTypesSyn;
+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.*;
 import static org.junit.jupiter.api.Assertions.*;
 
 /**
@@ -18,6 +20,7 @@ import static org.junit.jupiter.api.Assertions.*;
  */
 public class DefaultOnlyWriteTest extends AbstractMqttTest {
 
+  private static final String TOPIC_NATIVE_BOOLEAN = "native/boolean";
   private static final String TOPIC_NATIVE_INT = "native/int";
   private static final String TOPIC_NATIVE_SHORT = "native/short";
   private static final String TOPIC_NATIVE_LONG = "native/long";
@@ -26,6 +29,16 @@ public class DefaultOnlyWriteTest extends AbstractMqttTest {
   private static final String TOPIC_NATIVE_CHAR = "native/char";
   private static final String TOPIC_NATIVE_STRING = "native/string";
 
+  private static final String TOPIC_NATIVE_BOOLEAN_TRANSFORMED = "native/boolean/t";
+  private static final String TOPIC_NATIVE_INT_TRANSFORMED = "native/int/t";
+  private static final String TOPIC_NATIVE_SHORT_TRANSFORMED = "native/short/t";
+  private static final String TOPIC_NATIVE_LONG_TRANSFORMED = "native/long/t";
+  private static final String TOPIC_NATIVE_FLOAT_TRANSFORMED = "native/float/t";
+  private static final String TOPIC_NATIVE_DOUBLE_TRANSFORMED = "native/double/t";
+  private static final String TOPIC_NATIVE_CHAR_TRANSFORMED = "native/char/t";
+  private static final String TOPIC_NATIVE_STRING_TRANSFORMED = "native/string/t";
+
+  private static final String TOPIC_BOXED_BOOLEAN = "boxed/Boolean";
   private static final String TOPIC_BOXED_INTEGER = "boxed/Integer";
   private static final String TOPIC_BOXED_SHORT = "boxed/Short";
   private static final String TOPIC_BOXED_LONG = "boxed/Long";
@@ -33,6 +46,14 @@ public class DefaultOnlyWriteTest extends AbstractMqttTest {
   private static final String TOPIC_BOXED_DOUBLE = "boxed/Double";
   private static final String TOPIC_BOXED_CHARACTER = "boxed/Character";
 
+  private static final String TOPIC_BOXED_BOOLEAN_TRANSFORMED = "boxed/Boolean/t";
+  private static final String TOPIC_BOXED_INTEGER_TRANSFORMED = "boxed/Integer/t";
+  private static final String TOPIC_BOXED_SHORT_TRANSFORMED = "boxed/Short/t";
+  private static final String TOPIC_BOXED_LONG_TRANSFORMED = "boxed/Long/t";
+  private static final String TOPIC_BOXED_FLOAT_TRANSFORMED = "boxed/Float/t";
+  private static final String TOPIC_BOXED_DOUBLE_TRANSFORMED = "boxed/Double/t";
+  private static final String TOPIC_BOXED_CHARACTER_TRANSFORMED = "boxed/Character/t";
+
   private A model;
   private NativeTypesSyn nativeIntegers;
   private NativeTypesSyn nativeFloats;
@@ -41,61 +62,14 @@ public class DefaultOnlyWriteTest extends AbstractMqttTest {
   private BoxedTypesSyn boxedFloats;
   private BoxedTypesSyn boxedChars;
   private MqttHandler receiver;
-  private ReceiverData data;
-
-  @Override
-  public void closeConnections() {
-    if (receiver != null) {
-      receiver.close();
-    }
-    if (model != null) {
-      model.ragconnectCloseConnections();
-    }
-  }
-
-
-
-
-  @Override
-  protected void communicateSendInitialValue() throws InterruptedException {
-    // check initial value
-    TestUtils.waitForMqtt();
-    checkData(1, 1, 1.1, 'a', "ab");
-
-    // set new value
-    setData("2", "2.2", "cd");
-
-    // check new value
-    TestUtils.waitForMqtt();
-    checkData(2, 2, 2.2, 'c', "cd");
-
-    // set new value
-    setData("3", "3.2", "ee");
-
-    // check new value
-    TestUtils.waitForMqtt();
-    checkData(3, 3, 3.2, 'e', "ee");
-  }
-
-  @Override
-  protected void communicateOnlyUpdatedValue() throws InterruptedException {
-    // check initial value (will be default values)
-    TestUtils.waitForMqtt();
-    checkData(0, null, null, null, null);
-
-    // set new value
-    setData("2", "2.2", "cd");
-
-    // check new value
-    TestUtils.waitForMqtt();
-    checkData(1, 2, 2.2, 'c', "cd");
-
-    // set new value
-    setData("3", "3.2", "ee");
-
-    // check new value
-    TestUtils.waitForMqtt();
-    checkData(2, 3, 3.2, 'e', "ee");
+  private ReceiverData dataNormal;
+  private ReceiverData dataTransformed;
+
+  @Test
+  public void checkNotJacksonReference() {
+    testJaddContainReferenceToJackson(
+        Paths.get("src", "test",
+            "02-after-ragconnect", "defaultOnlyWrite", "RagConnect.jadd"), false);
   }
 
   @Override
@@ -126,6 +100,7 @@ public class DefaultOnlyWriteTest extends AbstractMqttTest {
     receiver = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost());
     assertTrue(receiver.waitUntilReady(2, TimeUnit.SECONDS));
 
+    nativeIntegers.addNativeBooleanDependency(nativeIntegers);
     nativeIntegers.addNativeIntDependency(nativeIntegers);
     nativeIntegers.addNativeShortDependency(nativeIntegers);
     nativeIntegers.addNativeLongDependency(nativeIntegers);
@@ -134,6 +109,16 @@ public class DefaultOnlyWriteTest extends AbstractMqttTest {
     nativeChars.addNativeCharDependency(nativeChars);
     nativeChars.addNativeStringDependency(nativeChars);
 
+    nativeIntegers.addNativeBooleanTransformedDependency(nativeIntegers);
+    nativeIntegers.addNativeIntTransformedDependency(nativeIntegers);
+    nativeIntegers.addNativeShortTransformedDependency(nativeIntegers);
+    nativeIntegers.addNativeLongTransformedDependency(nativeIntegers);
+    nativeFloats.addNativeFloatTransformedDependency(nativeFloats);
+    nativeFloats.addNativeDoubleTransformedDependency(nativeFloats);
+    nativeChars.addNativeCharTransformedDependency(nativeChars);
+    nativeChars.addNativeStringTransformedDependency(nativeChars);
+
+    boxedIntegers.addBoxedBooleanDependency(boxedIntegers);
     boxedIntegers.addBoxedIntDependency(boxedIntegers);
     boxedIntegers.addBoxedShortDependency(boxedIntegers);
     boxedIntegers.addBoxedLongDependency(boxedIntegers);
@@ -141,74 +126,162 @@ public class DefaultOnlyWriteTest extends AbstractMqttTest {
     boxedFloats.addBoxedDoubleDependency(boxedFloats);
     boxedChars.addBoxedCharDependency(boxedChars);
 
-    data = new ReceiverData();
+    boxedIntegers.addBoxedBooleanTransformedDependency(boxedIntegers);
+    boxedIntegers.addBoxedIntTransformedDependency(boxedIntegers);
+    boxedIntegers.addBoxedShortTransformedDependency(boxedIntegers);
+    boxedIntegers.addBoxedLongTransformedDependency(boxedIntegers);
+    boxedFloats.addBoxedFloatTransformedDependency(boxedFloats);
+    boxedFloats.addBoxedDoubleTransformedDependency(boxedFloats);
+    boxedChars.addBoxedCharTransformedDependency(boxedChars);
+
+    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));
+  }
+
+  private ReceiverData createReceiver(boolean transformed) {
+    ReceiverData result = new ReceiverData();
 
-    receiver.newConnection(TOPIC_NATIVE_INT, bytes -> {
-      data.numberOfNativeIntValues += 1;
-      data.lastNativeIntValue = java.nio.ByteBuffer.wrap(bytes).getInt();
+    receiver.newConnection(transformed ? TOPIC_NATIVE_BOOLEAN_TRANSFORMED : TOPIC_NATIVE_BOOLEAN, bytes -> {
+      result.numberOfNativeBoolValues += 1;
+      result.lastNativeBoolValue = TestUtils.DefaultMappings.BytesToBool(bytes);
+    });
+    receiver.newConnection(transformed ? TOPIC_NATIVE_INT_TRANSFORMED : TOPIC_NATIVE_INT, bytes -> {
+      result.numberOfNativeIntValues += 1;
+      result.lastNativeIntValue = TestUtils.DefaultMappings.BytesToInt(bytes);
     });
-    receiver.newConnection(TOPIC_NATIVE_SHORT, bytes -> {
-      data.numberOfNativeShortValues += 1;
-      data.lastNativeShortValue = java.nio.ByteBuffer.wrap(bytes).getShort();
+    receiver.newConnection(transformed ? TOPIC_NATIVE_SHORT_TRANSFORMED : TOPIC_NATIVE_SHORT, bytes -> {
+      result.numberOfNativeShortValues += 1;
+      result.lastNativeShortValue = TestUtils.DefaultMappings.BytesToShort(bytes);
     });
-    receiver.newConnection(TOPIC_NATIVE_LONG, bytes -> {
-      data.numberOfNativeLongValues += 1;
-      data.lastNativeLongValue = java.nio.ByteBuffer.wrap(bytes).getLong();
+    receiver.newConnection(transformed ? TOPIC_NATIVE_LONG_TRANSFORMED : TOPIC_NATIVE_LONG, bytes -> {
+      result.numberOfNativeLongValues += 1;
+      result.lastNativeLongValue = TestUtils.DefaultMappings.BytesToLong(bytes);
     });
-    receiver.newConnection(TOPIC_NATIVE_FLOAT, bytes -> {
-      data.numberOfNativeFloatValues += 1;
-      data.lastNativeFloatValue = java.nio.ByteBuffer.wrap(bytes).getFloat();
+    receiver.newConnection(transformed ? TOPIC_NATIVE_FLOAT_TRANSFORMED : TOPIC_NATIVE_FLOAT, bytes -> {
+      result.numberOfNativeFloatValues += 1;
+      result.lastNativeFloatValue = TestUtils.DefaultMappings.BytesToFloat(bytes);
     });
-    receiver.newConnection(TOPIC_NATIVE_DOUBLE, bytes -> {
-      data.numberOfNativeDoubleValues += 1;
-      data.lastNativeDoubleValue = java.nio.ByteBuffer.wrap(bytes).getDouble();
+    receiver.newConnection(transformed ? TOPIC_NATIVE_DOUBLE_TRANSFORMED : TOPIC_NATIVE_DOUBLE, bytes -> {
+      result.numberOfNativeDoubleValues += 1;
+      result.lastNativeDoubleValue = TestUtils.DefaultMappings.BytesToDouble(bytes);
     });
-    receiver.newConnection(TOPIC_NATIVE_CHAR, bytes -> {
-      data.numberOfNativeCharValues += 1;
-      data.lastNativeCharValue = java.nio.ByteBuffer.wrap(bytes).getChar();
+    receiver.newConnection(transformed ? TOPIC_NATIVE_CHAR_TRANSFORMED : TOPIC_NATIVE_CHAR, bytes -> {
+      result.numberOfNativeCharValues += 1;
+      result.lastNativeCharValue = TestUtils.DefaultMappings.BytesToChar(bytes);
     });
-    receiver.newConnection(TOPIC_NATIVE_STRING, bytes -> {
-      data.numberOfNativeStringValues += 1;
-      data.lastNativeStringValue = new String(bytes);
+    receiver.newConnection(transformed ? TOPIC_NATIVE_STRING_TRANSFORMED : TOPIC_NATIVE_STRING, bytes -> {
+      result.numberOfNativeStringValues += 1;
+      result.lastNativeStringValue = TestUtils.DefaultMappings.BytesToString(bytes);
     });
-    receiver.newConnection(TOPIC_BOXED_INTEGER, bytes -> {
-      data.numberOfBoxedIntValues += 1;
-      data.lastBoxedIntValue = java.nio.ByteBuffer.wrap(bytes).getInt();
+    receiver.newConnection(transformed ? TOPIC_BOXED_BOOLEAN_TRANSFORMED : TOPIC_BOXED_BOOLEAN, bytes -> {
+      result.numberOfBoxedBoolValues += 1;
+      result.lastBoxedBoolValue = TestUtils.DefaultMappings.BytesToBool(bytes);
     });
-    receiver.newConnection(TOPIC_BOXED_SHORT, bytes -> {
-      data.numberOfBoxedShortValues += 1;
-      data.lastBoxedShortValue = java.nio.ByteBuffer.wrap(bytes).getShort();
+    receiver.newConnection(transformed ? TOPIC_BOXED_INTEGER_TRANSFORMED : TOPIC_BOXED_INTEGER, bytes -> {
+      result.numberOfBoxedIntValues += 1;
+      result.lastBoxedIntValue = TestUtils.DefaultMappings.BytesToInt(bytes);
     });
-    receiver.newConnection(TOPIC_BOXED_LONG, bytes -> {
-      data.numberOfBoxedLongValues += 1;
-      data.lastBoxedLongValue = java.nio.ByteBuffer.wrap(bytes).getLong();
+    receiver.newConnection(transformed ? TOPIC_BOXED_SHORT_TRANSFORMED : TOPIC_BOXED_SHORT, bytes -> {
+      result.numberOfBoxedShortValues += 1;
+      result.lastBoxedShortValue = TestUtils.DefaultMappings.BytesToShort(bytes);
     });
-    receiver.newConnection(TOPIC_BOXED_FLOAT, bytes -> {
-      data.numberOfBoxedFloatValues += 1;
-      data.lastBoxedFloatValue = java.nio.ByteBuffer.wrap(bytes).getFloat();
+    receiver.newConnection(transformed ? TOPIC_BOXED_LONG_TRANSFORMED : TOPIC_BOXED_LONG, bytes -> {
+      result.numberOfBoxedLongValues += 1;
+      result.lastBoxedLongValue = TestUtils.DefaultMappings.BytesToLong(bytes);
     });
-    receiver.newConnection(TOPIC_BOXED_DOUBLE, bytes -> {
-      data.numberOfBoxedDoubleValues += 1;
-      data.lastBoxedDoubleValue = java.nio.ByteBuffer.wrap(bytes).getDouble();
+    receiver.newConnection(transformed ? TOPIC_BOXED_FLOAT_TRANSFORMED : TOPIC_BOXED_FLOAT, bytes -> {
+      result.numberOfBoxedFloatValues += 1;
+      result.lastBoxedFloatValue = TestUtils.DefaultMappings.BytesToFloat(bytes);
     });
-    receiver.newConnection(TOPIC_BOXED_CHARACTER, bytes -> {
-      data.numberOfBoxedCharValues += 1;
-      data.lastBoxedCharValue = java.nio.ByteBuffer.wrap(bytes).getChar();
+    receiver.newConnection(transformed ? TOPIC_BOXED_DOUBLE_TRANSFORMED : TOPIC_BOXED_DOUBLE, bytes -> {
+      result.numberOfBoxedDoubleValues += 1;
+      result.lastBoxedDoubleValue = TestUtils.DefaultMappings.BytesToDouble(bytes);
     });
+    receiver.newConnection(transformed ? TOPIC_BOXED_CHARACTER_TRANSFORMED : TOPIC_BOXED_CHARACTER, bytes -> {
+      result.numberOfBoxedCharValues += 1;
+      result.lastBoxedCharValue = TestUtils.DefaultMappings.BytesToChar(bytes);
+    });
+    return result;
+  }
+
+  @Override
+  protected void communicateSendInitialValue() throws InterruptedException {
+    // check initial value
+    checkData(1, 1, false, 1, 1.1, 'a', "ab");
+
+    // set new value
+    setData("2", "2.2", "cd");
 
-    nativeIntegers.connectIntValue(mqttUri(TOPIC_NATIVE_INT), writeCurrentValue);
-    nativeIntegers.connectShortValue(mqttUri(TOPIC_NATIVE_SHORT), writeCurrentValue);
-    nativeIntegers.connectLongValue(mqttUri(TOPIC_NATIVE_LONG), writeCurrentValue);
-    nativeFloats.connectFloatValue(mqttUri(TOPIC_NATIVE_FLOAT), writeCurrentValue);
-    nativeFloats.connectDoubleValue(mqttUri(TOPIC_NATIVE_DOUBLE), writeCurrentValue);
-    nativeChars.connectCharValue(mqttUri(TOPIC_NATIVE_CHAR), writeCurrentValue);
-    nativeChars.connectStringValue(mqttUri(TOPIC_NATIVE_STRING), writeCurrentValue);
-    boxedIntegers.connectIntValue(mqttUri(TOPIC_BOXED_INTEGER), writeCurrentValue);
-    boxedIntegers.connectShortValue(mqttUri(TOPIC_BOXED_SHORT), writeCurrentValue);
-    boxedIntegers.connectLongValue(mqttUri(TOPIC_BOXED_LONG), writeCurrentValue);
-    boxedFloats.connectFloatValue(mqttUri(TOPIC_BOXED_FLOAT), writeCurrentValue);
-    boxedFloats.connectDoubleValue(mqttUri(TOPIC_BOXED_DOUBLE), writeCurrentValue);
-    boxedChars.connectCharValue(mqttUri(TOPIC_BOXED_CHARACTER), writeCurrentValue);
+    // check new value
+    checkData(2, 2, true, 2, 2.2, 'c', "cd");
+
+    // set new value
+    setData("3", "3.2", "ee");
+
+    // check new value
+    checkData(3, 2, true, 3, 3.2, 'e', "ee");
+  }
+
+  @Override
+  protected void communicateOnlyUpdatedValue() throws InterruptedException {
+    // check initial value (will be default values)
+    checkData(0, 0, null, null, null, null, null);
+
+    // set new value
+    setData("2", "2.2", "cd");
+
+    // check new value
+    checkData(1, 1, true, 2, 2.2, 'c', "cd");
+
+    // set new value
+    setData("3", "3.2", "ee");
+
+    // check new value
+    checkData(2, 1, true, 3, 3.2, 'e', "ee");
+  }
+
+  @Override
+  public void closeConnections() {
+    if (receiver != null) {
+      receiver.close();
+    }
+    if (model != null) {
+      model.ragconnectCloseConnections();
+    }
   }
 
   private void setData(String integerDriver, String floatDriver, String stringDriver) {
@@ -221,63 +294,78 @@ public class DefaultOnlyWriteTest extends AbstractMqttTest {
     boxedChars.setDriverSyn(stringDriver);
   }
 
-  private void checkData(int expectedNumberOfValues,
-                         Integer expectedInt, Double expectedDouble,
-                         Character expectedChar, String expectedString) {
-    assertEquals(expectedNumberOfValues, data.numberOfNativeIntValues);
-    assertEquals(expectedNumberOfValues, data.numberOfNativeShortValues);
-    assertEquals(expectedNumberOfValues, data.numberOfNativeLongValues);
-    assertEquals(expectedNumberOfValues, data.numberOfNativeFloatValues);
-    assertEquals(expectedNumberOfValues, data.numberOfNativeDoubleValues);
-    assertEquals(expectedNumberOfValues, data.numberOfNativeCharValues);
-    assertEquals(expectedNumberOfValues, data.numberOfNativeStringValues);
-
-    assertEquals(expectedNumberOfValues, data.numberOfBoxedIntValues);
-    assertEquals(expectedNumberOfValues, data.numberOfBoxedShortValues);
-    assertEquals(expectedNumberOfValues, data.numberOfBoxedLongValues);
-    assertEquals(expectedNumberOfValues, data.numberOfBoxedFloatValues);
-    assertEquals(expectedNumberOfValues, data.numberOfBoxedDoubleValues);
-    assertEquals(expectedNumberOfValues, data.numberOfBoxedCharValues);
-
-    if (expectedInt != null) {
-      assertEquals(expectedInt.intValue(), data.lastNativeIntValue);
-      assertEquals(expectedInt.shortValue(), data.lastNativeShortValue);
-      assertEquals(expectedInt.longValue(), data.lastNativeLongValue);
-      assertEquals(expectedInt.intValue(), data.lastBoxedIntValue.intValue());
-      assertEquals(expectedInt.shortValue(), data.lastBoxedShortValue.shortValue());
-      assertEquals(expectedInt.longValue(), data.lastBoxedLongValue.longValue());
-    } else {
-      assertEquals(0, data.lastNativeIntValue);
-      assertEquals(0, data.lastNativeShortValue);
-      assertEquals(0, data.lastNativeLongValue);
-      assertNull(data.lastBoxedIntValue);
-      assertNull(data.lastBoxedShortValue);
-      assertNull(data.lastBoxedLongValue);
-    }
-
-    if (expectedDouble != null) {
-      assertEquals(expectedDouble.floatValue(), data.lastNativeFloatValue, TestUtils.DELTA);
-      assertEquals(expectedDouble, data.lastNativeDoubleValue, TestUtils.DELTA);
-      assertEquals(expectedDouble.floatValue(), data.lastBoxedFloatValue, TestUtils.DELTA);
-      assertEquals(expectedDouble, data.lastBoxedDoubleValue, TestUtils.DELTA);
-    } else {
-      assertEquals(0f, data.lastNativeFloatValue, TestUtils.DELTA);
-      assertEquals(0d, data.lastNativeDoubleValue, TestUtils.DELTA);
-      assertNull(data.lastBoxedFloatValue);
-      assertNull(data.lastBoxedDoubleValue);
-    }
-
-    if (expectedChar != null) {
-      assertEquals(expectedChar.charValue(), data.lastNativeCharValue);
-      assertEquals(expectedChar, data.lastBoxedCharValue);
-    } else {
-      assertEquals('\0', data.lastNativeCharValue);
-      assertNull(data.lastBoxedCharValue);
+  private void checkData(int expectedNumberOfValues, int expectedNumberOfBoolValues,
+                         Boolean expectedBool, Integer expectedInt, Double expectedDouble,
+                         Character expectedChar, String expectedString) throws InterruptedException {
+    TestUtils.waitForMqtt();
+    for (ReceiverData data : new ReceiverData[]{dataNormal, dataTransformed}) {
+      assertEquals(expectedNumberOfBoolValues, data.numberOfNativeBoolValues);
+      assertEquals(expectedNumberOfValues, data.numberOfNativeIntValues);
+      assertEquals(expectedNumberOfValues, data.numberOfNativeShortValues);
+      assertEquals(expectedNumberOfValues, data.numberOfNativeLongValues);
+      assertEquals(expectedNumberOfValues, data.numberOfNativeFloatValues);
+      assertEquals(expectedNumberOfValues, data.numberOfNativeDoubleValues);
+      assertEquals(expectedNumberOfValues, data.numberOfNativeCharValues);
+      assertEquals(expectedNumberOfValues, data.numberOfNativeStringValues);
+
+      assertEquals(expectedNumberOfBoolValues, data.numberOfBoxedBoolValues);
+      assertEquals(expectedNumberOfValues, data.numberOfBoxedIntValues);
+      assertEquals(expectedNumberOfValues, data.numberOfBoxedShortValues);
+      assertEquals(expectedNumberOfValues, data.numberOfBoxedLongValues);
+      assertEquals(expectedNumberOfValues, data.numberOfBoxedFloatValues);
+      assertEquals(expectedNumberOfValues, data.numberOfBoxedDoubleValues);
+      assertEquals(expectedNumberOfValues, data.numberOfBoxedCharValues);
+
+      if (expectedBool != null) {
+        assertEquals(expectedBool, data.lastNativeBoolValue);
+        assertEquals(expectedBool, data.lastBoxedBoolValue);
+      } else {
+        assertFalse(data.lastNativeBoolValue);
+        assertNull(data.lastBoxedBoolValue);
+      }
+
+      if (expectedInt != null) {
+        assertEquals(expectedInt.intValue(), data.lastNativeIntValue);
+        assertEquals(expectedInt.shortValue(), data.lastNativeShortValue);
+        assertEquals(expectedInt.longValue(), data.lastNativeLongValue);
+        assertEquals(expectedInt.intValue(), data.lastBoxedIntValue.intValue());
+        assertEquals(expectedInt.shortValue(), data.lastBoxedShortValue.shortValue());
+        assertEquals(expectedInt.longValue(), data.lastBoxedLongValue.longValue());
+      } else {
+        assertEquals(0, data.lastNativeIntValue);
+        assertEquals(0, data.lastNativeShortValue);
+        assertEquals(0, data.lastNativeLongValue);
+        assertNull(data.lastBoxedIntValue);
+        assertNull(data.lastBoxedShortValue);
+        assertNull(data.lastBoxedLongValue);
+      }
+
+      if (expectedDouble != null) {
+        assertEquals(expectedDouble.floatValue(), data.lastNativeFloatValue, TestUtils.DELTA);
+        assertEquals(expectedDouble, data.lastNativeDoubleValue, TestUtils.DELTA);
+        assertEquals(expectedDouble.floatValue(), data.lastBoxedFloatValue, TestUtils.DELTA);
+        assertEquals(expectedDouble, data.lastBoxedDoubleValue, TestUtils.DELTA);
+      } else {
+        assertEquals(0f, data.lastNativeFloatValue, TestUtils.DELTA);
+        assertEquals(0d, data.lastNativeDoubleValue, TestUtils.DELTA);
+        assertNull(data.lastBoxedFloatValue);
+        assertNull(data.lastBoxedDoubleValue);
+      }
+
+      if (expectedChar != null) {
+        assertEquals(expectedChar.charValue(), data.lastNativeCharValue);
+        assertEquals(expectedChar, data.lastBoxedCharValue);
+      } else {
+        assertEquals('\0', data.lastNativeCharValue);
+        assertNull(data.lastBoxedCharValue);
+      }
+      assertEquals(expectedString, data.lastNativeStringValue);
     }
-    assertEquals(expectedString, data.lastNativeStringValue);
   }
 
   private static class ReceiverData {
+    boolean lastNativeBoolValue;
+    int numberOfNativeBoolValues = 0;
     int lastNativeIntValue;
     int numberOfNativeIntValues = 0;
     short lastNativeShortValue;
@@ -293,6 +381,8 @@ public class DefaultOnlyWriteTest extends AbstractMqttTest {
     String lastNativeStringValue;
     int numberOfNativeStringValues = 0;
 
+    Boolean lastBoxedBoolValue;
+    int numberOfBoxedBoolValues = 0;
     Integer lastBoxedIntValue;
     int numberOfBoxedIntValues = 0;
     Short lastBoxedShortValue;
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Errors.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Errors.java
index 17d853fba71c2ee46c6c6c7b5424958af0a5f8d2..4d32adff23abd12f2052f320466b79dc0f8673f5 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Errors.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Errors.java
@@ -2,7 +2,6 @@ package org.jastadd.ragconnect.tests;
 
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
-import org.jastadd.ragconnect.compiler.Compiler;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
@@ -10,21 +9,25 @@ import org.junit.jupiter.api.Test;
 import java.io.File;
 import java.io.IOException;
 import java.nio.charset.Charset;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.stream.Collectors;
 
-import static org.jastadd.ragconnect.tests.TestUtils.exec;
+import static org.assertj.core.api.Assertions.assertThat;
 import static org.jastadd.ragconnect.tests.TestUtils.readFile;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
-class Errors {
+public class Errors {
 
   private static final Logger logger = LogManager.getLogger(Errors.class);
   private static final String FILENAME_PATTERN = "$FILENAME";
-  private static final String INPUT_DIRECTORY = "./src/test/01-input/errors/";
-  private static final String OUTPUT_DIRECTORY = "./src/test/02-after-ragconnect/errors/";
+  private static final String ERROR_DIRECTORY = "errors/";
+  private static final String OUTPUT_DIRECTORY = TestUtils.OUTPUT_DIRECTORY_PREFIX + ERROR_DIRECTORY;
+
+  private static final String DEFAULT_GRAMMAR_NAME = "Errors";
 
   @BeforeAll
   public static void createOutputDirectory() {
@@ -34,40 +37,31 @@ class Errors {
 
   @Test
   void testStandardErrors() throws IOException {
-    test("Errors", "A");
+    test("Standard", "A","Standard");
+  }
+
+  @Test
+  void testTwoPartsErrors() throws IOException {
+    test("Part", "A","Part1", "Part2");
   }
 
   @SuppressWarnings("SameParameterValue")
-  private void test(String name, String rootNode) throws IOException {
-    String grammarFile = INPUT_DIRECTORY + name + ".relast";
-    String ragconnectFile = INPUT_DIRECTORY + name + ".connect";
-    String outFile = OUTPUT_DIRECTORY + name + ".out";
-    String expectedFile = INPUT_DIRECTORY + name + ".expected";
+  private void test(String expectedName, String rootNode, String... connectNames) throws IOException {
+    String grammarFile = ERROR_DIRECTORY + DEFAULT_GRAMMAR_NAME + ".relast";
+    List<String> connectFiles = Arrays.stream(connectNames)
+        .map(connectName -> ERROR_DIRECTORY + connectName + ".connect")
+        .collect(Collectors.toList());
+    Path outPath = TestUtils.runCompiler(grammarFile, connectFiles, rootNode, ERROR_DIRECTORY, 1);
 
-    try {
-      logger.debug("user.dir: {}", System.getProperty("user.dir"));
-      String[] args = {
-          "--o=" + OUTPUT_DIRECTORY,
-          grammarFile,
-          ragconnectFile,
-          "--rootNode=" + rootNode,
-          "--verbose"
-      };
-      int returnValue = exec(Compiler.class, args, new File(outFile));
-      Assertions.assertEquals(1, returnValue, "RagConnect did not return with value 1");
-    } catch (IOException | InterruptedException e) {
-      e.printStackTrace();
-    }
+    final String startOfErrorsPattern = "SEVERE: Errors:";
+    String out = readFile(outPath, Charset.defaultCharset());
+    assertThat(out).contains(startOfErrorsPattern);
+    out = out.substring(out.indexOf(startOfErrorsPattern) + 16);
 
-    String out = readFile(outFile, Charset.defaultCharset());
-    String expected = readFile(expectedFile, Charset.defaultCharset());
-//    if (inFiles.size() == 1) {
-      expected = expected.replace(FILENAME_PATTERN, name);
-//    } else {
-//      for (int i = 0; i < inFiles.size(); i++) {
-//        expected = expected.replace(FILENAME_PATTERN + (i + 1), inFiles.get(i));
-//      }
-//    }
+    Path expectedPath = Paths.get(TestUtils.INPUT_DIRECTORY_PREFIX)
+        .resolve(ERROR_DIRECTORY)
+        .resolve(expectedName + ".expected");
+    String expected = readFile(expectedPath, Charset.defaultCharset());
     List<String> outList = Arrays.asList(out.split("\n"));
     Collections.sort(outList);
     List<String> expectedList = Arrays.stream(expected.split("\n"))
@@ -75,10 +69,9 @@ class Errors {
         .filter(s -> !s.isEmpty() && !s.startsWith("//"))
         .collect(Collectors.toList());
 
-    // FIXME errors not handled correctly at the moment
-//    Assertions.assertLinesMatch(expectedList, outList);
+    Assertions.assertLinesMatch(expectedList, outList);
 
-    logger.info("ragconnect for " + name + " returned:\n{}", out);
+    logger.info("ragconnect for " + expectedName + " returned:\n{}", out);
   }
 
 }
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ExampleTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ExampleTest.java
index c3df78d1b17658a1327afa0f4e2f308eb5ee159b..5ad824bcd8de42d998d7825c6930892ecc57e37d 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ExampleTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ExampleTest.java
@@ -37,16 +37,70 @@ public class ExampleTest extends AbstractMqttTest {
   }
 
   @Override
-  public void closeConnections() {
-    if (handler != null) {
-      handler.close();
-    }
-    if (model != null) {
-      model.ragconnectCloseConnections();
-    }
+  protected void createModel() {
+    model = new Model();
+
+    ZoneModel zoneModel = new ZoneModel();
+
+    IntPosition firstPosition = makePosition(0, 0, 0);
+    IntPosition secondPosition = makePosition(-1, 0, 0);
+    IntPosition thirdPosition = makePosition(1, 0, 0);
+
+    Zone safetyZone = new Zone();
+    safetyZone.addCoordinate(new Coordinate(firstPosition));
+    safetyZone.addCoordinate(new Coordinate(secondPosition));
+    safetyZone.addCoordinate(new Coordinate(thirdPosition));
+    zoneModel.addSafetyZone(safetyZone);
+    model.setZoneModel(zoneModel);
+
+    robotArm = new RobotArm();
+
+    link1 = new Link();
+    link1.setName("joint1");
+    link1.setCurrentPosition(firstPosition);
+
+    link2 = new Link();
+    link2.setName("joint2");
+    link2.setCurrentPosition(secondPosition);
+
+    EndEffector endEffector = new EndEffector();
+    endEffector.setName("gripper");
+    endEffector.setCurrentPosition(makePosition(2, 2, 3));
+
+    robotArm.addLink(link1);
+    robotArm.addLink(link2);
+    robotArm.setEndEffector(endEffector);
+    model.setRobotArm(robotArm);
   }
 
+  @Override
+  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));
+
+    // add dependencies
+    robotArm.addDependency1(link1);
+    robotArm.addDependency1(link2);
+    robotArm.addDependency1(robotArm.getEndEffector());
+
+    data = new ReceiverData();
 
+    handler.newConnection(TOPIC_CONFIG, bytes -> {
+      data.numberOfConfigs += 1;
+      try {
+        data.lastConfig = RobotConfig.parseFrom(bytes);
+        data.failedLastConversion = false;
+      } catch (InvalidProtocolBufferException e) {
+        data.failedLastConversion = true;
+      }
+    });
+
+    assertTrue(robotArm.connectAppropriateSpeed(mqttUri(TOPIC_CONFIG), writeCurrentValue));
+    assertTrue(link1.connectCurrentPosition(mqttUri(TOPIC_JOINT1)));
+    assertTrue(link2.connectCurrentPosition(mqttUri(TOPIC_JOINT2)));
+  }
 
   @Override
   protected void communicateSendInitialValue() throws InterruptedException {
@@ -182,90 +236,34 @@ public class ExampleTest extends AbstractMqttTest {
     assertFalse(data.failedLastConversion);
   }
 
+  @Override
+  public void closeConnections() {
+    if (handler != null) {
+      handler.close();
+    }
+    if (model != null) {
+      model.ragconnectCloseConnections();
+    }
+  }
+
   @Test
   public void testFailedConversion() throws IOException {
     createModel();
     setupReceiverAndConnect(false);
 
-    handler.publish(TOPIC_JOINT1, "not-a-pandaLinkState".getBytes());
+    publisher.publish(TOPIC_JOINT1, "not-a-pandaLinkState".getBytes());
     assertEquals(0, data.numberOfConfigs);
     assertTrue(data.failedLastConversion);
   }
 
   private void sendData(String topic, float x, float y, float z) {
-    handler.publish(topic, RobotState.newBuilder()
+    publisher.publish(topic, RobotState.newBuilder()
         .setPosition(RobotState.Position.newBuilder().setX(x).setY(y).setZ(z).build())
         .build()
         .toByteArray()
     );
   }
 
-  @Override
-  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));
-
-    // add dependencies
-    robotArm.addDependency1(link1);
-    robotArm.addDependency1(link2);
-    robotArm.addDependency1(robotArm.getEndEffector());
-
-    data = new ReceiverData();
-
-    handler.newConnection(TOPIC_CONFIG, bytes -> {
-      data.numberOfConfigs += 1;
-      try {
-        data.lastConfig = RobotConfig.parseFrom(bytes);
-        data.failedLastConversion = false;
-      } catch (InvalidProtocolBufferException e) {
-        data.failedLastConversion = true;
-      }
-    });
-
-    robotArm.connectAppropriateSpeed(mqttUri(TOPIC_CONFIG), writeCurrentValue);
-    link1.connectCurrentPosition(mqttUri(TOPIC_JOINT1));
-    link2.connectCurrentPosition(mqttUri(TOPIC_JOINT2));
-  }
-
-  @Override
-  protected void createModel() {
-    model = new Model();
-
-    ZoneModel zoneModel = new ZoneModel();
-
-    IntPosition firstPosition = makePosition(0, 0, 0);
-    IntPosition secondPosition = makePosition(-1, 0, 0);
-    IntPosition thirdPosition = makePosition(1, 0, 0);
-
-    Zone safetyZone = new Zone();
-    safetyZone.addCoordinate(new Coordinate(firstPosition));
-    safetyZone.addCoordinate(new Coordinate(secondPosition));
-    safetyZone.addCoordinate(new Coordinate(thirdPosition));
-    zoneModel.addSafetyZone(safetyZone);
-    model.setZoneModel(zoneModel);
-
-    robotArm = new RobotArm();
-
-    link1 = new Link();
-    link1.setName("joint1");
-    link1.setCurrentPosition(firstPosition);
-
-    link2 = new Link();
-    link2.setName("joint2");
-    link2.setCurrentPosition(secondPosition);
-
-    EndEffector endEffector = new EndEffector();
-    endEffector.setName("gripper");
-    endEffector.setCurrentPosition(makePosition(2, 2, 3));
-
-    robotArm.addLink(link1);
-    robotArm.addLink(link2);
-    robotArm.setEndEffector(endEffector);
-    model.setRobotArm(robotArm);
-  }
-
   private static IntPosition makePosition(int x, int y, int z) {
     return IntPosition.of(x, y, z);
   }
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
new file mode 100644
index 0000000000000000000000000000000000000000..580c4fc81a2952f0042eb273ffcb9be35b0a3df5
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/IncrementalDependencyTest.java
@@ -0,0 +1,168 @@
+package org.jastadd.ragconnect.tests;
+
+import incremental.ast.A;
+import incremental.ast.B;
+import incremental.ast.MqttHandler;
+import org.junit.jupiter.api.Tag;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+import static org.jastadd.ragconnect.tests.TestUtils.mqttUri;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Testcase "Incremental Dependency".
+ *
+ * @author rschoene - Initial contribution
+ */
+@Tag("Incremental")
+public class IncrementalDependencyTest extends AbstractMqttTest {
+
+  private static final String TOPIC_IN = "in/a";
+  private static final String TOPIC_OUT_A = "out/a";
+  private static final String TOPIC_OUT_B1 = "out/b1";
+  private static final String TOPIC_OUT_B2 = "out/b2";
+
+  private MqttHandler handler;
+  private A model;
+  private B b1;
+  private B b2;
+
+  private ReceiverData dataA;
+  private ReceiverData dataB1;
+  private ReceiverData dataB2;
+
+  @Override
+  protected void createModel() {
+    model = new A();
+    model.setInput("Start");
+    b1 = new B();
+    b2 = new B();
+    model.addB(b1);
+    model.addB(b2);
+    model.ragconnectCheckIncremental();
+  }
+
+  @Override
+  protected void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException {
+    model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS);
+
+    handler = new MqttHandler("TestHandler")
+        .dontSendWelcomeMessage()
+        .setHost(TestUtils.getMqttHost());
+    assertTrue(handler.waitUntilReady(2, TimeUnit.SECONDS));
+
+    // no dependencies for the model are set here
+
+    dataA = new ReceiverData();
+    dataB1 = new ReceiverData();
+    dataB2 = new ReceiverData();
+
+    handler.newConnection(TOPIC_OUT_A, bytes -> {
+      dataA.numberOfStringValues += 1;
+      dataA.lastStringValue = TestUtils.DefaultMappings.BytesToString(bytes);
+    });
+    handler.newConnection(TOPIC_OUT_B1, bytes -> {
+      dataB1.numberOfStringValues += 1;
+      dataB1.lastStringValue = TestUtils.DefaultMappings.BytesToString(bytes);
+    });
+    handler.newConnection(TOPIC_OUT_B2, bytes -> {
+      dataB2.numberOfStringValues += 1;
+      dataB2.lastStringValue = TestUtils.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));
+  }
+
+  @Override
+  protected void communicateSendInitialValue() throws InterruptedException {
+    // check initial value
+    TestUtils.waitForMqtt();
+    checkData(1, "aStart",
+        "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");
+  }
+
+  @Override
+  protected void communicateOnlyUpdatedValue() throws InterruptedException {
+    // check initial value
+    TestUtils.waitForMqtt();
+    checkData(0, null,
+        null,
+        null);
+
+    // send and check new value
+    sendData("102");
+    checkData(1, "a102",
+        "b102Postfix",
+        "b102Postfix");
+
+    // send and check same value
+    sendData("102");
+    checkData(1, "a102",
+        "b102Postfix",
+        "b102Postfix");
+
+    // send and check new value
+    sendData("202");
+    checkData(2, "a202",
+        "b202Postfix",
+        "b202Postfix");
+
+  }
+
+  @Override
+  protected void closeConnections() {
+    if (handler != null) {
+      handler.close();
+    }
+    if (model != null) {
+      model.ragconnectCloseConnections();
+    }
+  }
+
+  private void sendData(String input) throws InterruptedException {
+    publisher.publish(TOPIC_IN, input.getBytes());
+    TestUtils.waitForMqtt();
+  }
+
+  private void checkData(int expectedNumberOfValues, String expectedLastAValue,
+                         String expectedLastB1Value, String expectedLastB2Value) {
+    dataA.assertEqualData(expectedNumberOfValues, expectedLastAValue);
+    dataB1.assertEqualData(expectedNumberOfValues, expectedLastB1Value);
+    dataB2.assertEqualData(expectedNumberOfValues, expectedLastB2Value);
+  }
+
+  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/MappingTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/MappingTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..52d750223efb102f73e5f63d83aaf8c85ae6847f
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/MappingTest.java
@@ -0,0 +1,231 @@
+package org.jastadd.ragconnect.tests;
+
+import mapping.ast.A;
+import mapping.ast.BoxedTypes;
+import mapping.ast.MqttHandler;
+import mapping.ast.NativeTypes;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+import static org.jastadd.ragconnect.tests.TestUtils.mqttUri;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Test case "mapping".
+ *
+ * @author rschoene - Initial contribution
+ */
+public class MappingTest extends AbstractMqttTest {
+
+  private static final String TOPIC_INPUT = "input";
+  private static final String TOPIC_WRITE_NATIVE_INT = "native/int";
+  private static final String TOPIC_WRITE_NATIVE_SHORT = "native/short";
+  private static final String TOPIC_WRITE_NATIVE_LONG = "native/long";
+  private static final String TOPIC_WRITE_NATIVE_FLOAT = "native/float";
+  private static final String TOPIC_WRITE_NATIVE_DOUBLE = "native/double";
+  private static final String TOPIC_WRITE_NATIVE_CHAR = "native/char";
+  private static final String TOPIC_WRITE_NATIVE_BOOLEAN = "native/boolean";
+
+  private A model;
+  private NativeTypes natives;
+  private BoxedTypes boxes;
+  private MqttHandler handler;
+  private ReceiverData data;
+
+  @Override
+  protected void createModel() {
+    model = new A();
+    natives = new NativeTypes();
+    natives.setDriver("1");
+    boxes = new BoxedTypes();
+    model.setNativeTypes(natives);
+    model.setBoxedTypes(boxes);
+  }
+
+  @Override
+  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));
+
+    natives.addNativeIntDependency(natives);
+    natives.addNativeShortDependency(natives);
+    natives.addNativeLongDependency(natives);
+    natives.addNativeFloatDependency(natives);
+    natives.addNativeDoubleDependency(natives);
+    natives.addNativeCharDependency(natives);
+    natives.addNativeBooleanDependency(natives);
+
+    data = new ReceiverData();
+    handler.newConnection(TOPIC_WRITE_NATIVE_INT, bytes -> {
+      data.numberOfNativeIntValues += 1;
+      data.lastNativeIntValue = TestUtils.DefaultMappings.BytesToInt(bytes);
+    });
+    handler.newConnection(TOPIC_WRITE_NATIVE_SHORT, bytes -> {
+      data.numberOfNativeShortValues += 1;
+      data.lastNativeShortValue = TestUtils.DefaultMappings.BytesToShort(bytes);
+    });
+    handler.newConnection(TOPIC_WRITE_NATIVE_LONG, bytes -> {
+      data.numberOfNativeLongValues += 1;
+      data.lastNativeLongValue = TestUtils.DefaultMappings.BytesToLong(bytes);
+    });
+    handler.newConnection(TOPIC_WRITE_NATIVE_FLOAT, bytes -> {
+      data.numberOfNativeFloatValues += 1;
+      data.lastNativeFloatValue = TestUtils.DefaultMappings.BytesToFloat(bytes);
+    });
+    handler.newConnection(TOPIC_WRITE_NATIVE_DOUBLE, bytes -> {
+      data.numberOfNativeDoubleValues += 1;
+      data.lastNativeDoubleValue = TestUtils.DefaultMappings.BytesToDouble(bytes);
+    });
+    handler.newConnection(TOPIC_WRITE_NATIVE_CHAR, bytes -> {
+      data.numberOfNativeCharValues += 1;
+      data.lastNativeCharValue = TestUtils.DefaultMappings.BytesToChar(bytes);
+    });
+    handler.newConnection(TOPIC_WRITE_NATIVE_BOOLEAN, bytes -> {
+      data.numberOfNativeBooleanValues += 1;
+      data.lastNativeBooleanValue = TestUtils.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.connectIntValue(mqttUri(TOPIC_INPUT)));
+    assertTrue(natives.connectShortValue(mqttUri(TOPIC_INPUT)));
+    assertTrue(natives.connectLongValue(mqttUri(TOPIC_INPUT)));
+    assertTrue(natives.connectFloatValue(mqttUri(TOPIC_INPUT)));
+    assertTrue(natives.connectDoubleValue(mqttUri(TOPIC_INPUT)));
+    assertTrue(natives.connectCharValue(mqttUri(TOPIC_INPUT)));
+    assertTrue(natives.connectBooleanValue(mqttUri(TOPIC_INPUT)));
+
+    assertTrue(boxes.connectIntValue(mqttUri(TOPIC_INPUT)));
+    assertTrue(boxes.connectShortValue(mqttUri(TOPIC_INPUT)));
+    assertTrue(boxes.connectLongValue(mqttUri(TOPIC_INPUT)));
+    assertTrue(boxes.connectFloatValue(mqttUri(TOPIC_INPUT)));
+    assertTrue(boxes.connectDoubleValue(mqttUri(TOPIC_INPUT)));
+    assertTrue(boxes.connectCharValue(mqttUri(TOPIC_INPUT)));
+    assertTrue(boxes.connectBooleanValue(mqttUri(TOPIC_INPUT)));
+  }
+
+  @Override
+  protected void communicateSendInitialValue() throws InterruptedException {
+    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);
+  }
+
+  @Override
+  protected void communicateOnlyUpdatedValue() throws InterruptedException {
+    checkSendData(0, 0, (short) 0, 0, 0f, 0d, (char) 0, 0, false);
+    // no check for initial received data (no input set yet)
+
+    // 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);
+
+    // 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);
+
+    // 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);
+  }
+
+  @Override
+  protected void closeConnections() {
+    if (handler != null) {
+      handler.close();
+    }
+    if (model != null) {
+      model.ragconnectCloseConnections();
+    }
+  }
+
+  private void sendAndSetData(String driver, String input) {
+    natives.setDriver(driver);
+    publisher.publish(TOPIC_INPUT, input.getBytes());
+  }
+
+  private void checkSendData(int expectedNumberOfValues, int expectedInt, short expectedShort, long expectedLong, float expectedFloat, double expectedDouble, char expectedChar, int expectedNumberOfBooleanValues, boolean expectedBoolean) throws InterruptedException {
+    TestUtils.waitForMqtt();
+    assertEquals(expectedNumberOfValues, data.numberOfNativeIntValues);
+    assertEquals(expectedNumberOfValues, data.numberOfNativeShortValues);
+    assertEquals(expectedNumberOfValues, data.numberOfNativeLongValues);
+    assertEquals(expectedNumberOfValues, data.numberOfNativeFloatValues);
+    assertEquals(expectedNumberOfValues, data.numberOfNativeDoubleValues);
+    assertEquals(expectedNumberOfValues, data.numberOfNativeCharValues);
+    assertEquals(expectedNumberOfBooleanValues, data.numberOfNativeBooleanValues);
+
+    if (expectedNumberOfValues == 0) {
+      return;
+    }
+    assertEquals(expectedInt, data.lastNativeIntValue);
+    assertEquals(expectedShort, data.lastNativeShortValue);
+    assertEquals(expectedLong, data.lastNativeLongValue);
+    assertEquals(expectedFloat, data.lastNativeFloatValue, TestUtils.DELTA);
+    assertEquals(expectedDouble, data.lastNativeDoubleValue, TestUtils.DELTA);
+    assertEquals(expectedChar, data.lastNativeCharValue);
+    assertEquals(expectedBoolean, data.lastNativeBooleanValue);
+  }
+
+  private void checkReceiveData(int expectedInt, short expectedShort, long expectedLong, float expectedFloat, double expectedDouble, char expectedChar, boolean expectedBoolean) {
+    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());
+
+    assertEquals(expectedInt, boxes.getIntValue());
+    assertEquals(expectedShort, boxes.getShortValue());
+    assertEquals(expectedLong, boxes.getLongValue());
+    assertEquals(expectedFloat, boxes.getFloatValue(), TestUtils.DELTA);
+    assertEquals(expectedDouble, boxes.getDoubleValue(), TestUtils.DELTA);
+    assertEquals(expectedChar, boxes.getCharValue());
+    assertEquals(expectedBoolean, boxes.getBooleanValue());
+  }
+
+  private static class ReceiverData {
+    int lastNativeIntValue;
+    int numberOfNativeIntValues = 0;
+    short lastNativeShortValue;
+    int numberOfNativeShortValues = 0;
+    long lastNativeLongValue;
+    int numberOfNativeLongValues = 0;
+    float lastNativeFloatValue;
+    int numberOfNativeFloatValues = 0;
+    double lastNativeDoubleValue;
+    int numberOfNativeDoubleValues = 0;
+    char lastNativeCharValue;
+    int numberOfNativeCharValues = 0;
+    boolean lastNativeBooleanValue;
+    int numberOfNativeBooleanValues = 0;
+  }
+
+}
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/MqttHandlerTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/MqttHandlerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..091990a64a31c01a6f2c59b934cfe15b6fb76ece
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/MqttHandlerTest.java
@@ -0,0 +1,72 @@
+package org.jastadd.ragconnect.tests;
+
+import example.ast.MqttHandler;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.fail;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Testing the {@link MqttHandler} used in the "example" test case.
+ *
+ * @author rschoene - Initial contribution
+ */
+@Tag("mqtt")
+public class MqttHandlerTest {
+
+  @Test
+  public void defaultBehaviour() {
+    MqttHandler handler = new MqttHandler();
+    try {
+      handler.setHost(TestUtils.getMqttHost());
+    } catch (IOException e) {
+      fail("Fail during setHost", e);
+    }
+    boolean ready = handler.waitUntilReady(2, TimeUnit.SECONDS);
+    assertTrue(ready);
+    handler.close();
+  }
+
+  @Test
+  public void testWelcomeMessage() throws Exception {
+    MqttHandler welcomeMessageSubscriber = new MqttHandler();
+    List<String> receivedMessages = new ArrayList<>();
+    welcomeMessageSubscriber.setHost(TestUtils.getMqttHost());
+    assertTrue(welcomeMessageSubscriber.waitUntilReady(2, TimeUnit.SECONDS));
+    welcomeMessageSubscriber.newConnection("components", bytes -> receivedMessages.add(new String(bytes)));
+    assertThat(receivedMessages).isEmpty();
+
+    MqttHandler handler = new MqttHandler();
+    handler.setHost(TestUtils.getMqttHost());
+    assertTrue(handler.waitUntilReady(2, TimeUnit.SECONDS));
+    TestUtils.waitForMqtt();
+
+    assertEquals(1, receivedMessages.size());
+  }
+
+  @Test
+  public void testDontSendWelcomeMessage() throws Exception {
+    MqttHandler welcomeMessageSubscriber = new MqttHandler();
+    List<String> receivedMessages = new ArrayList<>();
+    welcomeMessageSubscriber.setHost(TestUtils.getMqttHost());
+    assertTrue(welcomeMessageSubscriber.waitUntilReady(2, TimeUnit.SECONDS));
+    welcomeMessageSubscriber.newConnection("components", bytes -> receivedMessages.add(new String(bytes)));
+    assertThat(receivedMessages).isEmpty();
+
+    MqttHandler handler = new MqttHandler().dontSendWelcomeMessage();
+    handler.setHost(TestUtils.getMqttHost());
+    assertTrue(handler.waitUntilReady(2, TimeUnit.SECONDS));
+    TestUtils.waitForMqtt();
+
+    assertThat(receivedMessages).isEmpty();
+  }
+
+}
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Read1Write2Test.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Read1Write2Test.java
index 2940aacbf26ae1a6e63cd7c763868d6f48639217..76eac958448c69e0ac1158074a4ddac12f4027f9 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Read1Write2Test.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Read1Write2Test.java
@@ -37,78 +37,6 @@ public class Read1Write2Test extends AbstractMqttTest {
   private ReceiverData dataOther1;
   private ReceiverData dataOther2;
 
-  @Override
-  public void closeConnections() {
-    if (handler != null) {
-      handler.close();
-    }
-    if (model != null) {
-      model.ragconnectCloseConnections();
-    }
-  }
-
-
-
-  @Override
-  protected void communicateSendInitialValue() throws InterruptedException {
-    // check initial value
-    TestUtils.waitForMqtt();
-    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
-    TestUtils.waitForMqtt();
-    checkData(2, 2, prefixed("2"), 2, 3, prefixed("3"));
-
-    // set new value
-    sendData("4", "4");
-
-    // check new value
-    TestUtils.waitForMqtt();
-    checkData(3, 4, prefixed("4"), 3, 4, prefixed("4"));
-
-    // set new value only for same
-    setDataOnlySame("77");
-
-    // check new value
-    TestUtils.waitForMqtt();
-    checkData(4, 77, prefixed("77"), 3, 4, prefixed("4"));
-  }
-
-  private String prefixed(String s) {
-    return "prefix" + s;
-  }
-
-  @Override
-  protected void communicateOnlyUpdatedValue() throws InterruptedException {
-// check initial value
-    TestUtils.waitForMqtt();
-    checkData(0, null, null, 0, null, null);
-
-    // set new value
-    sendData("2", "3");
-
-    // check new value
-    TestUtils.waitForMqtt();
-    checkData(1, 2, prefixed("2"), 1, 3, prefixed("3"));
-
-    // set new value
-    sendData("4", "4");
-
-    // check new value
-    TestUtils.waitForMqtt();
-    checkData(2, 4, prefixed("4"), 2, 4, prefixed("4"));
-
-    // set new value only for same
-    setDataOnlySame("77");
-
-    // check new value
-    TestUtils.waitForMqtt();
-    checkData(3, 77, prefixed("77"), 2, 4, prefixed("4"));
-  }
-
   @Override
   protected void createModel() {
     // Setting value for Input without dependencies does not trigger any updates
@@ -147,45 +75,107 @@ public class Read1Write2Test extends AbstractMqttTest {
 
     handler.newConnection(TOPIC_SAME_WRITE_INT, bytes -> {
       dataSame.numberOfIntValues += 1;
-      dataSame.lastIntValue = java.nio.ByteBuffer.wrap(bytes).getInt();
+      dataSame.lastIntValue = TestUtils.DefaultMappings.BytesToInt(bytes);
     });
     handler.newConnection(TOPIC_SAME_WRITE_STRING, bytes -> {
       dataSame.numberOfStringValues += 1;
-      dataSame.lastStringValue = new String(bytes);
+      dataSame.lastStringValue = TestUtils.DefaultMappings.BytesToString(bytes);
     });
 
     handler.newConnection(TOPIC_DIFFERENT_WRITE1_INT, bytes -> {
       dataOther1.numberOfIntValues += 1;
-      dataOther1.lastIntValue = java.nio.ByteBuffer.wrap(bytes).getInt();
+      dataOther1.lastIntValue = TestUtils.DefaultMappings.BytesToInt(bytes);
     });
     handler.newConnection(TOPIC_DIFFERENT_WRITE1_STRING, bytes -> {
       dataOther1.numberOfStringValues += 1;
-      dataOther1.lastStringValue = new String(bytes);
+      dataOther1.lastStringValue = TestUtils.DefaultMappings.BytesToString(bytes);
     });
 
     handler.newConnection(TOPIC_DIFFERENT_WRITE2_INT, bytes -> {
       dataOther2.numberOfIntValues += 1;
-      dataOther2.lastIntValue = java.nio.ByteBuffer.wrap(bytes).getInt();
+      dataOther2.lastIntValue = TestUtils.DefaultMappings.BytesToInt(bytes);
     });
     handler.newConnection(TOPIC_DIFFERENT_WRITE2_STRING, bytes -> {
       dataOther2.numberOfStringValues += 1;
-      dataOther2.lastStringValue = new String(bytes);
+      dataOther2.lastStringValue = TestUtils.DefaultMappings.BytesToString(bytes);
     });
 
-    onSameNonterminal.connectInput(mqttUri(TOPIC_SAME_READ));
-    onSameNonterminal.connectOutInteger(mqttUri(TOPIC_SAME_WRITE_INT), writeCurrentValue);
-    onSameNonterminal.connectOutString(mqttUri(TOPIC_SAME_WRITE_STRING), writeCurrentValue);
+    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));
 
-    onDifferentNonterminal.connectInput(mqttUri(TOPIC_DIFFERENT_READ));
-    other1.connectOutInteger(mqttUri(TOPIC_DIFFERENT_WRITE1_INT), writeCurrentValue);
-    other1.connectOutString(mqttUri(TOPIC_DIFFERENT_WRITE1_STRING), writeCurrentValue);
-    other2.connectOutInteger(mqttUri(TOPIC_DIFFERENT_WRITE2_INT), writeCurrentValue);
-    other2.connectOutString(mqttUri(TOPIC_DIFFERENT_WRITE2_STRING), writeCurrentValue);
+    assertTrue(onDifferentNonterminal.connectInput(mqttUri(TOPIC_DIFFERENT_READ)));
+    assertTrue(other1.connectOutInteger(mqttUri(TOPIC_DIFFERENT_WRITE1_INT), writeCurrentValue));
+    assertTrue(other1.connectOutString(mqttUri(TOPIC_DIFFERENT_WRITE1_STRING), writeCurrentValue));
+    assertTrue(other2.connectOutInteger(mqttUri(TOPIC_DIFFERENT_WRITE2_INT), writeCurrentValue));
+    assertTrue(other2.connectOutString(mqttUri(TOPIC_DIFFERENT_WRITE2_STRING), writeCurrentValue));
+  }
+
+  @Override
+  protected void communicateSendInitialValue() throws InterruptedException {
+    // 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"));
+  }
+
+  @Override
+  protected void communicateOnlyUpdatedValue() throws InterruptedException {
+// check initial value
+    checkData(0, null, null, 0, null, null);
+
+    // set new value
+    sendData("2", "3");
+
+    // check new value
+    checkData(1, 2, prefixed("2"), 1, 3, prefixed("3"));
+
+    // set new value
+    sendData("4", "4");
+
+    // check new value
+    checkData(2, 4, prefixed("4"), 2, 4, prefixed("4"));
+
+    // set new value only for same
+    setDataOnlySame("78");
+
+    // check new value
+    checkData(3, 78, prefixed("78"), 2, 4, prefixed("4"));
+  }
+
+  @Override
+  public void closeConnections() {
+    if (handler != null) {
+      handler.close();
+    }
+    if (model != null) {
+      model.ragconnectCloseConnections();
+    }
+  }
+
+  private String prefixed(String s) {
+    return "prefix" + s;
   }
 
   private void sendData(String inputSame, String inputDifferent) {
-    handler.publish(TOPIC_SAME_READ, inputSame.getBytes());
-    handler.publish(TOPIC_DIFFERENT_READ, inputDifferent.getBytes());
+    publisher.publish(TOPIC_SAME_READ, inputSame.getBytes());
+    publisher.publish(TOPIC_DIFFERENT_READ, inputDifferent.getBytes());
   }
 
   private void setDataOnlySame(String inputSame) {
@@ -194,32 +184,13 @@ public class Read1Write2Test extends AbstractMqttTest {
 
   private void checkData(int numberOfSameValues, Integer lastSameIntValue, String lastSameStringValue,
                          int numberOfDifferentValues, Integer lastDifferentIntValue,
-                         String lastDifferentStringValue) {
-    /* the value "-2" is never used in the test, so a test will always fail comparing to this value
-     especially, it is not the initial value */
-    ReceiverData expectedDataSame = ReceiverData.of(
-        numberOfSameValues,
-        lastSameIntValue != null ? lastSameIntValue : -2,
-        lastSameStringValue);
-    compareData(expectedDataSame, dataSame);
-    ReceiverData expectedDataDifferent = ReceiverData.of(
-        numberOfDifferentValues,
-        lastDifferentIntValue != null ? lastDifferentIntValue : -2,
-        lastDifferentStringValue);
-    compareData(expectedDataDifferent, dataOther1);
-    compareData(expectedDataDifferent, dataOther2);
-  }
-
-  private void compareData(ReceiverData expectedData,
-                         ReceiverData actual) {
-    assertEquals(expectedData.numberOfIntValues, actual.numberOfIntValues);
-    assertEquals(expectedData.numberOfStringValues, actual.numberOfStringValues);
-    if (expectedData.numberOfIntValues > 0) {
-      assertEquals(expectedData.lastIntValue, actual.lastIntValue);
-    }
-    if (expectedData.numberOfStringValues > 0) {
-      assertEquals(expectedData.lastStringValue, actual.lastStringValue);
-    }
+                         String lastDifferentStringValue) throws InterruptedException {
+    TestUtils.waitForMqtt();
+    dataSame.assertEqualData(numberOfSameValues, lastSameIntValue, lastSameStringValue);
+    dataOther1.assertEqualData(numberOfDifferentValues,
+        lastDifferentIntValue, lastDifferentStringValue);
+    dataOther2.assertEqualData(numberOfDifferentValues,
+        lastDifferentIntValue, lastDifferentStringValue);
   }
 
   private static class ReceiverData {
@@ -228,14 +199,19 @@ public class Read1Write2Test extends AbstractMqttTest {
     String lastStringValue;
     int numberOfStringValues = 0;
 
-    static ReceiverData of(int numberOfValues, int lastIntValue, String lastStringValue) {
-      ReceiverData result = new ReceiverData();
-      result.lastIntValue = lastIntValue;
-      result.lastStringValue = lastStringValue;
-      result.numberOfIntValues = numberOfValues;
-      result.numberOfStringValues = numberOfValues;
-      return result;
+    void assertEqualData(int expectedNumberOfValues,
+                         Integer expectedLastIntValue,
+                         String expectedLastStringValue) {
+      /* the value "-2" is never used in the test, so a test will always fail comparing to this value
+         especially, it is not the initial value */
+      assertEquals(expectedNumberOfValues, this.numberOfIntValues);
+      assertEquals(expectedNumberOfValues, this.numberOfStringValues);
+      if (expectedNumberOfValues > 0) {
+        assertEquals(expectedLastIntValue != null ? expectedLastIntValue : -2, this.lastIntValue);
+      }
+      if (expectedNumberOfValues > 0) {
+        assertEquals(expectedLastStringValue, this.lastStringValue);
+      }
     }
   }
-
 }
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 4790b173bab7cab2d6022e35c40125af7b79ed5f..f6a0d33a4b70689a1c883f866d33442defd2b786 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Read2Write1Test.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Read2Write1Test.java
@@ -37,21 +37,71 @@ public class Read2Write1Test extends AbstractMqttTest {
   private ReceiverData dataOther2;
 
   @Override
-  public void closeConnections() {
-    if (handler != null) {
-      handler.close();
-    }
-    if (model != null) {
-      model.ragconnectCloseConnections();
-    }
+  protected void createModel() {
+    // Setting value for Input without dependencies does not trigger any updates
+    model = new A();
+
+    onSameNonterminal = new OnSameNonterminal();
+    model.setOnSameNonterminal(onSameNonterminal);
+    onSameNonterminal.setInput1(INITIAL_VALUE);
+    onSameNonterminal.setInput2(INITIAL_VALUE);
+
+    onDifferentNonterminal = new OnDifferentNonterminal();
+    other1 = new TheOther();
+    other2 = new TheOther();
+    onDifferentNonterminal.addTheOther(other1);
+    onDifferentNonterminal.addTheOther(other2);
+    model.setOnDifferentNonterminal(onDifferentNonterminal);
+    onDifferentNonterminal.setInput1(INITIAL_VALUE);
+    onDifferentNonterminal.setInput2(INITIAL_VALUE);
   }
 
+  @Override
+  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));
+
+    onSameNonterminal.addInt1Dependency(onSameNonterminal);
+    onSameNonterminal.addInt2Dependency(onSameNonterminal);
+    other1.addInt1Dependency(onDifferentNonterminal);
+    other1.addInt2Dependency(onDifferentNonterminal);
+    other2.addInt1Dependency(onDifferentNonterminal);
+    other2.addInt2Dependency(onDifferentNonterminal);
+
+    dataSame = new Read2Write1Test.ReceiverData();
+    dataOther1 = new Read2Write1Test.ReceiverData();
+    dataOther2 = new Read2Write1Test.ReceiverData();
+
+    handler.newConnection(TOPIC_SAME_WRITE_INT, bytes -> {
+      dataSame.numberOfIntValues += 1;
+      dataSame.lastIntValue = TestUtils.DefaultMappings.BytesToInt(bytes);
+    });
+
+    handler.newConnection(TOPIC_DIFFERENT_WRITE1_INT, bytes -> {
+      dataOther1.numberOfIntValues += 1;
+      dataOther1.lastIntValue = TestUtils.DefaultMappings.BytesToInt(bytes);
+    });
+
+    handler.newConnection(TOPIC_DIFFERENT_WRITE2_INT, bytes -> {
+      dataOther2.numberOfIntValues += 1;
+      dataOther2.lastIntValue = TestUtils.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(onDifferentNonterminal.connectInput1(mqttUri(TOPIC_DIFFERENT_READ1)));
+    assertTrue(onDifferentNonterminal.connectInput2(mqttUri(TOPIC_DIFFERENT_READ2)));
+    assertTrue(other1.connectOutInteger(mqttUri(TOPIC_DIFFERENT_WRITE1_INT), writeCurrentValue));
+    assertTrue(other2.connectOutInteger(mqttUri(TOPIC_DIFFERENT_WRITE2_INT), writeCurrentValue));
+  }
 
   @Override
   protected void communicateSendInitialValue() throws InterruptedException {
     // check initial value
-    TestUtils.waitForMqtt();
     checkData(1, Integer.parseInt(INITIAL_VALUE + INITIAL_VALUE),
         1, Integer.parseInt(INITIAL_VALUE + INITIAL_VALUE));
 
@@ -59,7 +109,6 @@ public class Read2Write1Test extends AbstractMqttTest {
     sendData(true, "2", true, "3");
 
     // check new value. same: 2, 0. different: 3, 0.
-    TestUtils.waitForMqtt();
     checkData(2, 20,
         2, 30);
 
@@ -67,27 +116,20 @@ public class Read2Write1Test extends AbstractMqttTest {
     sendData(false, "4", false, "4");
 
     // check new value. same: 2, 4. different: 3, 4.
-    TestUtils.waitForMqtt();
     checkData(3, 24,
         3, 34);
 
     // set new value only for same
-    setDataOnlySame(true, "77");
+    setDataOnlySame("77");
 
     // check new value. same: 77, 4. different: 3, 4.
-    TestUtils.waitForMqtt();
     checkData(4, 774,
         3, 34);
   }
 
-  private String prefixed(String s) {
-    return "prefix" + s;
-  }
-
   @Override
   protected void communicateOnlyUpdatedValue() throws InterruptedException {
     // check initial value
-    TestUtils.waitForMqtt();
     checkData(0, null,
         0, null);
 
@@ -95,7 +137,6 @@ public class Read2Write1Test extends AbstractMqttTest {
     sendData(true, "2", true, "3");
 
     // check new value. same: 2, 0. different: 3, 0.
-    TestUtils.waitForMqtt();
     checkData(1, 20,
         1, 30);
 
@@ -103,129 +144,61 @@ public class Read2Write1Test extends AbstractMqttTest {
     sendData(false, "4", false, "4");
 
     // check new value. same: 2, 4. different: 3, 4.
-    TestUtils.waitForMqtt();
     checkData(2, 24,
         2, 34);
 
     // set new value only for same
-    setDataOnlySame(true, "77");
+    setDataOnlySame("78");
 
-    // check new value. same: 77, 4. different: 3, 4.
-    TestUtils.waitForMqtt();
-    checkData(3, 774,
+    // check new value. same: 78, 4. different: 3, 4.
+    checkData(3, 784,
         2, 34);
   }
 
   @Override
-  protected void createModel() {
-    // Setting value for Input without dependencies does not trigger any updates
-    model = new A();
-
-    onSameNonterminal = new OnSameNonterminal();
-    model.setOnSameNonterminal(onSameNonterminal);
-    onSameNonterminal.setInput1(INITIAL_VALUE);
-    onSameNonterminal.setInput2(INITIAL_VALUE);
-
-    onDifferentNonterminal = new OnDifferentNonterminal();
-    other1 = new TheOther();
-    other2 = new TheOther();
-    onDifferentNonterminal.addTheOther(other1);
-    onDifferentNonterminal.addTheOther(other2);
-    model.setOnDifferentNonterminal(onDifferentNonterminal);
-    onDifferentNonterminal.setInput1(INITIAL_VALUE);
-    onDifferentNonterminal.setInput2(INITIAL_VALUE);
-  }
-
-  @Override
-  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));
-
-    onSameNonterminal.addInt1Dependency(onSameNonterminal);
-    onSameNonterminal.addInt2Dependency(onSameNonterminal);
-    other1.addInt1Dependency(onDifferentNonterminal);
-    other1.addInt2Dependency(onDifferentNonterminal);
-    other2.addInt1Dependency(onDifferentNonterminal);
-    other2.addInt2Dependency(onDifferentNonterminal);
-
-    dataSame = new Read2Write1Test.ReceiverData();
-    dataOther1 = new Read2Write1Test.ReceiverData();
-    dataOther2 = new Read2Write1Test.ReceiverData();
-
-    handler.newConnection(TOPIC_SAME_WRITE_INT, bytes -> {
-      dataSame.numberOfIntValues += 1;
-      dataSame.lastIntValue = java.nio.ByteBuffer.wrap(bytes).getInt();
-    });
-
-    handler.newConnection(TOPIC_DIFFERENT_WRITE1_INT, bytes -> {
-      dataOther1.numberOfIntValues += 1;
-      dataOther1.lastIntValue = java.nio.ByteBuffer.wrap(bytes).getInt();
-    });
-
-    handler.newConnection(TOPIC_DIFFERENT_WRITE2_INT, bytes -> {
-      dataOther2.numberOfIntValues += 1;
-      dataOther2.lastIntValue = java.nio.ByteBuffer.wrap(bytes).getInt();
-    });
-
-    onSameNonterminal.connectInput1(mqttUri(TOPIC_SAME_READ1));
-    onSameNonterminal.connectInput2(mqttUri(TOPIC_SAME_READ2));
-    onSameNonterminal.connectOutInteger(mqttUri(TOPIC_SAME_WRITE_INT), writeCurrentValue);
-
-    onDifferentNonterminal.connectInput1(mqttUri(TOPIC_DIFFERENT_READ1));
-    onDifferentNonterminal.connectInput2(mqttUri(TOPIC_DIFFERENT_READ2));
-    other1.connectOutInteger(mqttUri(TOPIC_DIFFERENT_WRITE1_INT), writeCurrentValue);
-    other2.connectOutInteger(mqttUri(TOPIC_DIFFERENT_WRITE2_INT), writeCurrentValue);
+  public void closeConnections() {
+    if (handler != null) {
+      handler.close();
+    }
+    if (model != null) {
+      model.ragconnectCloseConnections();
+    }
   }
 
   private void sendData(boolean useSameInput1, String inputSame,
                         boolean useDifferentInput1, String inputDifferent) {
-    handler.publish(useSameInput1 ? TOPIC_SAME_READ1 : TOPIC_SAME_READ2,
+    publisher.publish(useSameInput1 ? TOPIC_SAME_READ1 : TOPIC_SAME_READ2,
         inputSame.getBytes());
-    handler.publish(useDifferentInput1 ? TOPIC_DIFFERENT_READ1 : TOPIC_DIFFERENT_READ2,
+    publisher.publish(useDifferentInput1 ? TOPIC_DIFFERENT_READ1 : TOPIC_DIFFERENT_READ2,
         inputDifferent.getBytes());
   }
 
-  private void setDataOnlySame(boolean useSameInput1, String inputSame) {
-    handler.publish(useSameInput1 ? TOPIC_SAME_READ1 : TOPIC_DIFFERENT_READ2,
-        inputSame.getBytes());
+  private void setDataOnlySame(String inputSame) {
+    publisher.publish(TOPIC_SAME_READ1, inputSame.getBytes());
   }
 
   private void checkData(int numberOfSameValues, Integer lastSameIntValue,
-                         int numberOfDifferentValues, Integer lastDifferentIntValue) {
+                         int numberOfDifferentValues, Integer lastDifferentIntValue)
+      throws InterruptedException {
+    TestUtils.waitForMqtt();
     /* the value "-2" is never used in the test, so a test will always fail comparing to this value
      especially, it is not the initial value */
-    ReceiverData expectedDataSame = ReceiverData.of(
-        numberOfSameValues,
-        lastSameIntValue != null ? lastSameIntValue : -2
-    );
-    compareData(expectedDataSame, dataSame);
-    ReceiverData expectedDataDifferent = ReceiverData.of(
-        numberOfDifferentValues,
-        lastDifferentIntValue != null ? lastDifferentIntValue : -2
-    );
-    compareData(expectedDataDifferent, dataOther1);
-    compareData(expectedDataDifferent, dataOther2);
-  }
-
-  private void compareData(ReceiverData expectedData,
-                         ReceiverData actual) {
-    assertEquals(expectedData.numberOfIntValues, actual.numberOfIntValues);
-    if (expectedData.numberOfIntValues > 0) {
-      assertEquals(expectedData.lastIntValue, actual.lastIntValue);
-    }
+    dataSame.assertEqualData(numberOfSameValues, lastSameIntValue);
+    dataOther1.assertEqualData(numberOfDifferentValues, lastDifferentIntValue);
+    dataOther2.assertEqualData(numberOfDifferentValues, lastDifferentIntValue);
   }
 
   private static class ReceiverData {
     int lastIntValue;
     int numberOfIntValues = 0;
 
-    static ReceiverData of(int numberOfValues, int lastIntValue) {
-      ReceiverData result = new ReceiverData();
-      result.lastIntValue = lastIntValue;
-      result.numberOfIntValues = numberOfValues;
-      return result;
+    void assertEqualData(int expectedNumberOfValues, Integer expectedLastIntValue) {
+      /* the value "-2" is never used in the test, so a test will always fail comparing to this value
+         especially, it is not the initial value */
+      assertEquals(expectedNumberOfValues, this.numberOfIntValues);
+      if (expectedNumberOfValues > 0) {
+        assertEquals(expectedLastIntValue != null ? expectedLastIntValue : -2, this.lastIntValue);
+      }
     }
   }
 
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/RegressionTests.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/RegressionTests.java
new file mode 100644
index 0000000000000000000000000000000000000000..67c82177d96fe34c6379b9d62537f785292827df
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/RegressionTests.java
@@ -0,0 +1,65 @@
+package org.jastadd.ragconnect.tests;
+
+import org.junit.jupiter.api.Test;
+import via.ast.A;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.util.Collections;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Regression tests for fixed issues.
+ *
+ * @author rschoene - Initial contribution
+ */
+public class RegressionTests {
+
+  private static final String REGRESSION_TEST_OUTPUT_DIRECTORY = "regression-test/";
+
+  @Test
+  public void issue22() {
+    // use model of "via" test case as it uses both mqtt and rest as protocols
+    A a = new A();
+    try {
+      // should fail because of missing scheme
+      assertFalse(a.connectBoth2BothInput("missing/scheme"));
+
+      // should fail because of missing host
+      assertFalse(a.connectBoth2BothInput("mqtt://"));
+
+      // should fail because of missing part
+      assertFalse(a.connectBoth2BothInput("mqtt://localhost"));
+
+      // should fail because of unknown scheme
+      assertFalse(a.connectBoth2BothInput("badScheme://host/some/topic"));
+    } catch (IOException e) {
+      fail(e);
+    }
+  }
+
+  @Test
+  public void issue27() throws IOException {
+    String grammarFile = "regression-tests/issue27/Test.relast";
+    String connectFile = "regression-tests/issue27/Test.connect";
+    grammarFile = ensureNoTrailingNewLine(grammarFile);
+    connectFile = ensureNoTrailingNewLine(connectFile);
+    TestUtils.runCompiler(grammarFile, Collections.singletonList(connectFile), "A", REGRESSION_TEST_OUTPUT_DIRECTORY, 0);
+  }
+
+  private String ensureNoTrailingNewLine(String inputFileSuffix) throws IOException {
+    int dotIndex = inputFileSuffix.lastIndexOf('.');
+    String outFileSuffix = inputFileSuffix.substring(0, dotIndex) + ".noNewLine" + inputFileSuffix.substring(dotIndex);
+    Path inputPath = Paths.get(TestUtils.INPUT_DIRECTORY_PREFIX).resolve(inputFileSuffix);
+    Path outputPath = Paths.get(TestUtils.INPUT_DIRECTORY_PREFIX).resolve(outFileSuffix);
+
+    String content = Files.readString(inputPath);
+    Files.writeString(outputPath, content.stripTrailing(), StandardOpenOption.CREATE);
+
+    return outFileSuffix;
+  }
+}
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TestUtils.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TestUtils.java
index 143c74e984af40f9acf883ed7be2f3eb8adb253a..ce79190fe184f5b777366c25e9eff07cc8fc3576 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TestUtils.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TestUtils.java
@@ -1,12 +1,24 @@
 package org.jastadd.ragconnect.tests;
 
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.jastadd.ragconnect.compiler.Compiler;
+import org.junit.jupiter.api.Assertions;
+
 import java.io.File;
 import java.io.IOException;
 import java.nio.charset.Charset;
 import java.nio.file.Files;
+import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.TimeUnit;
 
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
 /**
  * Utility methods for tests.
  *
@@ -14,7 +26,10 @@ import java.util.concurrent.TimeUnit;
  */
 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) {
@@ -38,6 +53,41 @@ public class TestUtils {
     return 1883;
   }
 
+  public static Path runCompiler(String grammarFile, Iterable<String> connectFiles, String rootNode, String outputDirectory, int expectedReturnValue) {
+
+    assertThat(connectFiles).isNotEmpty();
+
+    Path outPath = Paths.get(OUTPUT_DIRECTORY_PREFIX)
+        .resolve(outputDirectory)
+        .resolve("Compiler.out");
+    ensureCreated(outPath.getParent());
+
+    try {
+      logger.debug("user.dir: {}", System.getProperty("user.dir"));
+      List<String> args = new ArrayList<>() {{
+        add("--o=" + OUTPUT_DIRECTORY_PREFIX + outputDirectory);
+        add("--rootNode=" + rootNode);
+        add("--verbose");
+        add(INPUT_DIRECTORY_PREFIX + grammarFile);
+      }};
+      connectFiles.forEach(connectFile -> args.add(INPUT_DIRECTORY_PREFIX + connectFile));
+
+      int returnValue = exec(Compiler.class, args.toArray(new String[0]), outPath.toFile());
+      Assertions.assertEquals(expectedReturnValue, returnValue, "RagConnect did not return with value " + expectedReturnValue);
+    } catch (IOException | InterruptedException e) {
+      fail(e);
+    }
+    return outPath;
+  }
+
+  private static void ensureCreated(Path directory) {
+    File directoryFile = directory.toFile();
+    if (directoryFile.exists() && directoryFile.isDirectory()) {
+      return;
+    }
+    assertTrue(directoryFile.mkdirs());
+  }
+
   public static int exec(Class<?> klass, String[] args, File err) throws IOException,
       InterruptedException {
     String javaHome = System.getProperty("java.home");
@@ -61,13 +111,214 @@ public class TestUtils {
     return process.exitValue();
   }
 
-  public static String readFile(String path, Charset encoding)
+  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(Paths.get(path));
+    byte[] encoded = Files.readAllBytes(path);
     return new String(encoded, encoding);
   }
 
-  static void waitForMqtt() throws InterruptedException {
-    TimeUnit.SECONDS.sleep(2);
+  public static void waitForMqtt() throws InterruptedException {
+    TimeUnit.MILLISECONDS.sleep(1500);
+  }
+
+  @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
index bc68f5bdb8b19167deb50fa54b52e933235bceb3..6d99149688b33d8401037fb358f736c2509b0921 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TokenValueSendTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TokenValueSendTest.java
@@ -39,13 +39,60 @@ public class TokenValueSendTest extends AbstractMqttTest {
   private ReceiverData dataThreeOther;
 
   @Override
-  public void closeConnections() {
-    if (handler != null) {
-      handler.close();
-    }
-    if (model != null) {
-      model.ragconnectCloseConnections();
-    }
+  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
@@ -169,65 +216,18 @@ public class TokenValueSendTest extends AbstractMqttTest {
   }
 
   @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 = new String(bytes);
-    });
-    handler.newConnection(TOPIC_SEND_TWO, bytes -> {
-      dataTwo.numberOfStringValues += 1;
-      dataTwo.lastStringValue = new String(bytes);
-    });
-
-    handler.newConnection(TOPIC_SEND_THREE_VALUE, bytes -> {
-      dataThree.numberOfStringValues += 1;
-      dataThree.lastStringValue = new String(bytes);
-    });
-    handler.newConnection(TOPIC_SEND_THREE_OTHER, bytes -> {
-      dataThreeOther.numberOfStringValues += 1;
-      dataThreeOther.lastStringValue = new String(bytes);
-    });
-
-    one.connectValue(mqttUri(TOPIC_SEND_ONE), writeCurrentValue);
-    two.connectValue(mqttUri(TOPIC_RECEIVE_TWO));
-    two.connectValue(mqttUri(TOPIC_SEND_TWO), writeCurrentValue);
-    three.connectValue(mqttUri(TOPIC_RECEIVE_THREE_VALUE));
-    three.connectValue(mqttUri(TOPIC_SEND_THREE_VALUE), writeCurrentValue);
-    three.connectOtherOutput(mqttUri(TOPIC_SEND_THREE_OTHER), writeCurrentValue);
+  public void closeConnections() {
+    if (handler != null) {
+      handler.close();
+    }
+    if (model != null) {
+      model.ragconnectCloseConnections();
+    }
   }
 
   private void sendData(String inputTwo, String inputThree) {
-    handler.publish(TOPIC_RECEIVE_TWO, inputTwo.getBytes());
-    handler.publish(TOPIC_RECEIVE_THREE_VALUE, inputThree.getBytes());
+    publisher.publish(TOPIC_RECEIVE_TWO, inputTwo.getBytes());
+    publisher.publish(TOPIC_RECEIVE_THREE_VALUE, inputThree.getBytes());
   }
 
   private void setData(String inputOne, String inputTwo, String inputThree) {
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 d7f346252639630d5818af4ceeef314b7ccecd9e..64ad5fbd147ba6c7e22d24ba7f92fe8d7114ced7 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TutorialTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TutorialTest.java
@@ -2,11 +2,11 @@ package org.jastadd.ragconnect.tests;
 
 import tutorial.ast.A;
 import tutorial.ast.B;
-import tutorial.ast.MqttHandler;
 
 import java.io.IOException;
 
 import static org.jastadd.ragconnect.tests.TestUtils.mqttUri;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 /**
  * Testcase "Tutorial".
@@ -15,21 +15,10 @@ import static org.jastadd.ragconnect.tests.TestUtils.mqttUri;
  */
 public class TutorialTest extends AbstractMqttTest {
 
-  private MqttHandler handler;
   private A a;
   private B b1;
   private B b2;
 
-  @Override
-  protected void closeConnections() {
-    if (handler != null) {
-      handler.close();
-    }
-    if (a != null) {
-      a.ragconnectCloseConnections();
-    }
-  }
-
   @Override
   protected void createModel() {
     a = new A();
@@ -50,10 +39,10 @@ public class TutorialTest extends AbstractMqttTest {
     // b2.OutputOnB -> a.Input
     b2.addDependencyB(a);
 
-    a.connectInput(mqttUri("topic/for/input"));
-    a.connectOutputOnA(mqttUri("a/out"), true);
-    b1.connectOutputOnB(mqttUri("b1/out"), true);
-    b2.connectOutputOnB(mqttUri("b2/out"), false);
+    assertTrue(a.connectInput(mqttUri("topic/for/input")));
+    assertTrue(a.connectOutputOnA(mqttUri("a/out"), true));
+    assertTrue(b1.connectOutputOnB(mqttUri("b1/out"), true));
+    assertTrue(b2.connectOutputOnB(mqttUri("b2/out"), false));
   }
 
   @Override
@@ -65,4 +54,11 @@ public class TutorialTest extends AbstractMqttTest {
   protected void communicateOnlyUpdatedValue() {
     // empty
   }
+
+  @Override
+  protected void closeConnections() {
+    if (a != null) {
+      a.ragconnectCloseConnections();
+    }
+  }
 }
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 3c173522680500703be35e96289f0a6e35117b3a..7ab46f7616a1cf90737c2e0b3ff752f553d38292 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ViaTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ViaTest.java
@@ -57,16 +57,68 @@ public class ViaTest extends AbstractMqttTest {
   private WebTarget senderBoth2Rest;
 
   @Override
-  public void closeConnections() {
-    if (handler != null) {
-      handler.close();
-    }
-    if (model != null) {
-      model.ragconnectCloseConnections();
-    }
+  protected void createModel() {
+    // Setting value for Input without dependencies does not trigger any updates
+    model = new A();
+    model.setMqtt2MqttInput("100");
+    model.setRest2RestInput("200");
+    model.setMqtt2RestInput("300");
+    model.setRest2MqttInput("400");
+    model.setBoth2BothInput("500");
   }
 
+  @Override
+  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));
+
+    model.addDependencyMqtt2Mqtt(model);
+    model.addDependencyRest2Rest(model);
+    model.addDependencyMqtt2Rest(model);
+    model.addDependencyRest2Mqtt(model);
+    model.addDependencyBoth2Mqtt(model);
+    model.addDependencyBoth2Rest(model);
+
+    dataMqtt2Mqtt = new ReceiverData();
+    dataRest2Mqtt = new ReceiverData();
+    dataBoth2Mqtt = new ReceiverData();
+
+    handler.newConnection(TOPIC_MQTT_2_MQTT_SEND, bytes -> {
+      dataMqtt2Mqtt.numberOfStringValues += 1;
+      dataMqtt2Mqtt.lastStringValue = TestUtils.DefaultMappings.BytesToString(bytes);
+    });
+    handler.newConnection(TOPIC_REST_2_MQTT_SEND, bytes -> {
+      dataRest2Mqtt.numberOfStringValues += 1;
+      dataRest2Mqtt.lastStringValue = TestUtils.DefaultMappings.BytesToString(bytes);
+    });
+    handler.newConnection(TOPIC_BOTH_2_MQTT_SEND, bytes -> {
+      dataBoth2Mqtt.numberOfStringValues += 1;
+      dataBoth2Mqtt.lastStringValue = TestUtils.DefaultMappings.BytesToString(bytes);
+    });
+
+    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);
+    dataBoth2Rest = client.target(REST_SERVER_BASE_URL + PATH_BOTH_2_REST_SEND);
+    senderRest2Rest = client.target(REST_SERVER_BASE_URL + PATH_REST_2_REST_RECEIVE);
+    senderRest2Mqtt = client.target(REST_SERVER_BASE_URL + PATH_REST_2_MQTT_RECEIVE);
+    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.connectMqtt2RestInput(mqttUri(TOPIC_MQTT_2_REST_RECEIVE)));
+    assertTrue(model.connectMqtt2RestOutput(restUri(PATH_MQTT_2_REST_SEND, REST_PORT), writeCurrentValue));
+    assertTrue(model.connectRest2MqttInput(restUri(PATH_REST_2_MQTT_RECEIVE, REST_PORT)));
+    assertTrue(model.connectRest2MqttOutput(mqttUri(TOPIC_REST_2_MQTT_SEND), writeCurrentValue));
+    assertTrue(model.connectRest2RestInput(restUri(PATH_REST_2_REST_RECEIVE, REST_PORT)));
+    assertTrue(model.connectRest2RestOutput(restUri(PATH_REST_2_REST_SEND, REST_PORT), writeCurrentValue));
+    assertTrue(model.connectBoth2BothInput(mqttUri(TOPIC_BOTH_MQTT_RECEIVE)));
+    assertTrue(model.connectBoth2BothInput(restUri(PATH_BOTH_REST_RECEIVE, REST_PORT)));
+    assertTrue(model.connectBoth2MqttOutput(mqttUri(TOPIC_BOTH_2_MQTT_SEND), writeCurrentValue));
+    assertTrue(model.connectBoth2RestOutput(restUri(PATH_BOTH_2_REST_SEND, REST_PORT), writeCurrentValue));
+  }
 
   @Override
   protected void communicateSendInitialValue() throws InterruptedException {
@@ -142,7 +194,7 @@ public class ViaTest extends AbstractMqttTest {
 
   @Override
   protected void communicateOnlyUpdatedValue() throws InterruptedException {
-// check initial value
+    // check initial value
     TestUtils.waitForMqtt();
     checkData(0, null,
         "200-R2R-ToRest",
@@ -212,16 +264,26 @@ public class ViaTest extends AbstractMqttTest {
         "512-B2R-ToRest");
   }
 
+  @Override
+  public void closeConnections() {
+    if (handler != null) {
+      handler.close();
+    }
+    if (model != null) {
+      model.ragconnectCloseConnections();
+    }
+  }
+
   private void sendData(String inputMqtt2Mqtt, String inputRest2Rest, String inputMqtt2Rest, String inputRest2Mqtt) {
-    handler.publish(TOPIC_MQTT_2_MQTT_RECEIVE, inputMqtt2Mqtt.getBytes());
+    publisher.publish(TOPIC_MQTT_2_MQTT_RECEIVE, inputMqtt2Mqtt.getBytes());
     senderRest2Rest.request().put(Entity.entity(inputRest2Rest, MediaType.TEXT_PLAIN_TYPE));
-    handler.publish(TOPIC_MQTT_2_REST_RECEIVE, inputMqtt2Rest.getBytes());
+    publisher.publish(TOPIC_MQTT_2_REST_RECEIVE, inputMqtt2Rest.getBytes());
     senderRest2Mqtt.request().put(Entity.entity(inputRest2Mqtt, MediaType.TEXT_PLAIN_TYPE));
   }
 
   private void sendDataForBoth(String input, boolean useMqtt) {
     if (useMqtt) {
-      handler.publish(TOPIC_BOTH_MQTT_RECEIVE, input.getBytes());
+      publisher.publish(TOPIC_BOTH_MQTT_RECEIVE, input.getBytes());
     } else {
       senderBoth2Rest.request().put(Entity.entity(input, MediaType.TEXT_PLAIN_TYPE));
     }
@@ -248,70 +310,6 @@ public class ViaTest extends AbstractMqttTest {
     return dataBoth2Rest.request().get().readEntity(String.class);
   }
 
-  @Override
-  protected void createModel() {
-    // Setting value for Input without dependencies does not trigger any updates
-    model = new A();
-    model.setMqtt2MqttInput("100");
-    model.setRest2RestInput("200");
-    model.setMqtt2RestInput("300");
-    model.setRest2MqttInput("400");
-    model.setBoth2BothInput("500");
-  }
-
-  @Override
-  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));
-
-    model.addDependencyMqtt2Mqtt(model);
-    model.addDependencyRest2Rest(model);
-    model.addDependencyMqtt2Rest(model);
-    model.addDependencyRest2Mqtt(model);
-    model.addDependencyBoth2Mqtt(model);
-    model.addDependencyBoth2Rest(model);
-
-    dataMqtt2Mqtt = new ReceiverData();
-    dataRest2Mqtt = new ReceiverData();
-    dataBoth2Mqtt = new ReceiverData();
-
-    handler.newConnection(TOPIC_MQTT_2_MQTT_SEND, bytes -> {
-      dataMqtt2Mqtt.numberOfStringValues += 1;
-      dataMqtt2Mqtt.lastStringValue = new String(bytes);
-    });
-    handler.newConnection(TOPIC_REST_2_MQTT_SEND, bytes -> {
-      dataRest2Mqtt.numberOfStringValues += 1;
-      dataRest2Mqtt.lastStringValue = new String(bytes);
-    });
-    handler.newConnection(TOPIC_BOTH_2_MQTT_SEND, bytes -> {
-      dataBoth2Mqtt.numberOfStringValues += 1;
-      dataBoth2Mqtt.lastStringValue = new String(bytes);
-    });
-
-    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);
-    dataBoth2Rest = client.target(REST_SERVER_BASE_URL + PATH_BOTH_2_REST_SEND);
-    senderRest2Rest = client.target(REST_SERVER_BASE_URL + PATH_REST_2_REST_RECEIVE);
-    senderRest2Mqtt = client.target(REST_SERVER_BASE_URL + PATH_REST_2_MQTT_RECEIVE);
-    senderBoth2Rest = client.target(REST_SERVER_BASE_URL + PATH_BOTH_REST_RECEIVE);
-
-    model.connectMqtt2MqttInput(mqttUri(TOPIC_MQTT_2_MQTT_RECEIVE));
-    model.connectMqtt2MqttOutput(mqttUri(TOPIC_MQTT_2_MQTT_SEND), writeCurrentValue);
-    model.connectMqtt2RestInput(mqttUri(TOPIC_MQTT_2_REST_RECEIVE));
-    model.connectMqtt2RestOutput(restUri(PATH_MQTT_2_REST_SEND, REST_PORT), writeCurrentValue);
-    model.connectRest2MqttInput(restUri(PATH_REST_2_MQTT_RECEIVE, REST_PORT));
-    model.connectRest2MqttOutput(mqttUri(TOPIC_REST_2_MQTT_SEND), writeCurrentValue);
-    model.connectRest2RestInput(restUri(PATH_REST_2_REST_RECEIVE, REST_PORT));
-    model.connectRest2RestOutput(restUri(PATH_REST_2_REST_SEND, REST_PORT), writeCurrentValue);
-    model.connectBoth2BothInput(mqttUri(TOPIC_BOTH_MQTT_RECEIVE));
-    model.connectBoth2BothInput(restUri(PATH_BOTH_REST_RECEIVE, REST_PORT));
-    model.connectBoth2MqttOutput(mqttUri(TOPIC_BOTH_2_MQTT_SEND), writeCurrentValue);
-    model.connectBoth2RestOutput(restUri(PATH_BOTH_2_REST_SEND, REST_PORT), writeCurrentValue);
-  }
-
   private static class ReceiverData {
     String lastStringValue;
     int numberOfStringValues = 0;
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
new file mode 100644
index 0000000000000000000000000000000000000000..18a6e59bcc07ffc5c61006c671d15b8c55a8d501
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/list/AbstractListTest.java
@@ -0,0 +1,179 @@
+package org.jastadd.ragconnect.tests.list;
+
+import org.jastadd.ragconnect.tests.AbstractMqttTest;
+import org.jastadd.ragconnect.tests.TestUtils;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.function.Function;
+
+import static java.util.Collections.addAll;
+import static org.assertj.core.util.Lists.newArrayList;
+import static org.jastadd.ragconnect.tests.list.AbstractListTest.IntList.list;
+import static org.jastadd.ragconnect.tests.TestUtils.testJaddContainReferenceToJackson;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * Base class for test cases "list manual" and "list incremental".
+ *
+ * @author rschoene - Initial contribution
+ */
+@Tag("List")
+public abstract class AbstractListTest extends AbstractMqttTest {
+
+  public interface TestWrapperJastAddList<T> extends Iterable<T> {
+    int getNumChild();
+  }
+  public interface TestWrapperReceiverRoot {
+    TestWrapperJastAddList<? extends TestWrapperA> getAList();
+    TestWrapperJastAddList<? extends TestWrapperA> getAs();
+    int getNumA();
+    TestWrapperA getA(int index);
+
+    TestWrapperJastAddList<? extends TestWrapperA> getFromSingleAList();
+    TestWrapperJastAddList<? extends TestWrapperA> getFromSingleAs();
+
+    TestWrapperJastAddList<? extends TestWrapperA> getWithAddFromAList();
+    TestWrapperJastAddList<? extends TestWrapperA> getWithAddFromAs();
+
+    TestWrapperJastAddList<? extends TestWrapperA> getWithAddFromSingleAList();
+    TestWrapperJastAddList<? extends TestWrapperA> getWithAddFromSingleAs();
+
+    boolean connectAList(String mqttUri) throws IOException;
+    boolean connectFromSingleAList(String mqttUri) throws IOException;
+    boolean connectWithAddFromAList(String mqttUri) throws IOException;
+    boolean connectWithAddFromSingleAList(String mqttUri) throws IOException;
+  }
+  public interface TestWrapperA {
+    AbstractListTest.TestWrapperB getB(int i);
+    int getNumB();
+    int getID();
+  }
+  public interface TestWrapperB {
+    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 TestWrapperReceiverRoot receiverRoot;
+  protected ReceiverData data;
+  protected ReceiverData dataSingle;
+
+  private final String shortName;
+
+  @Test
+  public void checkJacksonReference() {
+    testJaddContainReferenceToJackson(
+        Paths.get("src", "test",
+            "02-after-ragconnect", shortName, "RagConnect.jadd"), true);
+  }
+
+  @Override
+  protected void communicateSendInitialValue() throws InterruptedException {
+    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));
+  }
+
+  @Override
+  protected void communicateOnlyUpdatedValue() throws InterruptedException {
+    checkTree(0, list(), list(), list(), list());
+
+    setInput(1);
+    checkTree(1, list(1), list(1), list(1), list(1));
+
+    setInput(1);
+    checkTree(1, list(1), list(1), list(1), list(1));
+
+    setInput(2);
+    checkTree(2, list(1, 2), list(2), list(1, 1, 2), list(1, 2));
+
+    setInput(3);
+    checkTree(3, list(1, 2, 3), list(3), list(1, 1, 2, 1, 2, 3), list(1, 2, 3));
+  }
+
+  protected abstract void setInput(int input);
+
+  private void checkTree(int expectedTransmissions, IntList normalA, IntList fromSingleA, IntList withAddA, IntList withAddFromSingleA) throws InterruptedException {
+    TestUtils.waitForMqtt();
+
+    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.getAList(), true);
+    checkList(normalA.toList(), receiverRoot.getAs(), true);
+
+    checkList(fromSingleA.toList(), receiverRoot.getFromSingleAList(), false);
+    checkList(fromSingleA.toList(), receiverRoot.getFromSingleAs(), false);
+
+    checkList(withAddA.toList(), receiverRoot.getWithAddFromAList(), true);
+    checkList(withAddA.toList(), receiverRoot.getWithAddFromAs(), true);
+
+    checkList(withAddFromSingleA.toList(), receiverRoot.getWithAddFromSingleAList(), false);
+    checkList(withAddFromSingleA.toList(), receiverRoot.getWithAddFromSingleAs(), false);
+  }
+
+  private void checkList(List<Integer> expectedList, int numChildren, Function<Integer, TestWrapperA> getA, boolean expectB) {
+    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");
+      }
+    }
+  }
+
+  private void checkList(List<Integer> expectedList, TestWrapperJastAddList<? extends TestWrapperA> actualList, boolean expectB) {
+    assertEquals(expectedList.size(), actualList.getNumChild(), "same list size");
+    int index = 0;
+    for (TestWrapperA a : actualList) {
+      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");
+      }
+      index++;
+    }
+  }
+
+  protected static class ReceiverData {
+    int numberOfElements = 0;
+  }
+
+  protected static class IntList {
+    private final List<Integer> integers = newArrayList();
+    public IntList(Integer... values) {
+      addAll(integers, values);
+    }
+
+    public List<Integer> toList() {
+      return integers;
+    }
+
+    public static IntList list(Integer... values) {
+      return new IntList(values);
+    }
+  }
+
+}
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
new file mode 100644
index 0000000000000000000000000000000000000000..e5fbae01e28d2c31eed1511c7ff9a0712cd5e3c4
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/list/ListIncrementalTest.java
@@ -0,0 +1,85 @@
+package org.jastadd.ragconnect.tests.list;
+
+import listInc.ast.*;
+import org.jastadd.ragconnect.tests.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.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Test case "list incremental".
+ *
+ * @author rschoene - Initial contribution
+ */
+@Tag("Incremental")
+public class ListIncrementalTest extends AbstractListTest {
+
+  private Root model;
+  private SenderRoot senderRoot;
+  private MqttHandler handler;
+
+  ListIncrementalTest() {
+    super("listInc");
+  }
+
+  @Override
+  protected void createModel() {
+    model = new Root();
+    senderRoot = new SenderRoot();
+    senderRoot.setInput(0);
+    model.addSenderRoot(senderRoot);
+
+    receiverRoot = new ReceiverRoot();
+    model.addReceiverRoot((ReceiverRoot) receiverRoot);
+  }
+
+  @Override
+  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));
+
+    // no dependencies
+
+    data = new ReceiverData();
+    dataSingle = new ReceiverData();
+    handler.newConnection(TOPIC_A, bytes -> data.numberOfElements += 1);
+    handler.newConnection(TOPIC_SINGLE_A, bytes -> dataSingle.numberOfElements += 1);
+
+    // connect. important: first receivers, then senders. to not miss initial value.
+    assertTrue(receiverRoot.connectAList(mqttUri(TOPIC_A)));
+    assertTrue(receiverRoot.connectFromSingleAList(mqttUri(TOPIC_SINGLE_A)));
+    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));
+  }
+
+  @Override
+  protected void closeConnections() {
+    if (handler != null) {
+      handler.close();
+    }
+    if (model != null) {
+      model.ragconnectCloseConnections();
+    }
+  }
+
+  @Override
+  protected void setInput(int input) {
+    senderRoot.setInput(input);
+    assertEquals(input, senderRoot.getNumA(), "size of normal NTA");
+    if (input > 0) {
+      assertEquals(input, senderRoot.getA(input - 1).getID(), "ID value of last A in normal list");
+    }
+    assertEquals(1, senderRoot.getNumSingleA(), "size of single NTA");
+    assertEquals(input, senderRoot.getSingleA(0).getID(), "ID value of single A");
+  }
+}
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
new file mode 100644
index 0000000000000000000000000000000000000000..f3e6eee5c93ea62e6edf939a1e6f5f3ef5419463
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/list/ListManualTest.java
@@ -0,0 +1,84 @@
+package org.jastadd.ragconnect.tests.list;
+
+import list.ast.MqttHandler;
+import list.ast.ReceiverRoot;
+import list.ast.Root;
+import list.ast.SenderRoot;
+import org.jastadd.ragconnect.tests.TestUtils;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+import static org.jastadd.ragconnect.tests.TestUtils.mqttUri;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Test case "list manual".
+ *
+ * @author rschoene - Initial contribution
+ */
+public class ListManualTest extends AbstractListTest {
+
+  private Root model;
+  private SenderRoot senderRoot;
+  private MqttHandler handler;
+
+  ListManualTest() {
+    super("list");
+  }
+
+  @Override
+  protected void createModel() {
+    model = new Root();
+    senderRoot = new SenderRoot();
+    senderRoot.setInput(0);
+    model.addSenderRoot(senderRoot);
+
+    receiverRoot = new ReceiverRoot();
+    model.addReceiverRoot((ReceiverRoot) receiverRoot);
+  }
+
+  @Override
+  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));
+
+    // add dependencies
+    senderRoot.addInputDependencyToA(senderRoot);
+    senderRoot.addInputDependencyToSingleA(senderRoot);
+
+    data = new ReceiverData();
+    dataSingle = new ReceiverData();
+    handler.newConnection(TOPIC_A, bytes -> data.numberOfElements += 1);
+    handler.newConnection(TOPIC_SINGLE_A, bytes -> dataSingle.numberOfElements += 1);
+
+    // connect. important: first receivers, then senders. to not miss initial value.
+    assertTrue(receiverRoot.connectAList(mqttUri(TOPIC_A)));
+    assertTrue(receiverRoot.connectFromSingleAList(mqttUri(TOPIC_SINGLE_A)));
+    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));
+  }
+
+  @Override
+  protected void setInput(int input) {
+    senderRoot.setInput(input);
+    assertEquals(input, senderRoot.getNumA(), "size of normal NTA");
+    assertEquals(1, senderRoot.getNumSingleA(), "size of single NTA");
+  }
+
+  @Override
+  protected void closeConnections() {
+    if (handler != null) {
+      handler.close();
+    }
+    if (model != null) {
+      model.ragconnectCloseConnections();
+    }
+  }
+}
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
new file mode 100644
index 0000000000000000000000000000000000000000..3c2779f8d4188c0f1fff7f8773d27401e94dd597
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleList/AbstractSingleListTest.java
@@ -0,0 +1,297 @@
+package org.jastadd.ragconnect.tests.singleList;
+
+import org.jastadd.ragconnect.tests.AbstractMqttTest;
+import org.jastadd.ragconnect.tests.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.function.Function;
+
+import static java.util.Collections.addAll;
+import static org.assertj.core.util.Lists.newArrayList;
+import static org.jastadd.ragconnect.tests.TestUtils.mqttUri;
+import static org.jastadd.ragconnect.tests.TestUtils.testJaddContainReferenceToJackson;
+import static org.jastadd.ragconnect.tests.singleList.AbstractSingleListTest.IntList.list;
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Base class for test cases "singleList manual" and "singleList incremental".
+ *
+ * @author rschoene - Initial contribution
+ */
+@Tag("List")
+@Tag("SingleList")
+public abstract class AbstractSingleListTest extends AbstractMqttTest {
+
+  public interface TestWrapperJastAddList<T> extends Iterable<T> {
+    int getNumChild();
+  }
+  public interface TestWrapperReceiverRoot {
+    TestWrapperJastAddList<? extends TestWrapperA> getAList();
+    TestWrapperJastAddList<? extends TestWrapperA> getAs();
+    int getNumA();
+    int getNumWithAddA();
+    TestWrapperA getA(int index);
+
+    TestWrapperJastAddList<? extends TestWrapperA> getWithAddAList();
+    TestWrapperJastAddList<? extends TestWrapperA> getUsingWildcardAList();
+    TestWrapperJastAddList<? extends TestWrapperA> getUsingWildcardWithAddAList();
+
+    @SuppressWarnings("unused") boolean connectA(String mqttUri) throws IOException;
+    boolean connectA(String mqttUri, int index) throws IOException;
+    boolean connectUsingWildcardA(String mqttUri) throws IOException;
+    @SuppressWarnings("unused") boolean connectUsingWildcardA(String mqttUri, int index) throws IOException;
+    boolean connectWithAddA(String mqttUri) throws IOException;
+    boolean connectUsingWildcardWithAddA(String mqttUri) throws IOException;
+  }
+  @SuppressWarnings("UnusedReturnValue")
+  public interface TestWrapperSenderRoot {
+    boolean connectA1(String mqttUri, boolean writeCurrentValue) throws IOException;
+    boolean connectA2(String mqttUri, boolean writeCurrentValue) throws IOException;
+    boolean connectA3(String mqttUri, boolean writeCurrentValue) throws IOException;
+    boolean connectA4(String mqttUri, boolean writeCurrentValue) throws IOException;
+    boolean connectInOutput(String mqttUri, boolean writeCurrentValue) throws IOException;
+
+    TestWrapperSenderRoot setInput1(int input);
+    TestWrapperSenderRoot setInput2(int input);
+    TestWrapperSenderRoot setInput3(int input);
+    TestWrapperSenderRoot setInput4(int input);
+    TestWrapperSenderRoot setInOutput(int input);
+
+    TestWrapperA getA1();
+    TestWrapperA getA2();
+    TestWrapperA getA3();
+    TestWrapperA getA4();
+  }
+  public interface TestWrapperA {
+    int getID();
+  }
+
+  AbstractSingleListTest(String shortName) {
+    this.shortName = shortName;
+  }
+
+  protected static final String TOPIC_A_1 = "a/first";
+  protected static final String TOPIC_A_2 = "a/second";
+  protected static final String TOPIC_A_3 = "a/third";
+  protected static final String TOPIC_A_4 = "a/fourth";
+  protected static final String TOPIC_A_5_INOUT = "a/special";
+  protected static final String TOPIC_A_WILDCARD = "a/#";
+
+  protected TestWrapperSenderRoot senderRoot;
+  protected TestWrapperReceiverRoot receiverRoot;
+  protected ReceiverData data;
+
+  private final String shortName;
+
+  @Test
+  public void checkJacksonReference() {
+    testJaddContainReferenceToJackson(
+        Paths.get("src", "test",
+            "02-after-ragconnect", shortName, "RagConnect.jadd"), true);
+  }
+
+  @Override
+  protected void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException, InterruptedException {
+    // late model initialization
+    senderRoot.setInput1(0);
+    senderRoot.setInput2(0);
+    senderRoot.setInput3(0);
+    senderRoot.setInput4(0);
+    senderRoot.setInOutput(0);
+
+    setupReceiverAndConnectPart();
+
+    // connect. important: first receivers, then senders. to not miss initial value.
+
+    // receive: explicit topic subscription
+    assertTrue(receiverRoot.connectA(mqttUri(TOPIC_A_1), 0));
+    assertTrue(receiverRoot.connectA(mqttUri(TOPIC_A_2), 1));
+    assertTrue(receiverRoot.connectA(mqttUri(TOPIC_A_3), 2));
+    assertTrue(receiverRoot.connectA(mqttUri(TOPIC_A_4), 3));
+    assertTrue(receiverRoot.connectA(mqttUri(TOPIC_A_5_INOUT), 4));
+
+    assertTrue(receiverRoot.connectWithAddA(mqttUri(TOPIC_A_1)));
+    assertTrue(receiverRoot.connectWithAddA(mqttUri(TOPIC_A_2)));
+    assertTrue(receiverRoot.connectWithAddA(mqttUri(TOPIC_A_3)));
+    assertTrue(receiverRoot.connectWithAddA(mqttUri(TOPIC_A_4)));
+    assertTrue(receiverRoot.connectWithAddA(mqttUri(TOPIC_A_5_INOUT)));
+
+    // receive: wildcard subscription
+    assertTrue(receiverRoot.connectUsingWildcardA(mqttUri(TOPIC_A_WILDCARD)));
+    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));
+    }
+
+    assertTrue(senderRoot.connectA3(mqttUri(TOPIC_A_3), writeCurrentValue));
+    if (writeCurrentValue) {
+      assertTrue(arrived.get(TOPIC_A_3).await(2, TimeUnit.SECONDS));
+    }
+
+    assertTrue(senderRoot.connectA2(mqttUri(TOPIC_A_2), writeCurrentValue));
+    if (writeCurrentValue) {
+      assertTrue(arrived.get(TOPIC_A_2).await(2, TimeUnit.SECONDS));
+    }
+
+    assertTrue(senderRoot.connectA1(mqttUri(TOPIC_A_1), writeCurrentValue));
+    if (writeCurrentValue) {
+      assertTrue(arrived.get(TOPIC_A_1).await(2, TimeUnit.SECONDS));
+    }
+
+    assertTrue(senderRoot.connectInOutput(mqttUri(TOPIC_A_5_INOUT), writeCurrentValue));
+    // no need to wait here, because first "checkTree" will wait anyway
+    checkArrivalHandler.close();
+  }
+
+  abstract protected void setupReceiverAndConnectPart() throws IOException;
+
+  @Override
+  protected void communicateSendInitialValue() throws InterruptedException {
+    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)
+
+    // 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)
+
+    // 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)
+
+    // 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)
+
+    // 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)
+
+    // 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)
+  }
+
+  @Override
+  protected void communicateOnlyUpdatedValue() throws InterruptedException {
+    checkTree(0, 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
+
+    // A1 should stay at 2
+    setInput(1, 1);
+    checkTree(1, 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
+
+    // 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
+
+    // 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
+  }
+
+  protected void setInput(int index, int input) {
+    int actualComputedValue;
+    switch (index) {
+      case 1: senderRoot.setInput1(input); actualComputedValue = senderRoot.getA1().getID(); break;
+      case 2: senderRoot.setInput2(input); actualComputedValue = senderRoot.getA2().getID(); break;
+      case 3: senderRoot.setInput3(input); actualComputedValue = senderRoot.getA3().getID(); break;
+      case 4: senderRoot.setInput4(input); actualComputedValue = senderRoot.getA4().getID(); break;
+      case 5: senderRoot.setInOutput(input); return;
+      default: fail("Wrong index " + index); return;
+    }
+    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");
+
+    checkList(normalA.toList(), receiverRoot.getNumA(), receiverRoot::getA);
+    checkList(normalA.toList(), receiverRoot.getAList());
+
+    checkList(usingWildcardA.toList(), receiverRoot.getUsingWildcardAList());
+
+    checkList(withAddA.toList(), receiverRoot.getWithAddAList());
+
+    checkList(usingWildcardWithAddA.toList(), receiverRoot.getUsingWildcardWithAddAList());
+  }
+
+  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");
+    }
+  }
+
+  private void checkList(List<Integer> expectedList, TestWrapperJastAddList<? extends TestWrapperA> actualList) {
+    assertEquals(expectedList.size(), actualList.getNumChild(), "same list size");
+    int index = 0;
+    for (TestWrapperA a : actualList) {
+      assertEquals(expectedList.get(index), a.getID(), "correct ID for A");
+      index++;
+    }
+  }
+
+  protected static class ReceiverData {
+    int numberOfElements = 0;
+  }
+
+  protected static class IntList {
+    private final List<Integer> integers = newArrayList();
+    public IntList(Integer... values) {
+      addAll(integers, values);
+    }
+
+    public List<Integer> toList() {
+      return integers;
+    }
+
+    public static IntList list(Integer... values) {
+      return new IntList(values);
+    }
+  }
+
+}
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
new file mode 100644
index 0000000000000000000000000000000000000000..7a946e44499f71dc0dfb92271ca1fc414275597b
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleList/SingleListIncrementalTest.java
@@ -0,0 +1,67 @@
+package org.jastadd.ragconnect.tests.singleList;
+
+import org.jastadd.ragconnect.tests.TestUtils;
+import org.junit.jupiter.api.Tag;
+import singleListInc.ast.*;
+
+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 "list incremental".
+ *
+ * @author rschoene - Initial contribution
+ */
+@Tag("Incremental")
+public class SingleListIncrementalTest extends AbstractSingleListTest {
+
+  private Root model;
+  private MqttHandler handler;
+
+  SingleListIncrementalTest() {
+    super("singleListInc");
+  }
+
+  @Override
+  protected void createModel() {
+    model = new Root();
+    senderRoot = new SenderRoot();
+    model.addSenderRoot((SenderRoot) senderRoot);
+
+    ReceiverRoot localReceiverRoot = new ReceiverRoot();
+    model.addReceiverRoot(localReceiverRoot);
+
+    // first prepare non-wildcard lists
+    for (int i = 0; i < 5; i++) {
+      localReceiverRoot.addA(new A());
+    }
+    receiverRoot = localReceiverRoot;
+    assertEquals(5, receiverRoot.getNumA());
+  }
+
+  @Override
+  protected void setupReceiverAndConnectPart() throws IOException {
+    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
+  protected void closeConnections() {
+    if (handler != null) {
+      handler.close();
+    }
+    if (model != null) {
+      model.ragconnectCloseConnections();
+    }
+  }
+}
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
new file mode 100644
index 0000000000000000000000000000000000000000..c2896f633bc299159f0ab74913c3a9c0456d8370
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleList/SingleListManualTest.java
@@ -0,0 +1,69 @@
+package org.jastadd.ragconnect.tests.singleList;
+
+import org.jastadd.ragconnect.tests.TestUtils;
+import singleList.ast.*;
+
+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 "list manual".
+ *
+ * @author rschoene - Initial contribution
+ */
+public class SingleListManualTest extends AbstractSingleListTest {
+
+  private Root model;
+  private MqttHandler handler;
+
+  SingleListManualTest() {
+    super("singleList");
+  }
+
+  @Override
+  protected void createModel() {
+    model = new Root();
+    senderRoot = new SenderRoot();
+    model.addSenderRoot((SenderRoot) senderRoot);
+
+    ReceiverRoot localReceiverRoot = new ReceiverRoot();
+    model.addReceiverRoot(localReceiverRoot);
+
+    // first prepare non-wildcard lists
+    for (int i = 0; i < 5; i++) {
+      localReceiverRoot.addA(new A());
+    }
+    receiverRoot = localReceiverRoot;
+    assertEquals(5, receiverRoot.getNumA());
+  }
+
+  @Override
+  protected void setupReceiverAndConnectPart() throws IOException {
+    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
+  protected void closeConnections() {
+    if (handler != null) {
+      handler.close();
+    }
+    if (model != null) {
+      model.ragconnectCloseConnections();
+    }
+  }
+}
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
new file mode 100644
index 0000000000000000000000000000000000000000..465ad8ac024d595f29a87757c776db6c88e72fe4
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleListVariant/AbstractSingleListVariantTest.java
@@ -0,0 +1,406 @@
+package org.jastadd.ragconnect.tests.singleListVariant;
+
+import org.jastadd.ragconnect.tests.AbstractMqttTest;
+import org.jastadd.ragconnect.tests.TestUtils;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.function.BiConsumer;
+
+import static java.lang.Math.abs;
+import static java.util.Collections.addAll;
+import static org.assertj.core.util.Lists.newArrayList;
+import static org.jastadd.ragconnect.tests.TestUtils.mqttUri;
+import static org.jastadd.ragconnect.tests.TestUtils.testJaddContainReferenceToJackson;
+import static org.jastadd.ragconnect.tests.singleListVariant.AbstractSingleListVariantTest.IntList.list;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Base class for test cases "singleList manual" and "singleList incremental".
+ *
+ * @author rschoene - Initial contribution
+ */
+@Tag("List")
+@Tag("SingleList")
+public abstract class AbstractSingleListVariantTest extends AbstractMqttTest {
+
+  public interface TestWrapperJastAddList<T> extends Iterable<T> {
+    int getNumChild();
+  }
+  public interface TestWrapperReceiverRoot {
+    TestWrapperJastAddList<? extends TestWrapperT_Empty> getT_EmptyList();
+    TestWrapperJastAddList<? extends TestWrapperT_Token> getT_TokenList();
+    TestWrapperJastAddList<? extends TestWrapperT_OneChild> getT_OneChildList();
+    TestWrapperJastAddList<? extends TestWrapperT_OneOpt> getT_OneOptList();
+    TestWrapperJastAddList<? extends TestWrapperT_OneList> getT_OneListList();
+    TestWrapperJastAddList<? extends TestWrapperT_TwoChildren> getT_TwoChildrenList();
+    TestWrapperJastAddList<? extends TestWrapperT_OneOfEach> getT_OneOfEachList();
+    TestWrapperJastAddList<? extends TestWrapperT_Abstract> getT_AbstractList();
+
+    TestWrapperJastAddList<? extends TestWrapperT_Empty> getMyEmptyList();
+
+    TestWrapperJastAddList<? extends TestWrapperT_Empty> getEmptyWithAddList();
+    TestWrapperJastAddList<? extends TestWrapperT_Token> getTokenWithAddList();
+    TestWrapperJastAddList<? extends TestWrapperT_OneChild> getOneChildWithAddList();
+    TestWrapperJastAddList<? extends TestWrapperT_OneOpt> getOneOptWithAddList();
+    TestWrapperJastAddList<? extends TestWrapperT_OneList> getOneListWithAddList();
+    TestWrapperJastAddList<? extends TestWrapperT_TwoChildren> getTwoChildrenWithAddList();
+    TestWrapperJastAddList<? extends TestWrapperT_OneOfEach> getOneOfEachWithAddList();
+    TestWrapperJastAddList<? extends TestWrapperT_Abstract> getAbstractWithAddList();
+
+    boolean connectT_Empty(String mqttUri) throws IOException;
+    boolean connectT_Token(String mqttUri) throws IOException;
+    boolean connectT_OneChild(String mqttUri) throws IOException;
+    boolean connectT_OneOpt(String mqttUri) throws IOException;
+    boolean connectT_OneList(String mqttUri) throws IOException;
+    boolean connectT_TwoChildren(String mqttUri) throws IOException;
+    boolean connectT_OneOfEach(String mqttUri) throws IOException;
+    boolean connectT_Abstract(String mqttUri) throws IOException;
+
+    boolean connectMyEmpty(String mqttUri) throws IOException;
+
+    boolean connectEmptyWithAdd(String mqttUri) throws IOException;
+    boolean connectTokenWithAdd(String mqttUri) throws IOException;
+    boolean connectOneChildWithAdd(String mqttUri) throws IOException;
+    boolean connectOneOptWithAdd(String mqttUri) throws IOException;
+    boolean connectOneListWithAdd(String mqttUri) throws IOException;
+    boolean connectTwoChildrenWithAdd(String mqttUri) throws IOException;
+    boolean connectOneOfEachWithAdd(String mqttUri) throws IOException;
+    boolean connectAbstractWithAdd(String mqttUri) throws IOException;
+  }
+  @SuppressWarnings("UnusedReturnValue")
+  public interface TestWrapperSenderRoot {
+    boolean connectT_Empty(String mqttUri, boolean writeCurrentValue) throws IOException;
+    boolean connectT_Token(String mqttUri, boolean writeCurrentValue) throws IOException;
+    boolean connectT_OneChild(String mqttUri, boolean writeCurrentValue) throws IOException;
+    boolean connectT_OneOpt(String mqttUri, boolean writeCurrentValue) throws IOException;
+    boolean connectT_OneList(String mqttUri, boolean writeCurrentValue) throws IOException;
+    boolean connectT_TwoChildren(String mqttUri, boolean writeCurrentValue) throws IOException;
+    boolean connectT_OneOfEach(String mqttUri, boolean writeCurrentValue) throws IOException;
+    boolean connectT_Abstract(String mqttUri, boolean writeCurrentValue) throws IOException;
+
+    TestWrapperSenderRoot setInput(int input);
+    TestWrapperSenderRoot setShouldSetOptAndList(boolean shouldSetOptAndList);
+    TestWrapperT_Empty getT_Empty();
+    TestWrapperT_OneOpt getT_OneOpt();
+  }
+  public interface TestWrapperNameable {
+    int getID();
+  }
+  public interface TestWrapperOther extends TestWrapperNameable {}
+  public interface TestWrapperT_Empty extends TestWrapperNameable {}
+  public interface TestWrapperT_Token extends TestWrapperNameable {
+    String getValue();
+  }
+  public interface TestWrapperT_OneChild extends TestWrapperNameable {
+    TestWrapperNameable getOther();
+  }
+  public interface TestWrapperT_OneOpt extends TestWrapperNameable {
+    boolean hasOther();
+    TestWrapperNameable getOther();
+  }
+  public interface TestWrapperT_OneList extends TestWrapperNameable {
+    int getNumOther();
+    TestWrapperNameable getOther(int index);
+  }
+  public interface TestWrapperT_TwoChildren extends TestWrapperNameable {
+    TestWrapperNameable getLeft();
+    TestWrapperNameable getRight();
+  }
+  public interface TestWrapperT_OneOfEach extends TestWrapperNameable {
+    TestWrapperNameable getFirst();
+    boolean hasSecond();
+    TestWrapperNameable getSecond();
+    int getNumThird();
+    TestWrapperNameable getThird(int index);
+    String getFourth();
+  }
+  public interface TestWrapperT_Abstract extends TestWrapperNameable {
+    String getValueAbstract();
+    String getValueSub();
+  }
+
+  AbstractSingleListVariantTest(String shortName) {
+    this.shortName = shortName;
+  }
+
+  protected static final String TOPIC_T_Empty = "t/Empty";
+  protected static final String TOPIC_T_Token = "t/Token";
+  protected static final String TOPIC_T_OneChild = "t/OneChild";
+  protected static final String TOPIC_T_OneOpt = "t/OneOpt";
+  protected static final String TOPIC_T_OneList = "t/OneList";
+  protected static final String TOPIC_T_TwoChildren = "t/TwoChildren";
+  protected static final String TOPIC_T_OneOfEach = "t/OneOfEach";
+  protected static final String TOPIC_T_Abstract = "t/Abstract";
+  protected static final String TOPIC_T_all = "t/#";
+
+  protected TestWrapperSenderRoot senderRoot;
+  protected TestWrapperReceiverRoot receiverRoot;
+  protected ReceiverData data;
+
+  private final String shortName;
+
+  @Test
+  public void checkJacksonReference() {
+    testJaddContainReferenceToJackson(
+        Paths.get("src", "test",
+            "02-after-ragconnect", shortName, "RagConnect.jadd"), true);
+  }
+
+  @Override
+  protected void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException, InterruptedException {
+    // late model initialization
+    setInput(0);
+    setShouldSetOptAndList(false);
+
+    setupReceiverAndConnectPart();
+
+    // connect. important: first receivers, then senders. to not miss initial value.
+
+    // receive: unnamed
+    assertTrue(receiverRoot.connectT_Empty(mqttUri(TOPIC_T_Empty)));
+    assertTrue(receiverRoot.connectT_Token(mqttUri(TOPIC_T_Token)));
+    assertTrue(receiverRoot.connectT_OneChild(mqttUri(TOPIC_T_OneChild)));
+    assertTrue(receiverRoot.connectT_OneOpt(mqttUri(TOPIC_T_OneOpt)));
+    assertTrue(receiverRoot.connectT_OneList(mqttUri(TOPIC_T_OneList)));
+    assertTrue(receiverRoot.connectT_TwoChildren(mqttUri(TOPIC_T_TwoChildren)));
+    assertTrue(receiverRoot.connectT_OneOfEach(mqttUri(TOPIC_T_OneOfEach)));
+    assertTrue(receiverRoot.connectT_Abstract(mqttUri(TOPIC_T_Abstract)));
+
+    // receive: named
+    assertTrue(receiverRoot.connectMyEmpty(mqttUri(TOPIC_T_Empty)));
+
+    // receive: with add
+    assertTrue(receiverRoot.connectEmptyWithAdd(mqttUri(TOPIC_T_Empty)));
+    assertTrue(receiverRoot.connectTokenWithAdd(mqttUri(TOPIC_T_Token)));
+    assertTrue(receiverRoot.connectOneChildWithAdd(mqttUri(TOPIC_T_OneChild)));
+    assertTrue(receiverRoot.connectOneOptWithAdd(mqttUri(TOPIC_T_OneOpt)));
+    assertTrue(receiverRoot.connectOneListWithAdd(mqttUri(TOPIC_T_OneList)));
+    assertTrue(receiverRoot.connectTwoChildrenWithAdd(mqttUri(TOPIC_T_TwoChildren)));
+    assertTrue(receiverRoot.connectOneOfEachWithAdd(mqttUri(TOPIC_T_OneOfEach)));
+    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));
+  }
+
+  abstract protected void setupReceiverAndConnectPart() throws IOException;
+
+  @Override
+  protected void communicateSendInitialValue() throws InterruptedException {
+    // transmissions: 8 * 1 = 8
+    checkTree(8, list(-0), list(0), list(-0));
+
+    setInput(1);
+    // transmissions: 8 + 8 = 16
+    checkTree(16, list(-1), list(0, 1), list(-0, -1));
+
+    setInput(1);
+    // transmissions: 16
+    checkTree(16, 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));
+
+    setShouldSetOptAndList(true);
+    // transmissions: 19
+    checkTree(19, 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));
+
+    setInput(5);
+    // transmissions: 27 + 8 = 35
+    checkTree(35, list(5), list(0, 1, 2, 5), list(-0, -1, 1, 2, 5));
+  }
+
+  @Override
+  protected void communicateOnlyUpdatedValue() throws InterruptedException {
+    // transmissions: 0
+    checkTree(0, list(), list(), list());
+
+    setInput(1);
+    // transmissions: 8 * 1 = 0
+    checkTree(8, list(-1), list(1), list(-1));
+
+    setInput(1);
+    // transmissions: 8
+    checkTree(8, list(-1), list(1), list(-1));
+
+    setShouldSetOptAndList(true);
+    // transmissions: 8 + 3 = 11
+    checkTree(11, list(1), list(1), list(-1, 1));
+
+    setShouldSetOptAndList(true);
+    // transmissions: 11
+    checkTree(11, list(1), list(1), list(-1, 1));
+
+    setInput(2);
+    // transmissions: 11 + 8 = 19
+    checkTree(19, 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));
+  }
+
+  protected void setInput(int input) {
+    senderRoot.setInput(input);
+    assertEquals(input, senderRoot.getT_Empty().getID(), "ID value of empty");
+  }
+
+  protected void setShouldSetOptAndList(boolean shouldSetOptAndList) {
+    senderRoot.setShouldSetOptAndList(shouldSetOptAndList);
+    assertEquals(shouldSetOptAndList, senderRoot.getT_OneOpt().hasOther(), "opt is filled or not");
+  }
+
+  /**
+   * 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
+   *                                         (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
+   */
+  private void checkTree(int expectedTransmissions,
+                         IntList expectedList,
+                         IntList expectedWithAddList,
+                         IntList expectedWithAddListForOptAndList)
+      throws InterruptedException {
+    TestUtils.waitForMqtt();
+    assertEquals(expectedTransmissions, data.numberOfElements, "transmissions for any element");
+
+    // check unnamed
+    checkList(expectedList.toList(), receiverRoot.getT_EmptyList(), (e, n) -> {});
+    checkList(expectedList.toList(), receiverRoot.getT_TokenList(),
+        (e, n) -> assertEquals(Integer.toString(abs(e)), n.getValue()));
+    checkList(expectedList.toList(), receiverRoot.getT_OneChildList(),
+        (e, n) -> assertEquals(abs(e) + 1, n.getOther().getID()));
+    checkList(expectedList.toList(), receiverRoot.getT_OneOptList(),
+        (e, n) -> {
+          assertEquals(e > 0, n.hasOther());
+          if (n.hasOther()) {
+            assertEquals(abs(e) + 1, n.getOther().getID());
+          }
+        });
+    checkList(expectedList.toList(), receiverRoot.getT_OneListList(),
+        (e, n) -> {
+          assertEquals(e > 0 ? 1 : 0, n.getNumOther());
+          if (n.getNumOther() > 0) {
+            assertEquals(abs(e) + 1, n.getOther(0).getID());
+          }
+        });
+    checkList(expectedList.toList(), receiverRoot.getT_TwoChildrenList(),
+        (e, n) -> {
+          assertEquals(abs(e) + 1, n.getLeft().getID());
+          assertEquals(abs(e) + 1, n.getRight().getID());
+        });
+    checkList(expectedList.toList(), receiverRoot.getT_OneOfEachList(),
+        (e, n) -> {
+          assertEquals(abs(e) + 1, n.getFirst().getID());
+          assertEquals(e > 0, n.hasSecond());
+          if (n.hasSecond()) {
+            assertEquals(abs(e) + 1, n.getSecond().getID());
+          }
+          assertEquals(e > 0 ? 1 : 0, n.getNumThird());
+          if (n.getNumThird() > 0) {
+            assertEquals(abs(e) + 1, n.getThird(0).getID());
+          }
+          assertEquals(Integer.toString(abs(e)), n.getFourth());
+        });
+    checkList(expectedList.toList(), receiverRoot.getT_AbstractList(),
+        (e, n) -> {
+          assertEquals(Integer.toString(abs(e)), n.getValueAbstract());
+          assertEquals(Integer.toString(abs(e)), n.getValueSub());
+        });
+
+    // check named
+    checkList(expectedList.toList(), receiverRoot.getMyEmptyList(), (e, n) -> {});
+
+    // check with add
+    checkList(expectedWithAddList.toList(), receiverRoot.getEmptyWithAddList(), (e, n) -> {});
+    checkList(expectedWithAddList.toList(), receiverRoot.getTokenWithAddList(),
+        (e, n) -> assertEquals(Integer.toString(abs(e)), n.getValue()));
+    checkList(expectedWithAddList.toList(), receiverRoot.getOneChildWithAddList(),
+        (e, n) -> assertEquals(abs(e) + 1, n.getOther().getID()));
+    checkList(expectedWithAddListForOptAndList.toList(), receiverRoot.getOneOptWithAddList(),
+        (e, n) -> {
+          if (n.hasOther()) {
+            assertEquals(abs(e) + 1, n.getOther().getID());
+          }
+        });
+    checkList(expectedWithAddListForOptAndList.toList(), receiverRoot.getOneListWithAddList(),
+        (e, n) -> {
+          if (n.getNumOther() > 0) {
+            assertEquals(abs(e) + 1, n.getOther(0).getID());
+          }
+        });
+    checkList(expectedWithAddList.toList(), receiverRoot.getTwoChildrenWithAddList(),
+        (e, n) -> {
+          assertEquals(abs(e) + 1, n.getLeft().getID());
+          assertEquals(abs(e) + 1, n.getRight().getID());
+        });
+    checkList(expectedWithAddListForOptAndList.toList(), receiverRoot.getOneOfEachWithAddList(),
+        (e, n) -> {
+          assertEquals(abs(e) + 1, n.getFirst().getID());
+          if (n.hasSecond()) {
+            assertEquals(abs(e) + 1, n.getSecond().getID());
+          }
+          if (n.getNumThird() > 0) {
+            assertEquals(abs(e) + 1, n.getThird(0).getID());
+          }
+          assertEquals(Integer.toString(abs(e)), n.getFourth());
+        });
+    checkList(expectedWithAddList.toList(), receiverRoot.getAbstractWithAddList(),
+        (e, n) -> {
+          assertEquals(Integer.toString(abs(e)), n.getValueAbstract());
+          assertEquals(Integer.toString(abs(e)), n.getValueSub());
+        });
+  }
+
+  private <T extends TestWrapperNameable> void checkList(List<Integer> expectedList, TestWrapperJastAddList<T> actualList, BiConsumer<Integer, T> additionalTest) {
+    assertEquals(expectedList.size(), actualList.getNumChild(), "same list size");
+    int index = 0;
+    for (T element : actualList) {
+      assertEquals(abs(expectedList.get(index)), element.getID(), "correct ID for A");
+      additionalTest.accept(expectedList.get(index), element);
+      index++;
+    }
+  }
+
+  protected static class ReceiverData {
+    int numberOfElements = 0;
+  }
+
+  protected static class IntList {
+    private final List<Integer> integers = newArrayList();
+    public IntList(Integer... values) {
+      addAll(integers, values);
+    }
+
+    public List<Integer> toList() {
+      return integers;
+    }
+
+    public static IntList list(Integer... values) {
+      return new IntList(values);
+    }
+  }
+
+}
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/SingleListVariantIncrementalVariantTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..9a55da378b93cc3bcc741d1904dc58b3d9ae96bd
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleListVariant/SingleListVariantIncrementalVariantTest.java
@@ -0,0 +1,62 @@
+package org.jastadd.ragconnect.tests.singleListVariant;
+
+import org.jastadd.ragconnect.tests.TestUtils;
+import org.junit.jupiter.api.Tag;
+import singleListVariantInc.ast.*;
+
+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 "list incremental".
+ *
+ * @author rschoene - Initial contribution
+ */
+@Tag("Incremental")
+public class SingleListVariantIncrementalVariantTest extends AbstractSingleListVariantTest {
+
+  private Root model;
+  private MqttHandler handler;
+
+  SingleListVariantIncrementalVariantTest() {
+    super("singleListVariantInc");
+  }
+
+  @Override
+  protected void createModel() {
+    model = new Root();
+    senderRoot = new SenderRoot();
+    model.addSenderRoot((SenderRoot) senderRoot);
+
+    ReceiverRoot localReceiverRoot = new ReceiverRoot();
+    model.addReceiverRoot(localReceiverRoot);
+    receiverRoot = localReceiverRoot;
+    assertEquals(0, receiverRoot.getT_EmptyList().getNumChild());
+  }
+
+  @Override
+  protected void setupReceiverAndConnectPart() throws IOException {
+    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_T_all, bytes -> data.numberOfElements += 1);
+  }
+
+  @Override
+  protected void closeConnections() {
+    if (handler != null) {
+      handler.close();
+    }
+    if (model != null) {
+      model.ragconnectCloseConnections();
+    }
+  }
+}
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/SingleListVariantManualVariantTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..c65a09ec65add4afd7fa5ca8a2809b28e0be4df2
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleListVariant/SingleListVariantManualVariantTest.java
@@ -0,0 +1,72 @@
+package org.jastadd.ragconnect.tests.singleListVariant;
+
+import org.jastadd.ragconnect.tests.TestUtils;
+import singleListVariant.ast.*;
+
+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 "list manual".
+ *
+ * @author rschoene - Initial contribution
+ */
+public class SingleListVariantManualVariantTest extends AbstractSingleListVariantTest {
+
+  private Root model;
+  private MqttHandler handler;
+
+  SingleListVariantManualVariantTest() {
+    super("singleListVariant");
+  }
+
+  @Override
+  protected void createModel() {
+    model = new Root();
+    senderRoot = new SenderRoot();
+    model.addSenderRoot((SenderRoot) senderRoot);
+
+    ReceiverRoot localReceiverRoot = new ReceiverRoot();
+    model.addReceiverRoot(localReceiverRoot);
+    receiverRoot = localReceiverRoot;
+    assertEquals(0, receiverRoot.getT_EmptyList().getNumChild());
+  }
+
+  @Override
+  protected void setupReceiverAndConnectPart() throws IOException {
+    model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS);
+
+    handler = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost());
+    assertTrue(handler.waitUntilReady(2, TimeUnit.SECONDS));
+
+    // add dependencies: input
+    ((SenderRoot) senderRoot).addInputDependencyToT_Empty((SenderRoot) senderRoot);
+    ((SenderRoot) senderRoot).addInputDependencyToT_Token((SenderRoot) senderRoot);
+    ((SenderRoot) senderRoot).addInputDependencyToT_OneChild((SenderRoot) senderRoot);
+    ((SenderRoot) senderRoot).addInputDependencyToT_OneOpt((SenderRoot) senderRoot);
+    ((SenderRoot) senderRoot).addInputDependencyToT_OneList((SenderRoot) senderRoot);
+    ((SenderRoot) senderRoot).addInputDependencyToT_TwoChildren((SenderRoot) senderRoot);
+    ((SenderRoot) senderRoot).addInputDependencyToT_OneOfEach((SenderRoot) senderRoot);
+    ((SenderRoot) senderRoot).addInputDependencyToT_Abstract((SenderRoot) senderRoot);
+    // add dependencies: shouldSetOptAndList
+    ((SenderRoot) senderRoot).addShouldSetOptAndListDependencyToT_OneOpt((SenderRoot) senderRoot);
+    ((SenderRoot) senderRoot).addShouldSetOptAndListDependencyToT_OneList((SenderRoot) senderRoot);
+    ((SenderRoot) senderRoot).addShouldSetOptAndListDependencyToT_OneOfEach((SenderRoot) senderRoot);
+
+    data = new ReceiverData();
+    handler.newConnection(TOPIC_T_all, bytes -> data.numberOfElements += 1);
+  }
+
+  @Override
+  protected void closeConnections() {
+    if (handler != null) {
+      handler.close();
+    }
+    if (model != null) {
+      model.ragconnectCloseConnections();
+    }
+  }
+}
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
new file mode 100644
index 0000000000000000000000000000000000000000..ebcccfe9898e374a5dc8d5886d83f999fc0def36
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/tree/AbstractTreeTest.java
@@ -0,0 +1,211 @@
+package org.jastadd.ragconnect.tests.tree;
+
+import org.jastadd.ragconnect.tests.AbstractMqttTest;
+import org.jastadd.ragconnect.tests.TestUtils;
+import org.junit.jupiter.api.Tag;
+
+import java.io.IOException;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Base class for test cases "tree manual" and "tree incremental".
+ *
+ * @author rschoene - Initial contribution
+ */
+@Tag("Tree")
+public abstract class AbstractTreeTest extends AbstractMqttTest {
+  protected static final String TOPIC_ALFA = "alfa";
+  protected ReceiverData data;
+  protected TestWrapperReceiverRoot receiverRoot;
+
+  public interface TestWrapperReceiverRoot {
+    TestWrapperAlfa getAlfa();
+    boolean connectAlfa(String mqttUri) throws IOException;
+  }
+  public interface TestWrapperAlfa {
+    TestWrapperBravo getBravo(int i);
+    int getNumBravo();
+    int getNumCharlie();
+    int getNumDelta();
+
+    TestWrapperAlfa getMyself();
+    TestWrapperBravo getMyBravo();
+    boolean hasOptionalBravo();
+    TestWrapperBravo getOptionalBravo();
+    TestWrapperCharlie getCharlie(int i);
+    TestWrapperDelta getDelta(int i);
+    <T extends TestWrapperBravo> List<T> getMultiBravoList();
+  }
+  public interface TestWrapperBravo {
+
+    TestWrapperCharlie getMyCharlie();
+    boolean hasOptionalCharlie();
+    TestWrapperCharlie getOptionalCharlie();
+    <T extends TestWrapperCharlie> List<T> getMultiCharlieList();
+    boolean hasSingleBi1Delta();
+    TestWrapperDelta getSingleBi1Delta();
+    <T extends TestWrapperDelta> List<T> getMultiBi2DeltaList();
+    <T extends TestWrapperDelta> List<T> getMultiBi3DeltaList();
+  }
+  public interface TestWrapperCharlie {
+    boolean hasMyAlfa();
+    TestWrapperAlfa getMyAlfa();
+  }
+  public interface TestWrapperDelta {
+    boolean hasSingleBack1Alfa();
+    TestWrapperAlfa getSingleBack1Alfa();
+    boolean hasSingleBack2Alfa();
+    TestWrapperAlfa getSingleBack2Alfa();
+    <T extends TestWrapperAlfa> List<T> getMultiBack3AlfaList();
+    boolean hasSingleBack1Bravo();
+    TestWrapperBravo getSingleBack1Bravo();
+    TestWrapperBravo getSingleBack2Bravo();
+    boolean hasSingleBack2Bravo();
+    <T extends TestWrapperBravo> List<T> getMultiBack3BravoList();
+  }
+
+  @Override
+  protected void communicateSendInitialValue() throws InterruptedException {
+    checkTree(1, 0);
+
+    setInput(1);
+    checkTree(2, 1);
+
+    setInput(1);
+    checkTree(2, 1);
+
+    setInput(2);
+    checkTree(3, 2);
+  }
+
+  @Override
+  protected void communicateOnlyUpdatedValue() throws InterruptedException {
+    checkTree(0, null);
+
+    setInput(1);
+    checkTree(1, 1);
+
+    setInput(1);
+    checkTree(1, 1);
+
+    setInput(2);
+    checkTree(2, 2);
+  }
+
+  protected abstract void setInput(int input);
+
+  protected void checkTree(int expectedCount, Integer expectedInput) throws InterruptedException {
+    TestUtils.waitForMqtt();
+
+    assertEquals(expectedCount, data.numberOfTrees);
+    if (expectedInput == null) {
+      assertNull(receiverRoot.getAlfa());
+    } else {
+      assertNotNull(receiverRoot.getAlfa());
+      TestWrapperAlfa alfa = receiverRoot.getAlfa();
+      assertEquals(4, alfa.getNumBravo());
+      assertEquals(4, alfa.getNumCharlie());
+      assertEquals(4, alfa.getNumDelta());
+
+      TestWrapperBravo inputBravo = alfa.getBravo(expectedInput);
+      TestWrapperCharlie inputCharlie = alfa.getCharlie(expectedInput);
+      TestWrapperDelta inputDelta = alfa.getDelta(expectedInput);
+
+      // Alfa -> Alfa
+      assertEquals(alfa, alfa.getMyself());
+
+      // Alfa -> Bravo
+      assertEquals(inputBravo, alfa.getMyBravo());
+      assertTrue(alfa.hasOptionalBravo());
+      assertEquals(inputBravo, alfa.getOptionalBravo());
+      assertThat(alfa.getMultiBravoList()).containsExactly(
+          inputBravo, alfa.getBravo(expectedInput + 1));
+
+      // Charlie -> Alfa
+      for (int i = 0; i < 4; i++) {
+        TestWrapperCharlie Charlie = alfa.getCharlie(i);
+        if (i == expectedInput) {
+          assertTrue(Charlie.hasMyAlfa());
+          assertEquals(alfa, Charlie.getMyAlfa());
+        } else {
+          assertFalse(Charlie.hasMyAlfa());
+        }
+      }
+
+      // Alfa <-> Delta
+      for (int i = 0; i < 4; i++) {
+        TestWrapperDelta Delta = alfa.getDelta(i);
+        if (i == expectedInput) {
+          assertTrue(Delta.hasSingleBack1Alfa());
+          assertEquals(alfa, Delta.getSingleBack1Alfa());
+        } else {
+          assertFalse(Delta.hasSingleBack1Alfa());
+        }
+        if (i == expectedInput || i == expectedInput + 1) {
+          assertTrue(Delta.hasSingleBack2Alfa());
+          assertEquals(alfa, Delta.getSingleBack2Alfa());
+          assertThat(Delta.getMultiBack3AlfaList()).containsExactly(alfa);
+        } else {
+          assertFalse(Delta.hasSingleBack2Alfa());
+          assertThat(Delta.getMultiBack3AlfaList()).isEmpty();
+        }
+      }
+
+      // Bravo -> Charlie
+      for (int i = 0; i < 4; i++) {
+        TestWrapperBravo Bravo = alfa.getBravo(i);
+
+        if (i == expectedInput) {
+          assertEquals(inputCharlie, Bravo.getMyCharlie());
+          assertTrue(Bravo.hasOptionalCharlie());
+          assertEquals(inputCharlie, Bravo.getOptionalCharlie());
+          assertThat(Bravo.getMultiCharlieList()).containsExactly(
+              inputCharlie, alfa.getCharlie(expectedInput + 1));
+        } else {
+          assertEquals(alfa.getCharlie(0), Bravo.getMyCharlie());
+          assertFalse(Bravo.hasOptionalCharlie());
+          assertThat(Bravo.getMultiCharlieList()).isEmpty();
+        }
+      }
+
+      // Bravo <-> Delta
+      for (int i = 0; i < 4; i++) {
+        TestWrapperBravo Bravo = alfa.getBravo(i);
+        TestWrapperDelta Delta = alfa.getDelta(i);
+
+        if (i == expectedInput) {
+          assertTrue(Bravo.hasSingleBi1Delta());
+          assertTrue(Delta.hasSingleBack1Bravo());
+          assertEquals(inputDelta, Bravo.getSingleBi1Delta());
+          assertEquals(inputBravo, Delta.getSingleBack1Bravo());
+          assertThat(Bravo.getMultiBi2DeltaList()).containsExactly(
+              inputDelta, alfa.getDelta(expectedInput + 1));
+        } else {
+          assertFalse(Bravo.hasSingleBi1Delta());
+          assertFalse(Delta.hasSingleBack1Bravo());
+          assertThat(Bravo.getMultiBi2DeltaList()).isEmpty();
+        }
+        if (i == expectedInput || i == expectedInput + 1) {
+          assertThat(Bravo.getMultiBi3DeltaList()).containsExactly(
+              inputDelta, alfa.getDelta(expectedInput + 1));
+          assertTrue(Delta.hasSingleBack2Bravo());
+          assertEquals(inputBravo, Delta.getSingleBack2Bravo());
+          assertThat(Delta.getMultiBack3BravoList()).containsExactly(
+              inputBravo, alfa.getBravo(expectedInput + 1));
+        } else {
+          assertThat(Bravo.getMultiBi3DeltaList()).isEmpty();
+          assertFalse(Delta.hasSingleBack2Bravo());
+          assertThat(Delta.getMultiBack3BravoList()).isEmpty();
+        }
+      }
+    }
+  }
+
+  protected static class ReceiverData {
+    int numberOfTrees = 0;
+  }
+
+}
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
new file mode 100644
index 0000000000000000000000000000000000000000..6722e159ed064ee253dd79c01fbf3e009ebba874
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/tree/TreeIncrementalTest.java
@@ -0,0 +1,76 @@
+package org.jastadd.ragconnect.tests.tree;
+
+import org.jastadd.ragconnect.tests.TestUtils;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import treeInc.ast.*;
+
+import java.io.IOException;
+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.junit.jupiter.api.Assertions.*;
+
+/**
+ * Test case "tree incremental"
+ *
+ * @author rschoene - Initial contribution
+ */
+@Tag("Incremental")
+public class TreeIncrementalTest extends AbstractTreeTest {
+
+  private Root model;
+  private SenderRoot senderRoot;
+  private MqttHandler handler;
+
+  @Test
+  public void checkJacksonReference() {
+    testJaddContainReferenceToJackson(
+        Paths.get("src", "test",
+            "02-after-ragconnect", "treeInc", "RagConnect.jadd"), true);
+  }
+
+  @Override
+  protected void createModel() {
+    model = new Root();
+    senderRoot = new SenderRoot();
+    model.addSenderRoot(senderRoot);
+
+    receiverRoot = new ReceiverRoot();
+    model.addReceiverRoot((ReceiverRoot) receiverRoot);
+  }
+
+  @Override
+  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));
+
+    // no dependencies
+
+    data = new ReceiverData();
+    handler.newConnection(TOPIC_ALFA, bytes -> data.numberOfTrees += 1);
+
+    // connect. important: first receiver, then sender. to not miss initial value.
+    assertTrue(receiverRoot.connectAlfa(mqttUri(TOPIC_ALFA)));
+    assertTrue(senderRoot.connectAlfa(mqttUri(TOPIC_ALFA), writeCurrentValue));
+  }
+
+  @Override
+  protected void closeConnections() {
+    if (handler != null) {
+      handler.close();
+    }
+    if (model != null) {
+      model.ragconnectCloseConnections();
+    }
+  }
+
+  protected void setInput(int input) {
+    senderRoot.setInput(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
new file mode 100644
index 0000000000000000000000000000000000000000..d60e8fa0590bc60306997a8896856da99b0a7956
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/tree/TreeManualTest.java
@@ -0,0 +1,78 @@
+package org.jastadd.ragconnect.tests.tree;
+
+import org.jastadd.ragconnect.tests.TestUtils;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import tree.ast.MqttHandler;
+import tree.ast.ReceiverRoot;
+import tree.ast.Root;
+import tree.ast.SenderRoot;
+
+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.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Test case "tree manual"
+ *
+ * @author rschoene - Initial contribution
+ */
+public class TreeManualTest extends AbstractTreeTest {
+
+  private Root model;
+  private SenderRoot senderRoot;
+  private MqttHandler handler;
+
+  @Test
+  public void checkJacksonReference() {
+    testJaddContainReferenceToJackson(
+        Paths.get("src", "test",
+            "02-after-ragconnect", "tree", "RagConnect.jadd"), true);
+  }
+
+  @Override
+  protected void createModel() {
+    model = new Root();
+    senderRoot = new SenderRoot();
+    model.addSenderRoot(senderRoot);
+
+    receiverRoot = new ReceiverRoot();
+    model.addReceiverRoot((ReceiverRoot) receiverRoot);
+  }
+
+  @Override
+  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));
+
+    // add dependencies
+    senderRoot.addInputDependency(senderRoot);
+
+    data = new ReceiverData();
+    handler.newConnection(TOPIC_ALFA, bytes -> data.numberOfTrees += 1);
+
+    // connect. important: first receiver, then sender. to not miss initial value.
+    assertTrue(receiverRoot.connectAlfa(mqttUri(TOPIC_ALFA)));
+    assertTrue(senderRoot.connectAlfa(mqttUri(TOPIC_ALFA), writeCurrentValue));
+  }
+
+  @Override
+  protected void closeConnections() {
+    if (handler != null) {
+      handler.close();
+    }
+    if (model != null) {
+      model.ragconnectCloseConnections();
+    }
+  }
+
+  protected void setInput(int input) {
+    senderRoot.setInput(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
new file mode 100644
index 0000000000000000000000000000000000000000..12b2c99912ac7f2b4401aeefd76a3f91fce06f7b
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/treeAllowedTokens/AbstractTreeAllowedTokensTest.java
@@ -0,0 +1,221 @@
+package org.jastadd.ragconnect.tests.treeAllowedTokens;
+
+import org.jastadd.ragconnect.tests.AbstractMqttTest;
+import org.jastadd.ragconnect.tests.TestUtils;
+import org.junit.jupiter.api.Tag;
+
+import java.io.IOException;
+import java.time.Instant;
+import java.time.Period;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Base class for test cases "tree allowed tokens manual" and "tree allowed tokens incremental".
+ *
+ * @author rschoene - Initial contribution
+ */
+@Tag("Tree")
+public abstract class AbstractTreeAllowedTokensTest extends AbstractMqttTest {
+
+  protected static final String TOPIC_INPUT1TRUE = "input1/true";
+  protected static final String TOPIC_INPUT1FALSE = "input1/false";
+  protected static final String TOPIC_INPUT2 = "input2";
+  protected static final String TOPIC_INPUT3 = "input3";
+  protected static final String TOPIC_ALFA = "alfa";
+  protected static final String TOPIC_ALFA_PRIMITIVE = "primitive";
+
+  protected static final String INSTANT_A = "1999-12-03T10:15:30Z";
+  protected static final String INSTANT_B = "2011-12-03T10:15:30Z";
+  protected static final String INSTANT_C = "2012-12-03T10:15:30Z";
+
+  protected TestWrapperReceiverRoot receiverRoot;
+
+  public interface TestWrapperReceiverRoot {
+    TestWrapperAlfa getAlfa();
+    TestWrapperAlfa getAlfaPrimitive();
+    boolean connectAlfa(String mqttUri) throws IOException;
+    boolean connectAlfaPrimitive(String mqttUri) throws IOException;
+  }
+  public interface TestWrapperAlfa {
+    boolean getBooleanValue();
+    int getIntValue();
+    short getShortValue();
+    long getLongValue();
+    float getFloatValue();
+    double getDoubleValue();
+    String getStringValue();
+    char getCharValue();
+    Instant getInstantValue();
+    Period getPeriodValue();
+  }
+
+  @Override
+  protected void communicateSendInitialValue() throws InterruptedException {
+    checkTree(1, false, 0, INSTANT_A, 0);
+    checkPrimitiveTree(1, INSTANT_A);
+
+    // flag: false. sendInput1WhenFalse(2)
+    sendInput1WhenFalse(2);
+    checkTree(2, false, 2, INSTANT_A, 0);
+    checkPrimitiveTree(1, INSTANT_A);
+
+    // flag: false. setFlag(false) -> no change
+    setFlag(false);
+    checkTree(2, false, 2, INSTANT_A, 0);
+    checkPrimitiveTree(1, INSTANT_A);
+
+    // flag: false. setFlag(true)
+    setFlag(true);
+    checkTree(3, true, 0, INSTANT_A, 0);
+    checkPrimitiveTree(1, INSTANT_A);
+
+    // flag: true. sendInput1WhenFalse(3) -> no change
+    sendInput1WhenFalse(3);
+    checkTree(3, true, 0, INSTANT_A, 0);
+    checkPrimitiveTree(1, INSTANT_A);
+
+    // flag: true. sendInput1WhenTrue(4)
+    sendInput1WhenTrue(4);
+    checkTree(4, true, 4, INSTANT_A, 0);
+    checkPrimitiveTree(1, INSTANT_A);
+
+    // sendInput2(INSTANT_B)
+    sendInput2(INSTANT_B);
+    checkTree(5, true, 4, INSTANT_B, 0);
+    checkPrimitiveTree(2, INSTANT_B);
+
+    // sendInput2(INSTANT_B) -> no change
+    sendInput2(INSTANT_B);
+    checkTree(5, true, 4, INSTANT_B, 0);
+    checkPrimitiveTree(2, INSTANT_B);
+
+    // sendInput3(5.1)
+    sendInput3(5.1);
+    checkTree(6, true, 4, INSTANT_B, 5.1);
+    checkPrimitiveTree(2, INSTANT_B);
+
+    // sendInput3(5.1) -> no change
+    sendInput3(5.1);
+    checkTree(6, true, 4, INSTANT_B, 5.1);
+    checkPrimitiveTree(2, INSTANT_B);
+  }
+
+  @Override
+  protected void communicateOnlyUpdatedValue() throws InterruptedException {
+    checkTree(0, false, null, null, 0);
+    checkPrimitiveTree(0, null);
+
+    // flag: false. sendInput1WhenFalse(12)
+    sendInput1WhenFalse(12);
+    checkTree(1, false, 12, INSTANT_A, 0);
+    checkPrimitiveTree(0, null);
+
+    // flag: false. setFlag(false) -> no change
+    setFlag(false);
+    checkTree(1, false, 12, INSTANT_A, 0);
+    checkPrimitiveTree(0, null);
+
+    // flag: false. setFlag(true)
+    setFlag(true);
+    checkTree(2, true, 0, INSTANT_A, 0);
+    checkPrimitiveTree(0, null);
+
+    // flag: true. sendInput1WhenFalse(13) -> no change
+    sendInput1WhenFalse(13);
+    checkTree(2, true, 0, INSTANT_A, 0);
+    checkPrimitiveTree(0, null);
+
+    // flag: true. sendInput1WhenTrue(14)
+    sendInput1WhenTrue(14);
+    checkTree(3, true, 14, INSTANT_A, 0);
+    checkPrimitiveTree(0, null);
+
+    // sendInput2(INSTANT_C)
+    sendInput2(INSTANT_C);
+    checkTree(4, true, 14, INSTANT_C, 0);
+    checkPrimitiveTree(1, INSTANT_C);
+
+    // sendInput2(INSTANT_C) -> no change
+    sendInput2(INSTANT_C);
+    checkTree(4, true, 14, INSTANT_C, 0);
+    checkPrimitiveTree(1, INSTANT_C);
+
+    // sendInput3(15.1)
+    sendInput3(15.1);
+    checkTree(5, true, 14, INSTANT_C, 15.1);
+    checkPrimitiveTree(1, INSTANT_C);
+
+    // sendInput3(15.1) -> no change
+    sendInput3(15.1);
+    checkTree(5, true, 14, INSTANT_C, 15.1);
+    checkPrimitiveTree(1, INSTANT_C);
+  }
+
+  protected void sendInput1WhenFalse(int value) {
+    publisher.publish(TOPIC_INPUT1FALSE, TestUtils.DefaultMappings.IntToBytes(value));
+  }
+
+  protected void sendInput1WhenTrue(int value) {
+    publisher.publish(TOPIC_INPUT1TRUE, TestUtils.DefaultMappings.IntToBytes(value));
+  }
+
+  protected void sendInput2(String value) {
+    publisher.publish(TOPIC_INPUT2, TestUtils.DefaultMappings.StringToBytes(value));
+  }
+
+  protected void sendInput3(double value) {
+    publisher.publish(TOPIC_INPUT3, TestUtils.DefaultMappings.DoubleToBytes(value));
+  }
+
+  protected abstract void setFlag(boolean value);
+
+  protected void checkTree(int expectedCount, boolean expectedBooleanValue, Integer expectedIntValue, String expectedStringValue, double expectedDoubleValue) throws InterruptedException {
+    TestUtils.waitForMqtt();
+
+    assertEquals(expectedCount, data.numberOfTrees);
+    if (expectedStringValue == null) {
+      assertNull(receiverRoot.getAlfa());
+    } else {
+      assertNotNull(receiverRoot.getAlfa());
+      TestWrapperAlfa alfa = receiverRoot.getAlfa();
+
+      assertEquals(expectedBooleanValue, alfa.getBooleanValue());
+      assertEquals(expectedIntValue, alfa.getIntValue());
+      assertEquals(expectedIntValue.shortValue(), alfa.getShortValue());
+      assertEquals(expectedIntValue.longValue(), alfa.getLongValue());
+
+      assertEquals(expectedDoubleValue, alfa.getFloatValue(), TestUtils.DELTA);
+      assertEquals(expectedDoubleValue, alfa.getDoubleValue(), TestUtils.DELTA);
+
+      assertEquals(expectedStringValue, alfa.getStringValue());
+      assertEquals(expectedStringValue.charAt(0), alfa.getCharValue());
+      assertEquals(Instant.parse(expectedStringValue), alfa.getInstantValue());
+      assertEquals(Period.of(0, 0, expectedIntValue), alfa.getPeriodValue());
+
+      checkMyEnum(alfa, expectedBooleanValue);
+    }
+  }
+
+  protected void checkPrimitiveTree(int expectedCount, String expectedStringValue) {
+    assertEquals(expectedCount, data.numberOfPrimitiveTrees);
+    if (expectedStringValue == null) {
+      assertNull(receiverRoot.getAlfaPrimitive());
+    } else {
+      assertNotNull(receiverRoot.getAlfaPrimitive());
+
+      TestWrapperAlfa alfaPrimitive = receiverRoot.getAlfaPrimitive();
+      assertEquals(expectedStringValue, alfaPrimitive.getStringValue());
+    }
+  }
+
+  protected abstract void checkMyEnum(TestWrapperAlfa alfa, boolean expectedBooleanValue);
+
+  protected ReceiverData data;
+
+  protected static class ReceiverData {
+    int numberOfTrees = 0;
+    int numberOfPrimitiveTrees = 0;
+  }
+
+}
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
new file mode 100644
index 0000000000000000000000000000000000000000..e848ca4059b962aa3431209e23de5ed9ccad4a24
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/treeAllowedTokens/TreeAllowedTokensIncrementalTest.java
@@ -0,0 +1,92 @@
+package org.jastadd.ragconnect.tests.treeAllowedTokens;
+
+import org.jastadd.ragconnect.tests.TestUtils;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import treeAllowedTokensInc.ast.*;
+
+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.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Test case "tree allowed tokens incremental"
+ *
+ * @author rschoene - Initial contribution
+ */
+@Tag("Incremental")
+public class TreeAllowedTokensIncrementalTest extends AbstractTreeAllowedTokensTest {
+
+  private Root model;
+  private SenderRoot senderRoot;
+  private MqttHandler handler;
+
+  @Test
+  public void checkJacksonReference() {
+    testJaddContainReferenceToJackson(
+        Paths.get("src", "test",
+            "02-after-ragconnect", "treeAllowedTokens", "RagConnect.jadd"), true);
+  }
+
+  @Override
+  protected void createModel() {
+    model = new Root();
+    senderRoot = new SenderRoot();
+    senderRoot.setFlag(false);
+    senderRoot.setInput2(INSTANT_A);
+    model.addSenderRoot(senderRoot);
+
+    receiverRoot = new ReceiverRoot();
+    model.addReceiverRoot((ReceiverRoot) receiverRoot);
+
+    model.ragconnectCheckIncremental();
+  }
+
+  @Override
+  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));
+
+    // no dependencies
+
+    data = new ReceiverData();
+    handler.newConnection(TOPIC_ALFA, bytes -> data.numberOfTrees += 1);
+    handler.newConnection(TOPIC_ALFA_PRIMITIVE, bytes -> data.numberOfPrimitiveTrees += 1);
+
+    // connect. important: first receiver, then sender. to not miss initial value.
+    assertTrue(senderRoot.connectInput1WhenFlagIsFalse(mqttUri(TOPIC_INPUT1FALSE)));
+    assertTrue(senderRoot.connectInput1WhenFlagIsTrue(mqttUri(TOPIC_INPUT1TRUE)));
+    assertTrue(senderRoot.connectInput2(mqttUri(TOPIC_INPUT2)));
+    assertTrue(senderRoot.connectInput3(mqttUri(TOPIC_INPUT3)));
+    assertTrue(receiverRoot.connectAlfa(mqttUri(TOPIC_ALFA)));
+    assertTrue(receiverRoot.connectAlfaPrimitive(mqttUri(TOPIC_ALFA_PRIMITIVE)));
+    assertTrue(senderRoot.connectAlfa(mqttUri(TOPIC_ALFA), writeCurrentValue));
+    assertTrue(senderRoot.connectAlfaPrimitive(mqttUri(TOPIC_ALFA_PRIMITIVE), writeCurrentValue));
+  }
+
+  @Override
+  protected void closeConnections() {
+    if (handler != null) {
+      handler.close();
+    }
+    if (model != null) {
+      model.ragconnectCloseConnections();
+    }
+  }
+
+  protected void setFlag(boolean value) {
+    senderRoot.setFlag(value);
+  }
+
+  @Override
+  protected void checkMyEnum(TestWrapperAlfa alfa, boolean expectedBooleanValue) {
+    assertEquals(expectedBooleanValue ? MyEnum.TRUE : MyEnum.FALSE, ((Alfa) alfa).getEnumValue());
+  }
+}
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
new file mode 100644
index 0000000000000000000000000000000000000000..3903834434d1b5031e1db5a68e6a6d5dbdef9a43
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/treeAllowedTokens/TreeAllowedTokensManualTest.java
@@ -0,0 +1,94 @@
+package org.jastadd.ragconnect.tests.treeAllowedTokens;
+
+import org.jastadd.ragconnect.tests.TestUtils;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import treeAllowedTokens.ast.*;
+
+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.junit.jupiter.api.Assertions.*;
+
+/**
+ * Test case "tree allowed tokens manual"
+ *
+ * @author rschoene - Initial contribution
+ */
+public class TreeAllowedTokensManualTest extends AbstractTreeAllowedTokensTest {
+
+  private Root model;
+  private SenderRoot senderRoot;
+  private MqttHandler handler;
+
+  @Test
+  public void checkJacksonReference() {
+    testJaddContainReferenceToJackson(
+        Paths.get("src", "test",
+            "02-after-ragconnect", "treeAllowedTokens", "RagConnect.jadd"), true);
+  }
+
+  @Override
+  protected void createModel() {
+    model = new Root();
+    senderRoot = new SenderRoot();
+    senderRoot.setFlag(false);
+    senderRoot.setInput2(INSTANT_A);
+    model.addSenderRoot(senderRoot);
+
+    receiverRoot = new ReceiverRoot();
+    model.addReceiverRoot((ReceiverRoot) receiverRoot);
+  }
+
+  @Override
+  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));
+
+    // add dependencies
+    senderRoot.addFlagDependency(senderRoot);
+    senderRoot.addInput1WhenFlagIsFalseDependency(senderRoot);
+    senderRoot.addInput1WhenFlagIsTrueDependency(senderRoot);
+    senderRoot.addInput2Dependency(senderRoot);
+    senderRoot.addInput3Dependency(senderRoot);
+    senderRoot.addPrimitiveInput2Dependency(senderRoot);
+
+    data = new ReceiverData();
+    handler.newConnection(TOPIC_ALFA, bytes -> data.numberOfTrees += 1);
+    handler.newConnection(TOPIC_ALFA_PRIMITIVE, bytes -> data.numberOfPrimitiveTrees += 1);
+
+    // connect. important: first receiver, then sender. to not miss initial value.
+    assertTrue(senderRoot.connectInput1WhenFlagIsFalse(mqttUri(TOPIC_INPUT1FALSE)));
+    assertTrue(senderRoot.connectInput1WhenFlagIsTrue(mqttUri(TOPIC_INPUT1TRUE)));
+    assertTrue(senderRoot.connectInput2(mqttUri(TOPIC_INPUT2)));
+    assertTrue(senderRoot.connectInput3(mqttUri(TOPIC_INPUT3)));
+    assertTrue(receiverRoot.connectAlfa(mqttUri(TOPIC_ALFA)));
+    assertTrue(receiverRoot.connectAlfaPrimitive(mqttUri(TOPIC_ALFA_PRIMITIVE)));
+    assertTrue(senderRoot.connectAlfa(mqttUri(TOPIC_ALFA), writeCurrentValue));
+    assertTrue(senderRoot.connectAlfaPrimitive(mqttUri(TOPIC_ALFA_PRIMITIVE), writeCurrentValue));
+  }
+
+  @Override
+  protected void closeConnections() {
+    if (handler != null) {
+      handler.close();
+    }
+    if (model != null) {
+      model.ragconnectCloseConnections();
+    }
+  }
+
+  protected void setFlag(boolean value) {
+    senderRoot.setFlag(value);
+  }
+
+  @Override
+  protected void checkMyEnum(TestWrapperAlfa alfa, boolean expectedBooleanValue) {
+    assertEquals(expectedBooleanValue ? MyEnum.TRUE : MyEnum.FALSE, ((Alfa) alfa).getEnumValue());
+  }
+}
diff --git a/settings.gradle b/settings.gradle
index e7769874a5f3186274ceecbcd445feeb656cf9a4..8a597cfa491920744bb8b5e1d3f103fb4fc95bb1 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,3 +1,9 @@
+pluginManagement {
+    plugins {
+        id 'org.jastadd' version '1.13.3'
+    }
+}
+
 rootProject.name = 'ragconnect'
 
 include 'relast-preprocessor'