diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index d3399cf3b1ba870b9d5c54978254134113d1d1b1..48ac46a416713cdf46c48e1286a73b8285529126 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -44,8 +44,8 @@ publish_dev:
   script:
     - "./gradlew setDevVersionForCI"
     - "./gradlew publish"
-  except:
-    - main
+  only:
+    - dev
 
 publish_main:
   image: openjdk:11
@@ -60,7 +60,6 @@ publish_main:
 ragdoc_build:
   image:
     name: "git-st.inf.tu-dresden.de:4567/jastadd/ragdoc-builder"
-    entrypoint: [""]
   stage: ragdoc
   needs:
     - build
@@ -68,6 +67,9 @@ ragdoc_build:
     - JAVA_FILES=$(find dumpAst.base/src/ -name '*.java')
     - echo $JAVA_FILES | wc -l
     - /ragdoc-builder/start-builder.sh -excludeGenerated -d data/ $JAVA_FILES
+  only:
+    - dev
+    - main
   artifacts:
     paths:
       - "data/"
@@ -75,7 +77,6 @@ ragdoc_build:
 ragdoc_view:
   image:
     name: "git-st.inf.tu-dresden.de:4567/jastadd/ragdoc-view:relations"
-    entrypoint: [""]
   stage: ragdoc
   needs:
     - ragdoc_build
@@ -84,7 +85,7 @@ ragdoc_view:
     - 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
+    - BASE_HREF=/dumpAst/ragdoc/ /ragdoc-view/build-view.sh --output-path=$OUTPUT_DIR
   only:
     - dev
     - main
@@ -92,7 +93,7 @@ ragdoc_view:
     paths:
       - "pages/docs/ragdoc"
 
-pages:
+build_pages:
   image: python:3.10.0-bullseye
   stage: publish
   needs:
@@ -105,5 +106,14 @@ pages:
   artifacts:
     paths:
       - public/
-  only:
-    - main
+
+pages:
+  stage: publish
+  needs:
+    - build_pages
+  script: [ "true" ]
+  artifacts:
+    paths:
+      - public
+  rules:
+    - if: '$CI_COMMIT_BRANCH == "main"'
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..80ee387db7b9df0b0b18117b75678097761d8513
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,29 @@
+BSD 3-Clause License
+
+Copyright (c) 2022, TU Dresden, Software Technology Group
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+* Neither the name of the copyright holder nor the names of its
+  contributors may be used to endorse or promote products derived from
+  this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/dumpAst.base/build.gradle b/dumpAst.base/build.gradle
index 96fd0c021d8d68a9b5cb57b7c433a157a6896a29..88ccfde78f90ad7ccb280fab03e417a534ccced3 100644
--- a/dumpAst.base/build.gradle
+++ b/dumpAst.base/build.gradle
@@ -147,7 +147,7 @@ task newVersion() {
 task setDevVersionForCI() {
     doFirst {
         def props = new Properties()
-        props['version'] = version + "-$System.env.CI_PIPELINE_IID"
+        props['version'] = version + "-dev-$System.env.CI_PIPELINE_IID"
         props.store(file(versionFile).newWriter(), null)
     }
 }
@@ -179,6 +179,15 @@ publishing {
     }
 }
 
+// --- Misc ---
+task ensureNoTrainlingNewlineForRelationStyleMustache() {
+    doFirst {
+        def text = project.file("src/main/resources/RelationStyle.mustache").text
+        project.file("src/main/resources/RelationStyle.mustache").write(text.strip())
+    }
+}
+
 // --- Task order ---
+compileJava.dependsOn ensureNoTrainlingNewlineForRelationStyleMustache
 generateAst.dependsOn relast
 publish.dependsOn jar
diff --git a/dumpAst.base/src/main/jastadd/ClassAnalysis.jrag b/dumpAst.base/src/main/jastadd/ClassAnalysis.jrag
index fdd6052df4fc03bb14e50d12eb11c36ec37d5f43..6558bc1eeb13ad3f63c0413ff0e212ea0d8bd635 100644
--- a/dumpAst.base/src/main/jastadd/ClassAnalysis.jrag
+++ b/dumpAst.base/src/main/jastadd/ClassAnalysis.jrag
@@ -48,29 +48,28 @@ aspect ClassAnalysis {
               String tokenName = invokeName(annotation);
               if (tokenName.startsWith("_impl_")) {
                 String relationName = titleCase(tokenName.substring(6));
-                try {
-                  java.lang.reflect.Method relationMethod = clazz.getMethod("get" + relationName);
+                if (present(getMethod(clazz, "get" + relationName), relationMethod -> {
                   // normal get + token-name -> singleRelation
-                  SingleRelationMethod singleRelationMethod = new SingleRelationMethod();
+                  SingleRelationMethod singleRelationMethod = getMethod(clazz, "has" + relationName).isPresent() ?
+                      new OptRelationMethod() : new SingleRelationMethod();
                   singleRelationMethod.setMethod(relationMethod);
                   singleRelationMethod.setName(relationName);
                   result.addOtherMethod(singleRelationMethod);
+                })) {
                   continue;
-                } catch (NoSuchMethodException e) {
-                  // ignore, but we know this is probably not a single relation
                 }
+                // we know here this is probably not a single or opt relation
                 // try list-relation next
-                try {
-                  java.lang.reflect.Method relationMethod = clazz.getMethod("get" + relationName + "List");
+                if (present(getMethod(clazz, "get" + relationName + "List"), relationMethod -> {
                   // normal get + token-name + "List" -> listRelation
                   ListRelationMethod listRelationMethod = new ListRelationMethod();
                   listRelationMethod.setMethod(relationMethod);
                   listRelationMethod.setName(relationName);
                   result.addOtherMethod(listRelationMethod);
+                })) {
                   continue;
-                } catch (NoSuchMethodException e) {
-                  // ignore, but we know this is probably not a relation at all
                 }
+                // we know here this is probably not a relation at all
               }
               IntrinsicTokenMethod tokenMethod = new IntrinsicTokenMethod();
               tokenMethod.setMethod(method);
@@ -201,6 +200,20 @@ aspect ClassAnalysis {
     }
   }
 
+  private static java.util.Optional<java.lang.reflect.Method> DumpAst.getMethod(Class<?> clazz, String methodName) {
+    try {
+      java.lang.reflect.Method method = clazz.getMethod(methodName);
+      return java.util.Optional.of(method);
+    } catch (NoSuchMethodException e) {
+      return java.util.Optional.empty();
+    }
+  }
+
+  private static <T> boolean DumpAst.present(java.util.Optional<T> optional, java.util.function.Consumer<T> callback) {
+    optional.ifPresent(callback);
+    return optional.isPresent();
+  }
+
   // --- astNodeAnnotationPrefix ---
   syn String DumpAst.astNodeAnnotationPrefix() = getPackageName() + ".ASTNodeAnnotation";
   inh String DumpNode.astNodeAnnotationPrefix();
diff --git a/dumpAst.base/src/main/jastadd/DumpAst.relast b/dumpAst.base/src/main/jastadd/DumpAst.relast
index fcfd923c0e47bc6648f3e397ecaa99b5ff1007a7..572b8b95a027c1f73e8503d4d39b2c45ad0b1276 100644
--- a/dumpAst.base/src/main/jastadd/DumpAst.relast
+++ b/dumpAst.base/src/main/jastadd/DumpAst.relast
@@ -5,26 +5,26 @@ DumpNode ::=  DumpChildNode* DumpToken* DumpRelation*
  <Name> <Label> <BackgroundColor> <TextColor> <Object:Object> <Invisible:boolean> <Computed:boolean> <ManualStereotypes>
  /InvisiblePath/ ;
 
-InnerDumpNode ;
-rel InnerDumpNode.DumpNode <-> DumpNode.ContainerOfInner ;
-InnerRelationDumpNode;
-rel InnerRelationDumpNode.DumpNode -> DumpNode ; // .ContainerOfInner*
+InnerDumpNode ::= <OriginalIndex:int> <Label> <LineColor> <TextColor> ;  //TODO remove colors (and label?)
+rel InnerDumpNode.DumpNode <-> DumpNode.ContainerOfInner? ;
+InnerRelationDumpNode ::= <OriginalIndex:int> <Label> <LineColor> <TextColor> ;  //TODO remove colors (and label?)
+rel InnerRelationDumpNode.DumpNode <-> DumpNode.ContainerOfRelationInner* ;
 
-abstract DumpChildNode ::= <Name> <Computed:boolean> ;
-DumpNormalChildNode : DumpChildNode ;
-rel DumpNormalChildNode.DumpNode <-> DumpNode.ContainerOfNormalChild ;
-DumpListChildNode : DumpChildNode ::= InnerDumpNode* ;
+abstract DumpChildNode ::= <Label> <Computed:boolean> <LineColor> <TextColor> ;
+DumpNormalChildNode : DumpChildNode ::= ;
+rel DumpNormalChildNode.DumpNode <-> DumpNode.ContainerOfNormalChild? ;
+DumpListChildNode : DumpChildNode ::= InnerDumpNode* <Name> ;
 
-abstract DumpToken ::= <Name> <Computed:boolean> ;
-DumpReferenceToken : DumpToken ;
-rel DumpReferenceToken.Value -> DumpNode ;
-DumpReferenceListToken : DumpToken ::= InnerRelationDumpNode* ;
+abstract DumpToken ::= <Label> <Computed:boolean> ;
+DumpReferenceToken : DumpToken ::= <LineColor> <TextColor> ;
+rel DumpReferenceToken.DumpNode -> DumpNode ;
+DumpReferenceListToken : DumpToken ::= InnerRelationDumpNode* <Name> <LineColor> <TextColor> ;
 DumpValueToken : DumpToken ::= <Value:Object> ;
 
-abstract DumpRelation ::= <Name> <Bidirectional:boolean> ;
-DumpNormalRelation : DumpRelation ;
+abstract DumpRelation ::= <Label> <Bidirectional:boolean> <LineColor> <TextColor> ;
+DumpNormalRelation : DumpRelation ::= ;
 rel DumpNormalRelation.DumpNode -> DumpNode ;
-DumpListRelation : DumpRelation ::= InnerRelationDumpNode* ;
+DumpListRelation : DumpRelation ::= InnerRelationDumpNode* <Name> ;
 
 // type of NTA
 InvisiblePath ::= InnerRelationDumpNode* ;
@@ -45,6 +45,7 @@ NormalListChildMethod : ListChildMethod ;
 NTAListChildMethod : ListChildMethod ;
 
 SingleRelationMethod : AnalysedMethod ;
+OptRelationMethod : SingleRelationMethod ;
 ListRelationMethod : AnalysedMethod ;
 
 // TODO can the refine bug also happen for refined attributes?
@@ -53,9 +54,6 @@ IntrinsicTokenMethod : TokenMethod ::= <Refined:boolean> ;
 AttributeMethod : TokenMethod ;
 
 BuildConfig ::= StyleInformation
- GlobalPatternCollection:PatternCollection
- ExcludeTypePattern:TypePatternCollectionMapping*
- IncludeTypePattern:TypePatternCollectionMapping*
  <IncludeRelationMethod:IncludeRelationMethod>
  <IncludeChildMethod:IncludeChildMethod>
  <IncludeAttributeMethod:IncludeAttributeMethod>
@@ -64,13 +62,16 @@ BuildConfig ::= StyleInformation
  <IncludeEmptyString:boolean>
  <ExcludeNullNodes:boolean>
  <Debug:boolean>;
-TypePatternCollectionMapping ::= <TypeRegex> PatternCollection ;
-PatternCollection ::= <TokenPattern> <ChildPattern> <RelationPattern> <AttributePattern> <NonterminalAttributePattern> ;
-StyleInformation ::= <NameMethod:StyleMethod> <BackgroundColorMethod:StyleMethod> <TextColorMethod:StyleMethod> <StereotypeMethod:StyleMethod> <ComputedColor>;
+StyleInformation ::= <ComputedColor>;
 
 PrintConfig ::= Header*
  <Scale:double>
  <Version>
  <RelationWithRank:boolean>
- <OrderChildren:boolean> ;
+ <OrderChildren:boolean>
+ <NodeStyleDefinition:NodeStyleDefinition>
+ <RelationStyleDefinition:RelationStyleDefinition>;
 Header ::= <Value> ;
+
+NodeStyle ::= <Label> <BackgroundColor> <TextColor> <StereoTypes> ;
+RelationStyle ::= <Label> <LineColor> <TextColor> ;
diff --git a/dumpAst.base/src/main/jastadd/Frontend.jrag b/dumpAst.base/src/main/jastadd/Frontend.jrag
index f5bf7c2fa2034bffb1b8a35c347abbc7eca1cc9b..eaaf02de2cef1576494e311800f539f2e5cc541e 100644
--- a/dumpAst.base/src/main/jastadd/Frontend.jrag
+++ b/dumpAst.base/src/main/jastadd/Frontend.jrag
@@ -1,36 +1,27 @@
 aspect Frontend {
-
-  // --- match{In,Ex}cludePatternCollection ---
-  syn PatternCollection BuildConfig.matchIncludePatternCollection(String typeName) {
-    for (TypePatternCollectionMapping mapping : getIncludeTypePatternList()) {
-      if (matches(mapping.typePattern(), typeName)) {
-        return mapping.getPatternCollection();
-      }
-    }
-    return null;
-  }
-  syn PatternCollection BuildConfig.matchExcludePatternCollection(String typeName) {
-    for (TypePatternCollectionMapping mapping : getExcludeTypePatternList()) {
-      if (matches(mapping.typePattern(), typeName)) {
-        return mapping.getPatternCollection();
-      }
-    }
-    return null;
-  }
-
   static StyleInformation StyleInformation.createDefault() {
     StyleInformation result = new StyleInformation();
-    result.setNameMethod(n -> n == null ? "null" : n.getClass().getSimpleName() + "@" + Integer.toHexString(n.hashCode()));
-    result.setBackgroundColorMethod(n -> "");
-    result.setTextColorMethod(n -> "");
-    result.setStereotypeMethod(n -> "");
     result.setComputedColor("blue");
     return result;
   }
 
   @FunctionalInterface
-  public interface StyleMethod<ASTNODE> {
-    String get(ASTNODE node);
+  public interface NodeStyleDefinition<ASTNODE> {
+    void style(ASTNODE node, NodeStyle style);
+  }
+
+  @FunctionalInterface
+  public interface RelationStyleDefinition<ASTNODE> {
+    void style(ASTNODE source, ASTNODE target, boolean isComputed, boolean isContainment, RelationStyle style);
+  }
+
+  public void NodeStyle.useSimpleLabel() {
+    setLabel(node.getObject() == null ? "null" : node.getObject().getClass().getSimpleName());
+  }
+
+  public void NodeStyle.useSimpleLabelWithHashCode() {
+    setLabel(node.getObject() == null ? "null" :
+        node.getObject().getClass().getSimpleName() + "@" + Integer.toHexString(node.getObject().hashCode()));
   }
 
   @FunctionalInterface
diff --git a/dumpAst.base/src/main/jastadd/GeneratedNavigation.jrag b/dumpAst.base/src/main/jastadd/GeneratedNavigation.jrag
index 4ef4b469c69254a667aa3449d9735b6a4a254bc6..c7ab0047767b4b12ff2598edce03d3809573028c 100644
--- a/dumpAst.base/src/main/jastadd/GeneratedNavigation.jrag
+++ b/dumpAst.base/src/main/jastadd/GeneratedNavigation.jrag
@@ -36,6 +36,12 @@ aspect Navigation {
   syn boolean AnalysedMethod.isSingleRelationMethod() = false;
   eq SingleRelationMethod.isSingleRelationMethod() = true;
 
+  /** Tests if SingleRelationMethod is a OptRelationMethod.
+  *  @return 'true' if this is a OptRelationMethod, otherwise 'false'
+  */
+  syn boolean SingleRelationMethod.isOptRelationMethod() = false;
+  eq OptRelationMethod.isOptRelationMethod() = true;
+
   /** Tests if AnalysedMethod is a ListRelationMethod.
   *  @return 'true' if this is a ListRelationMethod, otherwise 'false'
   */
@@ -102,6 +108,12 @@ aspect Navigation {
   syn boolean DumpToken.isDumpReferenceToken() = false;
   eq DumpReferenceToken.isDumpReferenceToken() = true;
 
+  /** Tests if DumpToken is a DumpReferenceListToken.
+  *  @return 'true' if this is a DumpReferenceListToken, otherwise 'false'
+  */
+  syn boolean DumpToken.isDumpReferenceListToken() = false;
+  eq DumpReferenceListToken.isDumpReferenceListToken() = true;
+
   /** Tests if DumpToken is a DumpValueToken.
   *  @return 'true' if this is a DumpValueToken, otherwise 'false'
   */
@@ -227,6 +239,13 @@ aspect Navigation {
   eq DumpToken.asDumpReferenceToken() = null;
   eq DumpReferenceToken.asDumpReferenceToken() = this;
 
+  /** casts a DumpToken into a DumpReferenceListToken if possible.
+   *  @return 'this' cast to a DumpReferenceListToken or 'null'
+   */
+  syn DumpReferenceListToken DumpToken.asDumpReferenceListToken();
+  eq DumpToken.asDumpReferenceListToken() = null;
+  eq DumpReferenceListToken.asDumpReferenceListToken() = this;
+
   /** casts a DumpToken into a DumpValueToken if possible.
    *  @return 'this' cast to a DumpValueToken or 'null'
    */
diff --git a/dumpAst.base/src/main/jastadd/Navigation.jrag b/dumpAst.base/src/main/jastadd/Navigation.jrag
index dc4ce9a3e857777d7305d07f48a436fb18c0bfc7..2bdae26788a81b697f720568b055d736d458b4a6 100644
--- a/dumpAst.base/src/main/jastadd/Navigation.jrag
+++ b/dumpAst.base/src/main/jastadd/Navigation.jrag
@@ -10,6 +10,11 @@ aspect Navigation {
   // --- buildConfig ---
   inh BuildConfig DumpNode.buildConfig();
   inh BuildConfig PrintConfig.buildConfig();
+  inh BuildConfig InnerDumpNode.buildConfig();
+  inh BuildConfig InnerRelationDumpNode.buildConfig();
+  inh BuildConfig DumpChildNode.buildConfig();
+  inh BuildConfig DumpRelation.buildConfig();
+  inh BuildConfig DumpToken.buildConfig();
   eq DumpAst.getChild().buildConfig() = getBuildConfig();
 
   // --- printConfig ---
@@ -25,11 +30,23 @@ aspect Navigation {
   inh DumpNode InnerRelationDumpNode.containingDumpNode();
   inh DumpNode DumpChildNode.containingDumpNode();
   inh DumpNode DumpRelation.containingDumpNode();
+  inh DumpNode DumpReferenceToken.containingDumpNode();
+  inh DumpNode DumpReferenceListToken.containingDumpNode();
   eq DumpNode.getDumpChildNode().containingDumpNode() = this;
   eq DumpNode.getDumpRelation().containingDumpNode() = this;
   eq DumpNode.getDumpToken().containingDumpNode() = this;
   eq DumpNode.getInvisiblePath().containingDumpNode() = this;
 
+  // --- containingDumpListChildNode ---
+  inh DumpListChildNode InnerDumpNode.containingDumpListChildNode();
+  eq DumpListChildNode.getInnerDumpNode().containingDumpListChildNode() = this;
+
+  // --- containingDumpListRelation ---
+  inh DumpListRelation InnerRelationDumpNode.containingDumpListRelation();
+  eq DumpListRelation.getInnerRelationDumpNode().containingDumpListRelation() = this;
+  eq DumpReferenceListToken.getInnerRelationDumpNode().containingDumpListRelation() = null;
+  eq InvisiblePath.getInnerRelationDumpNode().containingDumpListRelation() = null;
+
   // --- container ---
   syn DumpNode DumpNode.container() {
     if (getContainerOfNormalChild() != null) {
@@ -85,10 +102,10 @@ aspect Navigation {
     return result;
   }
 
-  // --- innerNotNull ---
-  syn boolean DumpNormalRelation.innerNotNull() = !printConfig().getRelationWithRank() && getDumpNode() != null && getDumpNode().getObject() != null;
-  syn boolean DumpReferenceToken.innerNotNull() = !printConfig().getRelationWithRank() && getValue() != null && getValue().getObject() != null;
-  syn boolean InnerRelationDumpNode.innerNotNull() = !printConfig().getRelationWithRank() && getDumpNode() != null && getDumpNode().getObject() != null;
+  // --- innerNotNullOrEmpty ---
+  syn boolean DumpNormalRelation.innerNotNullOrEmpty() = !printConfig().getRelationWithRank() && getDumpNode() != null && getDumpNode().getObject() != null && getDumpNode().getObject() != DumpAst.EMPTY;
+  syn boolean DumpReferenceToken.innerNotNullOrEmpty() = !printConfig().getRelationWithRank() && getDumpNode() != null && getDumpNode().getObject() != null && getDumpNode().getObject() != DumpAst.EMPTY;
+  syn boolean InnerRelationDumpNode.innerNotNullOrEmpty() = !printConfig().getRelationWithRank() && getDumpNode() != null && getDumpNode().getObject() != null && getDumpNode().getObject() != DumpAst.EMPTY;
 
   // === Method naviagtion ===
   coll java.util.List<SingleChildMethod> ClassAnalysisResult.singleChildMethods() [new java.util.ArrayList<>()] root ClassAnalysisResult;
diff --git a/dumpAst.base/src/main/jastadd/Printing.jrag b/dumpAst.base/src/main/jastadd/Printing.jrag
index f42e6fabd2035376057f4655ce36674261d27376..5b0c51589e911873d9b87ff45bc763a829b61773 100644
--- a/dumpAst.base/src/main/jastadd/Printing.jrag
+++ b/dumpAst.base/src/main/jastadd/Printing.jrag
@@ -1,43 +1,6 @@
 aspect Printing {
-  // --- outerNodeName ---
-  inh String InnerDumpNode.outerNodeName();
-  inh String InnerRelationDumpNode.outerNodeName();
-  inh String DumpChildNode.outerNodeName();
-  inh String DumpRelation.outerNodeName();
-  inh String DumpToken.outerNodeName();
-  inh String DumpReferenceToken.outerNodeName();
-  eq DumpNode.getChild().outerNodeName() = name();
-
-  // --- innerNodeName ---
-  syn String InnerDumpNode.innerNodeName() = getDumpNode() != null ? getDumpNode().name() : null;
-  syn String InnerRelationDumpNode.innerNodeName() = getDumpNode() != null ? getDumpNode().name() : null;
-  syn String DumpNormalChildNode.innerNodeName() = getDumpNode() != null ? getDumpNode().name() : null;
-  syn String DumpNormalRelation.innerNodeName() = getDumpNode() != null ? getDumpNode().name() : null;
-  syn String DumpReferenceToken.innerNodeName() = getValue().name();
-
   // --- name ---
   syn String DumpNode.name() = getName();  // might change in the future
-
-  // --- label ---
-  syn String DumpChildNode.label() = getName() + (getComputed() ? "()" : "");
-  syn String DumpRelation.label() = getName();
-  syn String DumpToken.label() = getName() + (getComputed() ? "()" : "");
-  syn String DumpNode.label() = getLabel();
-  inh String InnerDumpNode.label();
-  inh String InnerRelationDumpNode.label();
-  eq DumpListChildNode.getInnerDumpNode(int index).label() = label() + "[" + index + "]";
-  eq DumpListRelation.getInnerRelationDumpNode(int index).label() = label() + "[" + index + "]";
-  eq DumpReferenceListToken.getInnerRelationDumpNode(int index).label() = label() + "[" + index + "]";
-  eq InvisiblePath.getInnerRelationDumpNode(int index).label() = null;
-
-  // --- bothVisible ---
-  boolean ASTNode.bothVisible(DumpNode one, DumpNode two) {
-    return one != null && two != null && !one.getInvisible() && !two.getInvisible();
-  }
-  syn boolean InnerDumpNode.bothVisible() = bothVisible(containingDumpNode(), getDumpNode());
-  syn boolean InnerRelationDumpNode.bothVisible() = bothVisible(containingDumpNode(), getDumpNode());
-  syn boolean DumpNormalChildNode.bothVisible() = bothVisible(containingDumpNode(), getDumpNode());
-  syn boolean DumpNormalRelation.bothVisible() = bothVisible(containingDumpNode(), getDumpNode());
 }
 
 aspect Debugging {
diff --git a/dumpAst.base/src/main/jastadd/TemplateContext.jrag b/dumpAst.base/src/main/jastadd/TemplateContext.jrag
index e8d5549ccd5722276c53c6063ee381610e9f57c3..f7c0941d1442df026fc174fd892d7b699b829026 100644
--- a/dumpAst.base/src/main/jastadd/TemplateContext.jrag
+++ b/dumpAst.base/src/main/jastadd/TemplateContext.jrag
@@ -8,6 +8,11 @@ aspect TemplateContext {
     return getObject() == null;
   }
 
+  // --- isEmpty ---
+  syn boolean DumpNode.isEmpty() {
+    return getObject() == DumpAst.EMPTY;
+  }
+
   // --- isAstNode ---
   syn boolean DumpNode.isAstNode() {
     if (getObject() == null) {
@@ -22,11 +27,63 @@ aspect TemplateContext {
     return false;
   }
 
+  // --- outerNodeName ---
+  inh String InnerDumpNode.outerNodeName();
+  inh String InnerRelationDumpNode.outerNodeName();
+  inh String DumpChildNode.outerNodeName();
+  inh String DumpRelation.outerNodeName();
+  inh String DumpToken.outerNodeName();
+  inh String DumpReferenceToken.outerNodeName();
+  eq DumpNode.getChild().outerNodeName() = name();
+
+  // --- innerNodeName ---
+  syn String InnerDumpNode.innerNodeName() = getDumpNode() != null ? getDumpNode().name() : null;
+  syn String InnerRelationDumpNode.innerNodeName() = getDumpNode() != null ? getDumpNode().name() : null;
+  syn String DumpNormalChildNode.innerNodeName() = getDumpNode() != null ? getDumpNode().name() : null;
+  syn String DumpNormalRelation.innerNodeName() = getDumpNode() != null ? getDumpNode().name() : null;
+  syn String DumpReferenceToken.innerNodeName() = getDumpNode().name();
+
+  // --- label ---
+  syn String DumpChildNode.label() = safeLabel(getLabel() + (getComputed() ? "()" : ""));
+  syn String DumpRelation.label() = safeLabel(getLabel());
+  syn String DumpToken.label() = safeLabel(getLabel() + (getComputed() ? "()" : ""));
+  syn String DumpNode.label() = safeLabel(getLabel());
+  inh String InnerDumpNode.label();
+  inh String InnerRelationDumpNode.label();
+  eq DumpListChildNode.getInnerDumpNode(int index).label() {
+    InnerDumpNode inner = getInnerDumpNode(index);
+    return safeLabel(
+        (inner.getDumpNode().isEmpty() ? "" : "[" + chooseIndex(inner.getOriginalIndex(), index) + "]"));
+  }
+  eq DumpListRelation.getInnerRelationDumpNode(int index).label() {
+    InnerRelationDumpNode inner = getInnerRelationDumpNode(index);
+    return safeLabel(inner.getLabel() +
+        (inner.getDumpNode().isEmpty() ? "" : "[" + chooseIndex(inner.getOriginalIndex(), index) + "]"));
+  }
+  eq DumpReferenceListToken.getInnerRelationDumpNode(int index).label() = "[" + index + "]";
+  eq InvisiblePath.getInnerRelationDumpNode(int index).label() = null;
+  protected int ASTNode.chooseIndex(int originalIndex, int inheritedIndex) {
+    return originalIndex != 0 ? originalIndex : inheritedIndex;
+  }
+
+  static String ASTNode.safeLabel(String s) {
+    return s.isEmpty() ? " " : s;
+  }
+
+  // --- bothVisible ---
+  boolean ASTNode.bothVisible(DumpNode one, DumpNode two) {
+    return one != null && two != null && !one.getInvisible() && !two.getInvisible();
+  }
+  syn boolean InnerDumpNode.bothVisible() = bothVisible(containingDumpNode(), getDumpNode());
+  syn boolean InnerRelationDumpNode.bothVisible() = bothVisible(containingDumpNode(), getDumpNode());
+  syn boolean DumpNormalChildNode.bothVisible() = bothVisible(containingDumpNode(), getDumpNode());
+  syn boolean DumpNormalRelation.bothVisible() = bothVisible(containingDumpNode(), getDumpNode());
+
   // --- NTA: InvisiblePath ---
   syn InvisiblePath DumpNode.getInvisiblePath() {
     InvisiblePath result = new InvisiblePath();
     for (DumpNode successor : reachableThroughInvisible()) {
-      result.addInnerRelationDumpNode(new InnerRelationDumpNode(successor));
+      result.addInnerRelationDumpNode(new InnerRelationDumpNode().setDumpNode(successor));
     }
     return result;
   }
@@ -49,9 +106,9 @@ aspect TemplateContext {
   // --- labelAndTextColor ---
   syn String DumpNode.labelAndTextColor() {
     if (getTextColor().isEmpty()) {
-      return getLabel();
+      return label();
     } else {
-      return "<color:" + getTextColor() + ">" + getLabel() + "</color>";
+      return "<color:" + getTextColor() + ">" + label() + "</color>";
     }
   }
 
diff --git a/dumpAst.base/src/main/jastadd/ToYaml.jrag b/dumpAst.base/src/main/jastadd/ToYaml.jrag
index 0df35a8701bd1a8b4d5b93ab04d47a327ce21436..82fd9ab239bfa90fc8bdddb8d9685a452fd56032 100644
--- a/dumpAst.base/src/main/jastadd/ToYaml.jrag
+++ b/dumpAst.base/src/main/jastadd/ToYaml.jrag
@@ -32,9 +32,9 @@ aspect ToYaml {
     addYamledList(result, "Headers", getHeaderList(), fromRelation);
 
     // tokens
-    result.put("scale", getScale());
-    result.put("version", getVersion());
-    result.put("orderChildren", getOrderChildren());
+    result.put("OrderChildren", getOrderChildren());
+    result.put("Scale", getScale());
+    result.put("Version", getVersion());
 
     // attributes
     result.put("debug", debug());
@@ -44,7 +44,7 @@ aspect ToYaml {
   syn MappingElement Header.toYaml(boolean fromRelation) {
     MappingElement result = new MappingElement();
     // tokens
-    result.put("value", getValue());
+    result.put("Value", getValue());
     return result;
   }
 
@@ -59,19 +59,19 @@ aspect ToYaml {
     }
 
     // tokens
-    result.put("name", getName());
+    result.put("Name", getName());
     if (!fromRelation) {
-      result.put("computed", getComputed());
-      result.put("label", getLabel());
-      result.put("backgroundColor", getBackgroundColor());
-      result.put("textColor", getTextColor());
-      result.put("invisible", getInvisible());
+      result.put("BackgroundColor", getBackgroundColor());
+      result.put("Invisible", getInvisible());
+      result.put("TextColor", getTextColor());
     }
 
     // attributes
     if (!fromRelation) {
       result.put("isNull", isNull());
+      result.put("isEmpty", isEmpty());
       result.put("isAstNode", isAstNode());
+      result.put("label", label());
       result.put("labelAndTextColor", labelAndTextColor());
       result.put("stereotypeList", stereotypeList());
       addYamledList(result, "myChildren", myChildren(), true);
@@ -88,12 +88,15 @@ aspect ToYaml {
 
   syn MappingElement DumpChildNode.toYaml(boolean fromRelation) {
     MappingElement result = new MappingElement();
+
     // tokens
-    result.put("name", getName());
-    result.put("computed", getComputed());
+    result.put("LineColor", getLineColor());
+    result.put("TextColor", getTextColor());
+
     // attributes
-    result.put("label", label());
     result.put("isList", isList());
+    result.put("label", label());
+    result.put("needRelationStyling", needRelationStyling());
     result.put("outerNodeName", outerNodeName());
 
     return result;
@@ -101,6 +104,7 @@ aspect ToYaml {
 
   syn MappingElement DumpNormalChildNode.toYaml(boolean fromRelation) {
     MappingElement result = super.toYaml(fromRelation);
+
     // attributes
     result.put("bothVisible", bothVisible());
     result.put("innerNodeName", innerNodeName());
@@ -109,6 +113,10 @@ aspect ToYaml {
 
   syn MappingElement DumpListChildNode.toYaml(boolean fromRelation) {
     MappingElement result = super.toYaml(fromRelation);
+
+    // tokens
+    result.put("Name", getName());
+
     // children
     addYamledList(result, "InnerDumpNodes", getInnerDumpNodeList(), fromRelation);
     return result;
@@ -116,28 +124,33 @@ aspect ToYaml {
 
   syn MappingElement DumpToken.toYaml(boolean fromRelation) {
     MappingElement result = new MappingElement();
-    // tokens
-    result.put("name", getName());
-    result.put("computed", getComputed());
+
     // attributes
+    result.put("isDumpValueToken", isDumpValueToken());
     result.put("isList", isList());
     result.put("label", label());
-    result.put("isDumpValueToken", isDumpValueToken());
     return result;
   }
 
   syn MappingElement DumpValueToken.toYaml(boolean fromRelation) {
     MappingElement result = super.toYaml(fromRelation);
+
     // tokens
-    result.put("value", getValue().toString());
+    result.put("Value", getValue().toString());
     return result;
   }
 
   syn MappingElement DumpReferenceToken.toYaml(boolean fromRelation) {
     MappingElement result = super.toYaml(fromRelation);
+
+    // tokens
+    result.put("LineColor", getLineColor());
+    result.put("TextColor", getTextColor());
+
     // attributes
     result.put("innerNodeName", innerNodeName());
-    result.put("innerNotNull", innerNotNull());
+    result.put("innerNotNullOrEmpty", innerNotNullOrEmpty());
+    result.put("needRelationStyling", needRelationStyling());
     result.put("outerNodeName", outerNodeName());
     return result;
   }
@@ -145,37 +158,52 @@ aspect ToYaml {
   syn MappingElement DumpReferenceListToken.toYaml(boolean fromRelation) {
     MappingElement result = super.toYaml(fromRelation);
 
+    // tokens
+    result.put("Name", getName());
+    result.put("LineColor", getLineColor());
+    result.put("TextColor", getTextColor());
+
     // children
     addYamledList(result, "InnerRelationDumpNode", getInnerRelationDumpNodeList(), fromRelation);
 
     // attributes
+    result.put("needRelationStyling", needRelationStyling());
     result.put("outerNodeName", outerNodeName());
     return result;
   }
 
   syn MappingElement DumpRelation.toYaml(boolean fromRelation) {
     MappingElement result = new MappingElement();
+
     // tokens
-    result.put("name", getName());
-    result.put("bidirectional", getBidirectional());
+    result.put("Bidirectional", getBidirectional());
+    result.put("LineColor", getLineColor());
+    result.put("TextColor", getTextColor());
+
     // attributes
     result.put("isList", isList());
     result.put("label", label());
+    result.put("needRelationStyling", needRelationStyling());
     result.put("outerNodeName", outerNodeName());
     return result;
   }
 
   syn MappingElement DumpNormalRelation.toYaml(boolean fromRelation) {
     MappingElement result = super.toYaml(fromRelation);
+
     // attributes
     result.put("bothVisible", bothVisible());
     result.put("innerNodeName", innerNodeName());
-    result.put("innerNotNull", innerNotNull());
+    result.put("innerNotNullOrEmpty", innerNotNullOrEmpty());
     return result;
   }
 
   syn MappingElement DumpListRelation.toYaml(boolean fromRelation) {
     MappingElement result = super.toYaml(fromRelation);
+
+    // tokens
+    result.put("Name", getName());
+
     // children
     addYamledList(result, "InnerRelationDumpNode", getInnerRelationDumpNodeList(), fromRelation);
     return result;
@@ -183,27 +211,40 @@ aspect ToYaml {
 
   syn MappingElement InnerDumpNode.toYaml(boolean fromRelation) {
     MappingElement result = new MappingElement();
+
+    // tokens
+    result.put("LineColor", getLineColor());
+    result.put("TextColor", getTextColor());
+
     // attributes
     result.put("bothVisible", bothVisible());
     result.put("innerNodeName", innerNodeName());
-    result.put("outerNodeName", outerNodeName());
     result.put("label", label());
+    result.put("needRelationStyling", needRelationStyling());
+    result.put("outerNodeName", outerNodeName());
     return result;
   }
 
   syn MappingElement InnerRelationDumpNode.toYaml(boolean fromRelation) {
     MappingElement result = new MappingElement();
+
+    // tokens
+    result.put("LineColor", getLineColor());
+    result.put("TextColor", getTextColor());
+
     // attributes
     result.put("bothVisible", bothVisible());
     result.put("innerNodeName", innerNodeName());
-    result.put("innerNotNull", innerNotNull());
-    result.put("outerNodeName", outerNodeName());
+    result.put("innerNotNullOrEmpty", innerNotNullOrEmpty());
     result.put("label", label());
+    result.put("needRelationStyling", needRelationStyling());
+    result.put("outerNodeName", outerNodeName());
     return result;
   }
 
   syn MappingElement InvisiblePath.toYaml(boolean fromRelation) {
     MappingElement result = super.toYaml(fromRelation);
+
     // children
     addYamledList(result, "InnerRelationDumpNode", getInnerRelationDumpNodeList(), fromRelation);
     return result;
@@ -237,7 +278,7 @@ aspect ToYaml {
     if (value == null || value.equals("null")) {
       return StringElement.of("null");
     }
-    if (value.isEmpty()) {
+    if (value.isBlank()) {
       return StringElement.of(value);
     }
     return containsAny(value, ",#[{\"\n") ?
diff --git a/dumpAst.base/src/main/jastadd/Transform.jadd b/dumpAst.base/src/main/jastadd/Transform.jadd
index 747a59b1a71a04a008a36c8f5a5b8b054b8a8e65..eecc08257996d858cc3c1efdffa7ed843aeb9ffc 100644
--- a/dumpAst.base/src/main/jastadd/Transform.jadd
+++ b/dumpAst.base/src/main/jastadd/Transform.jadd
@@ -53,6 +53,8 @@ aspect Transform {
     return new TransformationOptions(Source.NORMAL, false, false);
   }
 
+  public static final Object DumpAst.EMPTY = new Object();
+
   // --- transform --- (need to be a method, because it alters the AST while traversing the object structure)
   protected DumpNode DumpAst.transform(TransformationTransferInformation tti, Object obj)
       throws java.lang.reflect.InvocationTargetException, IllegalAccessException, NoSuchMethodException {
@@ -81,7 +83,7 @@ aspect Transform {
     }
     DumpNode node;
     String objClassName;
-    if (obj == null) {
+    if (obj == null || obj == EMPTY) {
       node = null;
       objClassName = "null";
     } else {
@@ -97,7 +99,7 @@ aspect Transform {
     } else {
       node = new DumpNode();
       node.setObject(obj);
-      node.setName("node" + (tti.nodeCounter++));
+      node.setName(tti.nextName());
       node.setComputed(options.computed);
       tti.transformed.put(obj, node);
       this.addDumpNode(node);
@@ -114,8 +116,8 @@ aspect Transform {
     if (options.invisible || !isTypeEnabled(objClassName)) {
       node.setInvisible(true);
     }
-    if (obj == null) {
-      // for a null object, we do not need any further analysis
+    if (obj == null || obj == EMPTY) {
+      // for a null or empty object, we do not need any further analysis
       return node;
     }
     final ClassAnalysisResult car = analyzeClass(obj.getClass());
@@ -124,178 +126,261 @@ aspect Transform {
     }
     for (AnalysedMethod containmentMethod : car.getContainmentMethodList()) {
       if (containmentMethod.isSingleChildMethod() || containmentMethod.isOptChildMethod()) {
-        // -- singleChild or optChild --
-        Object target;
-        if (containmentMethod.isOptChildMethod() && !((boolean) containmentMethod.asOptChildMethod().getCheckMethod().invoke(obj))) {
-          if (getBuildConfig().getExcludeNullNodes()) {
-            continue;
-            //target = containmentMethod.getMethod().invoke(obj);
-          } else {
-            target = null;
-          }
-        } else {
-          target = containmentMethod.getMethod().invoke(obj);
-        }
-        String childName = containmentMethod.getName();
-        if (!getBuildConfig().getIncludeChildMethod().shouldInclude(obj, target, childName)) {
-          continue;
-        }
-        DumpNode targetNode = transform(tti, target, options.asNormal(false).allowNullObjectsOnce());
-        if (targetNode != null) {
-          DumpNormalChildNode normalChild = new DumpNormalChildNode();
-          normalChild.setName(childName);
-          normalChild.setDumpNode(targetNode);
-          normalChild.setComputed(false);
-          node.addDumpChildNode(normalChild);
-        }
+        handleContainmentSingleOrOptChild(node, containmentMethod, obj, tti, options);
       } else if (containmentMethod.isListChildMethod()) {
-        // -- listChild --
-        Iterable<?> targetList = (Iterable<?>) containmentMethod.getMethod().invoke(obj);
-        DumpListChildNode listChild = new DumpListChildNode();
-        listChild.setComputed(false);
-        String childName = containmentMethod.getName();
-        listChild.setName(childName);
-        for (Object target : targetList) {
-          if (!getBuildConfig().getIncludeChildMethod().shouldInclude(obj, target, childName)) {
-            continue;
-          }
-          DumpNode targetNode = transform(tti, target, options.asNormal(false));
-          if (target != null && targetNode != null) {
-            listChild.addInnerDumpNode(new InnerDumpNode().setDumpNode(targetNode));
-          }
-        }
-        if (listChild.getNumInnerDumpNode() > 0) {
-          node.addDumpChildNode(listChild);
-        }
+        handleContainmentListChild(node, containmentMethod, obj, tti, options);
       } else {
         throw new RuntimeException("Unknown containment method type " + containmentMethod);
       }
     }
     for (AnalysedMethod otherMethod : car.getOtherMethodList()) {
       if (otherMethod.isSingleChildMethod()) {
-        // -- singleChild --
-        String childName = otherMethod.getName();
-        boolean computed = otherMethod.asSingleChildMethod().isNTASingleChildMethod();
-        boolean shouldInclude = computed ?
-            getBuildConfig().getIncludeAttributeMethod().shouldInclude(obj, childName, true, () -> catchedInvoke(otherMethod.getMethod(), obj)) :
-            getBuildConfig().getIncludeChildMethod().shouldInclude(obj, otherMethod.getMethod().invoke(obj), childName);
-        if (!shouldInclude) {
-          continue;
-        }
-        DumpNode targetNode = transform(tti, otherMethod.getMethod().invoke(obj), options.asNormal(false).computed(computed).allowNullObjectsOnce());
-        if (targetNode != null) {
-          DumpNormalChildNode normalChild = new DumpNormalChildNode();
-          normalChild.setName(childName);
-          normalChild.setDumpNode(targetNode);
-          normalChild.setComputed(computed);
-          node.addDumpChildNode(normalChild);
-        }
+        handleOtherSingleChild(node, otherMethod, obj, tti, options);
       } else if (otherMethod.isListChildMethod()) {
-        // -- listChild --
-        // it is always a NTAListChildMethod
-        String childName = otherMethod.getName();
-        if (!getBuildConfig().getIncludeAttributeMethod().shouldInclude(obj, childName, true, () -> catchedInvoke(otherMethod.getMethod(), obj))) {
-          continue;
-        }
-        Iterable<?> targetList = (Iterable<?>) otherMethod.getMethod().invoke(obj);
-        DumpListChildNode listChild = new DumpListChildNode();
-        boolean computed = otherMethod.asListChildMethod().isNTAListChildMethod();
-        listChild.setComputed(computed);
-        listChild.setName(childName);
-        for (Object target : targetList) {
-          DumpNode targetNode = transform(tti, target, options.asNormal(false).computed(computed));
-          if (target != null && targetNode != null) {
-            listChild.addInnerDumpNode(new InnerDumpNode().setDumpNode(targetNode));
-          }
-        }
-        if (listChild.getNumInnerDumpNode() > 0) {
-          node.addDumpChildNode(listChild);
-        }
+        handleOtherListChild(node, otherMethod, obj, tti, options);
       } else if (otherMethod.isSingleRelationMethod()) {
-        // -- singleRelation --
-        Object target = otherMethod.getMethod().invoke(obj);
-        if (!getBuildConfig().getIncludeRelationMethod().shouldInclude(obj, target, otherMethod.getName())) {
-          continue;
-        }
-        DumpNode targetNode = transform(tti, target, options.asRelation());
-        if (targetNode != null) {
-          DumpNormalRelation normalRelation = new DumpNormalRelation();
-          normalRelation.setName(otherMethod.getName());
-          normalRelation.setDumpNode(targetNode);
-          node.addDumpRelation(normalRelation);
-        }
+        handleOtherSingleRelation(node, otherMethod, obj, tti, options);
       } else if (otherMethod.isListRelationMethod()) {
-        // -- listRelation --
-        Iterable<?> targetList = (Iterable<?>) otherMethod.getMethod().invoke(obj);
-        DumpListRelation listRelation = new DumpListRelation();
-        listRelation.setName(otherMethod.getName());
-        for (Object target : targetList) {
-          if (!getBuildConfig().getIncludeRelationMethod().shouldInclude(obj, target, otherMethod.getName())) {
-            continue;
-          }
-          DumpNode targetNode = transform(tti, target, options.asRelation());
-          if (target != null && targetNode != null) {
-            listRelation.addInnerRelationDumpNode(new InnerRelationDumpNode(targetNode));
+        handleOtherListRelation(node, otherMethod, obj, tti, options);
+      } else if (otherMethod.isTokenMethod()) {
+        handleOtherToken(node, otherMethod, obj, tti, options);
+      } else {
+        throw new RuntimeException("Unknown other method type " + otherMethod);
+      }
+    }
+    return node;
+  }
+
+  private boolean DumpAst.handleContainmentSingleOrOptChild(DumpNode node, AnalysedMethod containmentMethod,
+      Object obj, TransformationTransferInformation tti, TransformationOptions options)
+      throws java.lang.reflect.InvocationTargetException, IllegalAccessException, NoSuchMethodException {
+    // -- singleChild or optChild --
+    Object target;
+    if (containmentMethod.isOptChildMethod() && !((boolean) containmentMethod.asOptChildMethod().getCheckMethod().invoke(obj))) {
+      if (getBuildConfig().getExcludeNullNodes()) {
+        return false;
+      } else {
+        target = null;
+      }
+    } else {
+      target = containmentMethod.getMethod().invoke(obj);
+    }
+    String childName = containmentMethod.getName();
+    if (!getBuildConfig().getIncludeChildMethod().shouldInclude(obj, target, childName)) {
+      return false;
+    }
+    DumpNode targetNode = transform(tti, target, options.asNormal(false).allowNullObjectsOnce());
+    if (targetNode != null) {
+      DumpNormalChildNode normalChild = new DumpNormalChildNode();
+      normalChild.setLabel(childName + (containmentMethod.isOptChildMethod() ? "?" : ""));
+      normalChild.setDumpNode(targetNode);
+      normalChild.setComputed(false);
+      node.addDumpChildNode(normalChild);
+      applyStyle(normalChild);
+    }
+    return true;
+  }
+
+  private boolean DumpAst.handleContainmentListChild(DumpNode node, AnalysedMethod containmentMethod,
+  Object obj, TransformationTransferInformation tti, TransformationOptions options)
+      throws java.lang.reflect.InvocationTargetException, IllegalAccessException, NoSuchMethodException {
+    // -- listChild --
+    Iterable<?> targetList = (Iterable<?>) containmentMethod.getMethod().invoke(obj);
+    DumpListChildNode listChild = new DumpListChildNode();
+    listChild.setComputed(false);
+    String childName = containmentMethod.getName();
+    listChild.setLabel(childName);
+    listChild.setName(tti.nextName());
+    node.addDumpChildNode(listChild);
+    RelationStyle style = applyStyle(listChild);
+
+    int index = -1;
+    for (Object target : targetList) {
+      index++;
+      if (!getBuildConfig().getIncludeChildMethod().shouldInclude(obj, target, childName)) {
+        continue;
+      }
+      DumpNode targetNode = transform(tti, target, options.asNormal(false));
+      if (target != null && targetNode != null) {
+        InnerDumpNode inner = new InnerDumpNode().setDumpNode(targetNode).setOriginalIndex(index);
+        listChild.addInnerDumpNode(inner);
+        inner.applyStyle(style);
+      }
+    }
+    if (listChild.getNumInnerDumpNode() == 0) {
+      InnerDumpNode inner = new InnerDumpNode().setDumpNode(transform(tti, EMPTY,
+          options.asNormal(false).allowNullObjectsOnce()));
+      listChild.addInnerDumpNode(inner);
+      inner.applyStyle(style);
+    }
+    return true;
+  }
+
+  private boolean DumpAst.handleOtherSingleChild(DumpNode node, AnalysedMethod otherMethod,
+      Object obj, TransformationTransferInformation tti, TransformationOptions options)
+      throws java.lang.reflect.InvocationTargetException, IllegalAccessException, NoSuchMethodException {
+    // -- singleChild --
+    String childName = otherMethod.getName();
+    boolean computed = otherMethod.asSingleChildMethod().isNTASingleChildMethod();
+    boolean shouldInclude = computed ?
+        getBuildConfig().getIncludeAttributeMethod().shouldInclude(obj, childName, true, () -> catchedInvoke(otherMethod.getMethod(), obj)) :
+        getBuildConfig().getIncludeChildMethod().shouldInclude(obj, otherMethod.getMethod().invoke(obj), childName);
+    if (!shouldInclude) {
+      return false;
+    }
+    DumpNode targetNode = transform(tti, otherMethod.getMethod().invoke(obj), options.asNormal(false).computed(computed).allowNullObjectsOnce());
+    if (targetNode != null) {
+      DumpNormalChildNode normalChild = new DumpNormalChildNode();
+      normalChild.setLabel(childName);
+      normalChild.setDumpNode(targetNode);
+      normalChild.setComputed(computed);
+      node.addDumpChildNode(normalChild);
+      applyStyle(normalChild);
+    }
+    return true;
+  }
+
+  private boolean DumpAst.handleOtherListChild(DumpNode node, AnalysedMethod otherMethod,
+      Object obj, TransformationTransferInformation tti, TransformationOptions options)
+      throws java.lang.reflect.InvocationTargetException, IllegalAccessException, NoSuchMethodException {
+    // -- listChild --
+    // it is always a NTAListChildMethod
+    String childName = otherMethod.getName();
+    if (!getBuildConfig().getIncludeAttributeMethod().shouldInclude(obj, childName, true, () -> catchedInvoke(otherMethod.getMethod(), obj))) {
+      return false;
+    }
+    Iterable<?> targetList = (Iterable<?>) otherMethod.getMethod().invoke(obj);
+    DumpListChildNode listChild = new DumpListChildNode();
+    boolean computed = otherMethod.asListChildMethod().isNTAListChildMethod();
+    listChild.setComputed(computed);
+    listChild.setLabel(childName);
+    listChild.setName(tti.nextName());
+    node.addDumpChildNode(listChild);
+    RelationStyle style = applyStyle(listChild);
+
+    for (Object target : targetList) {
+      DumpNode targetNode = transform(tti, target, options.asNormal(false).computed(computed));
+      if (target != null && targetNode != null) {
+        InnerDumpNode inner = new InnerDumpNode().setDumpNode(targetNode);
+        listChild.addInnerDumpNode(inner);
+        inner.applyStyle(style);
+      }
+    }
+    return true;
+  }
+
+  private boolean DumpAst.handleOtherSingleRelation(DumpNode node, AnalysedMethod otherMethod,
+      Object obj, TransformationTransferInformation tti, TransformationOptions options)
+      throws java.lang.reflect.InvocationTargetException, IllegalAccessException, NoSuchMethodException {
+    // -- singleRelation --
+    Object target = otherMethod.getMethod().invoke(obj);
+    if (!getBuildConfig().getIncludeRelationMethod().shouldInclude(obj, target, otherMethod.getName())) {
+      return false;
+    }
+    DumpNode targetNode = transform(tti, target, options.asRelation());
+    if (targetNode != null) {
+      DumpNormalRelation normalRelation = new DumpNormalRelation();
+      normalRelation.setLabel(otherMethod.getName() +
+          (otherMethod.asSingleRelationMethod().isOptRelationMethod() ? "?" : ""));
+      normalRelation.setDumpNode(targetNode);
+      node.addDumpRelation(normalRelation);
+      applyStyle(normalRelation);
+    }
+    return true;
+  }
+
+  private boolean DumpAst.handleOtherListRelation(DumpNode node, AnalysedMethod otherMethod,
+      Object obj, TransformationTransferInformation tti, TransformationOptions options)
+      throws java.lang.reflect.InvocationTargetException, IllegalAccessException, NoSuchMethodException {
+    // -- listRelation --
+    Iterable<?> targetList = (Iterable<?>) otherMethod.getMethod().invoke(obj);
+    DumpListRelation listRelation = new DumpListRelation();
+    listRelation.setLabel(otherMethod.getName());
+    listRelation.setName(tti.nextName());
+    node.addDumpRelation(listRelation);
+    RelationStyle style = applyStyle(listRelation);
+
+    int index = -1;
+    for (Object target : targetList) {
+      index++;
+      if (!getBuildConfig().getIncludeRelationMethod().shouldInclude(obj, target, otherMethod.getName())) {
+        continue;
+      }
+      DumpNode targetNode = transform(tti, target, options.asRelation());
+      if (target != null && targetNode != null) {
+        InnerRelationDumpNode inner = new InnerRelationDumpNode().setDumpNode(targetNode).setOriginalIndex(index);
+        listRelation.addInnerRelationDumpNode(inner);
+        inner.applyStyle(style);
+      }
+    }
+    if (listRelation.getNumInnerRelationDumpNode() == 0) {
+      InnerRelationDumpNode inner = new InnerRelationDumpNode().setDumpNode(transform(tti, EMPTY,
+          options.asNormal(false).allowNullObjectsOnce()));
+      listRelation.addInnerRelationDumpNode(inner);
+      inner.applyStyle(style);
+    }
+    return true;
+  }
+
+  private boolean DumpAst.handleOtherToken(DumpNode node, AnalysedMethod otherMethod,
+      Object obj, TransformationTransferInformation tti, TransformationOptions options)
+      throws java.lang.reflect.InvocationTargetException, IllegalAccessException, NoSuchMethodException {
+    // -- token --
+    TokenMethod tokenMethod = otherMethod.asTokenMethod();
+    boolean shouldInclude = tokenMethod.isAttributeMethod() ?
+        getBuildConfig().getIncludeAttributeMethod().shouldInclude(obj, otherMethod.getName(), false, () -> catchedInvoke(otherMethod.getMethod(), obj)) :
+        getBuildConfig().getIncludeTokenMethod().shouldInclude(obj, otherMethod.getName(), otherMethod.getMethod().invoke(obj));
+    if (!shouldInclude) {
+      return false;
+    }
+    Object target = otherMethod.getMethod().invoke(obj);
+    // local function to add common member and add the token-node to the given node
+    java.util.function.Consumer<DumpToken> setMemberAndAdd = token -> {
+      token.setLabel(otherMethod.getName());
+      token.setComputed(otherMethod.asTokenMethod().isAttributeMethod());
+      node.addDumpToken(token);
+    };
+    if (target != null) {
+      boolean atLeastOneASTNode = false;
+      if (Iterable.class.isAssignableFrom(target.getClass())) {
+        java.util.List<DumpNode> nodes = new java.util.ArrayList<>();
+        Iterable<?> iterable = (Iterable<?>) target;
+        for (Object element : iterable) {
+          // check if isAstNode for first non-null. if yes, use DumpReferenceListToken, otherwise DumpValueToken
+          DumpNode nodeForElement = transform(tti, element, options.asRelation());
+          nodes.add(nodeForElement);
+          if (nodeForElement != null && nodeForElement.isAstNode()) {
+            atLeastOneASTNode = true;
           }
         }
-        if (listRelation.getNumInnerRelationDumpNode() > 0) {
-          node.addDumpRelation(listRelation);
-        }
-      } else if (otherMethod.isTokenMethod()) {
-        // -- token --
-        TokenMethod tokenMethod = otherMethod.asTokenMethod();
-        boolean shouldInclude = tokenMethod.isAttributeMethod() ?
-            getBuildConfig().getIncludeAttributeMethod().shouldInclude(obj, otherMethod.getName(), false, () -> catchedInvoke(otherMethod.getMethod(), obj)) :
-            getBuildConfig().getIncludeTokenMethod().shouldInclude(obj, otherMethod.getName(), otherMethod.getMethod().invoke(obj));
-        if (!shouldInclude) {
-          continue;
+        if (atLeastOneASTNode) {
+          DumpReferenceListToken listToken = new DumpReferenceListToken();
+          listToken.setName(tti.nextName());
+          setMemberAndAdd.accept(listToken);
+          RelationStyle style = applyStyle(listToken);
+          nodes.forEach(element -> {
+            InnerRelationDumpNode inner = new InnerRelationDumpNode().setDumpNode(element);
+            listToken.addInnerRelationDumpNode(inner);
+            inner.applyStyle(style);
+          });
         }
-        Object target = otherMethod.getMethod().invoke(obj);
-        if (target != null) {
-          DumpToken token = null;
-          boolean atLeastOneASTNode = false;
-          if (Iterable.class.isAssignableFrom(target.getClass())) {
-            java.util.List<DumpNode> nodes = new java.util.ArrayList<>();
-            Iterable iterable = (Iterable) target;
-            for (Object element : iterable) {
-              // TODO check if isAstNode for first non-null. if yes, use DumpReferenceListToken, other DumpValueToken
-              DumpNode nodeForElement = transform(tti, element, options.asRelation());
-              nodes.add(nodeForElement);
-              if (nodeForElement != null && nodeForElement.isAstNode()) {
-                atLeastOneASTNode = true;
-              }
-            }
-            if (atLeastOneASTNode) {
-              DumpReferenceListToken listToken = new DumpReferenceListToken();
-              nodes.forEach(element -> {
-                listToken.addInnerRelationDumpNode(new InnerRelationDumpNode().setDumpNode(element));
-              });
-              token = listToken;
-            }
-          }
-          if (!atLeastOneASTNode) {
-            DumpNode targetNode = transform(tti, target, options.asRelation());
-            if (targetNode != null && targetNode.isAstNode()) {
-              token = new DumpReferenceToken().setValue(targetNode);
-            } else {
-              if (getBuildConfig().getIncludeEmptyString() || !target.toString().isEmpty()) {
-                DumpValueToken valueToken = new DumpValueToken();
-                valueToken.setValue(target);
-                token = valueToken;
-              }
-            }
-          }
-          if (token != null) {
-            token.setName(otherMethod.getName());
-            token.setComputed(otherMethod.asTokenMethod().isAttributeMethod());
-            node.addDumpToken(token);
+      }
+      if (!atLeastOneASTNode) {
+        DumpNode targetNode = transform(tti, target, options.asRelation());
+        if (targetNode != null && targetNode.isAstNode()) {
+          DumpReferenceToken token = new DumpReferenceToken().setDumpNode(targetNode);
+          setMemberAndAdd.accept(token);
+          applyStyle(token.asDumpReferenceToken());
+        } else {
+          if (getBuildConfig().getIncludeEmptyString() || !target.toString().isEmpty()) {
+            DumpValueToken valueToken = new DumpValueToken();
+            valueToken.setValue(target);
+            setMemberAndAdd.accept(valueToken);
           }
         }
-      } else {
-        throw new RuntimeException("Unknown other method type " + otherMethod);
       }
     }
-    return node;
+    return true;
   }
 
   private Object DumpAst.catchedInvoke(java.lang.reflect.Method method, Object obj) {
@@ -308,38 +393,103 @@ aspect Transform {
     }
   }
 
+  // === NodeStyle ===
+  DumpNode NodeStyle.node;
+
+  NodeStyle DumpNode.createDefaultStyle() {
+    NodeStyle result = new NodeStyle();
+    result.node = this;
+    result.useSimpleLabelWithHashCode();
+    return result;
+  }
+
   private void DumpAst.applyStyle(DumpNode node) {
-    Object obj = node.getObject();
-    node.setLabel(getBuildConfig().getStyleInformation().getNameMethod().get(obj));
-    node.setBackgroundColor(getBuildConfig().getStyleInformation().getBackgroundColorMethod().get(obj));
-    node.setTextColor(getBuildConfig().getStyleInformation().getTextColorMethod().get(obj));
-    node.setManualStereotypes(getBuildConfig().getStyleInformation().getStereotypeMethod().get(obj));
+    NodeStyle style = node.createDefaultStyle();
+    getPrintConfig().getNodeStyleDefinition().style(node.getObject(), style);
+    node.setLabel(style.getLabel());
+    node.setBackgroundColor(style.getBackgroundColor());
+    node.setTextColor(style.getTextColor());
+    node.setManualStereotypes(style.getStereoTypes());
   }
 
-  // TODO: add new attributes for: {token,child,relation,attribute,nta}Enabled(String parentType, String name). 1) just move implementation into this attribute. 2) add include/exclude on type-level to it.
+  // === RelationStyle ===
+  interface RelationStyleDefinable {
+    // more methods in TransformPlus.jadd
+
+    // attributes
+    BuildConfig buildConfig();
+    boolean isComputed();
+    boolean isContainment();
+    String initialLabel();
+    DumpNode containingDumpNode();
+    DumpNode getDumpNode();
+  }
+  interface RelationStylable<SELF> {
+    // more methods in TransformPlus.jadd
+
+    // tokens
+    String getTextColor();
+    String getLineColor();
+
+    SELF setLabel(String name);
+    SELF setTextColor(String textColor);
+    SELF setLineColor(String lineColor);
+  }
+
+  DumpChildNode implements RelationStyleDefinable, RelationStylable<DumpChildNode>;
+  syn String DumpChildNode.initialLabel() = getLabel();
+  syn boolean DumpChildNode.isComputed() = getComputed();
+  syn boolean DumpChildNode.isContainment() = true;
+
+  syn DumpNode DumpListChildNode.getDumpNode() = null;
+
+  DumpRelation implements RelationStyleDefinable, RelationStylable<DumpRelation>;
+  syn String DumpRelation.initialLabel() = getLabel();
+  syn boolean DumpRelation.isComputed() = false;
+  syn boolean DumpRelation.isContainment() = false;
+
+  syn DumpNode DumpListRelation.getDumpNode() = null;
+
+  InnerDumpNode implements RelationStylable<InnerDumpNode>;
+  InnerRelationDumpNode implements RelationStylable<InnerRelationDumpNode>;
+
+  DumpReferenceListToken implements RelationStyleDefinable, RelationStylable<DumpReferenceListToken>;
+  syn String DumpReferenceListToken.initialLabel() = getLabel();
+  syn boolean DumpReferenceListToken.isComputed() = getComputed();
+  syn boolean DumpReferenceListToken.isContainment() = false;
+  syn DumpNode DumpReferenceListToken.getDumpNode() = null;
+
+  DumpReferenceToken implements RelationStyleDefinable, RelationStylable<DumpReferenceToken>;
+  syn String DumpReferenceToken.initialLabel() = getLabel();
+  syn boolean DumpReferenceToken.isComputed() = getComputed();
+  syn boolean DumpReferenceToken.isContainment() = false;
+
+  private void DumpAst.applyStyle(RelationStylable<?> stylable, RelationStyle style) {
+    stylable.applyStyle(style);
+  }
+  private void DumpAst.applyStyle(DumpToken token, RelationStyle style) {
+    if (token.isDumpReferenceListToken()) {
+      token.asDumpReferenceListToken().applyStyle(style);
+    } else if (token.isDumpReferenceToken()) {
+      token.asDumpReferenceToken().applyStyle(style);
+    }
+    // otherwise ignore
+  }
 
   // --- isTypeEnabled ---
   syn boolean DumpAst.isTypeEnabled(String typeName) {
-    return !matches(getBuildConfig().typeIgnorePattern(), typeName);
+    return !getBuildConfig().typeIgnorePattern().matcher(typeName).matches();
   }
-
-  // --- {typeIgnore,child,token,relation,attribute,nta}Pattern ---
+  // --- typeIgnorePattern ---
   syn java.util.regex.Pattern BuildConfig.typeIgnorePattern() = java.util.regex.Pattern.compile(getTypeIgnorePattern());
-  syn java.util.regex.Pattern PatternCollection.childPattern() = java.util.regex.Pattern.compile(getChildPattern());
-  syn java.util.regex.Pattern PatternCollection.tokenPattern() = java.util.regex.Pattern.compile(getTokenPattern());
-  syn java.util.regex.Pattern PatternCollection.relationPattern() = java.util.regex.Pattern.compile(getRelationPattern());
-  syn java.util.regex.Pattern PatternCollection.attributePattern() = java.util.regex.Pattern.compile(getAttributePattern());
-  syn java.util.regex.Pattern PatternCollection.ntaPattern() = java.util.regex.Pattern.compile(getNonterminalAttributePattern());
-  syn java.util.regex.Pattern TypePatternCollectionMapping.typePattern() = java.util.regex.Pattern.compile(getTypeRegex());
-
-  // --- matches ---
-  static boolean ASTNode.matches(java.util.regex.Pattern p, String input) {
-    return p.matcher(input).matches();
-  }
 
   class TransformationTransferInformation {
     java.util.Map<Object, DumpNode> transformed = new java.util.HashMap<>();
     java.util.Map<DumpNode, Boolean> relationTargetsUnprocessed = new java.util.HashMap<>();
     int nodeCounter = 0;
+
+    String nextName() {
+      return "node" + (nodeCounter++);
+    }
   }
 }
diff --git a/dumpAst.base/src/main/jastadd/TransformPlus.jadd b/dumpAst.base/src/main/jastadd/TransformPlus.jadd
new file mode 100644
index 0000000000000000000000000000000000000000..ff3b8e590f83022ccfb328bc072c700abee832e2
--- /dev/null
+++ b/dumpAst.base/src/main/jastadd/TransformPlus.jadd
@@ -0,0 +1,35 @@
+// more stuff from Transform that is not correctly handled by the IntelliJ plugin
+aspect Transform {
+  interface RelationStyleDefinable {
+    default RelationStyle createDefaultStyle() {
+      return new RelationStyle()
+        .setLabel(initialLabel())
+        .setLineColor(isComputed() ? buildConfig().getStyleInformation().getComputedColor() : "")
+        .setTextColor(isComputed() ? buildConfig().getStyleInformation().getComputedColor() : "");
+    }
+    default RelationStyle getStyle(RelationStyleDefinition relationStyleDefinition) {
+      RelationStyle style = createDefaultStyle();
+      relationStyleDefinition.style(containingDumpNode().getObject(),
+          getDumpNode() != null ? getDumpNode().getObject() : null,
+          isComputed(), isContainment(), style);
+      return style;
+    }
+  }
+
+  interface RelationStylable<SELF> {
+    default boolean needRelationStyling() {
+      return !getLineColor().isEmpty() || !getTextColor().isEmpty();
+    }
+    default void applyStyle(RelationStyle style) {
+      setLabel(style.getLabel());
+      setTextColor(style.getTextColor());
+      setLineColor(style.getLineColor());
+    }
+  }
+
+  private <T extends RelationStyleDefinable & RelationStylable<?>> RelationStyle DumpAst.applyStyle(T styleDefinable) {
+    RelationStyle result = styleDefinable.getStyle(getPrintConfig().getRelationStyleDefinition());
+    styleDefinable.applyStyle(result);
+    return result;
+  }
+}
diff --git a/dumpAst.base/src/main/jastadd/Util.jrag b/dumpAst.base/src/main/jastadd/Util.jrag
index 32c0ab008028cd8fc2b9b3aa132b94a942067f21..873e9b6ba6af303528aa3e53c82965030f3fb5fc 100644
--- a/dumpAst.base/src/main/jastadd/Util.jrag
+++ b/dumpAst.base/src/main/jastadd/Util.jrag
@@ -1,23 +1,4 @@
 aspect Util {
-  // --- find{In,Ex}cludePatternCollection ---
-  syn PatternCollection BuildConfig.findIncludePatternCollection(String typeRegex) {
-    for (TypePatternCollectionMapping mapping : getIncludeTypePatternList()) {
-      if (mapping.getTypeRegex().equals(typeRegex)) {
-        return mapping.getPatternCollection();
-      }
-    }
-    return null;
-  }
-
-  syn PatternCollection BuildConfig.findExcludePatternCollection(String typeRegex) {
-    for (TypePatternCollectionMapping mapping : getExcludeTypePatternList()) {
-      if (mapping.getTypeRegex().equals(typeRegex)) {
-        return mapping.getPatternCollection();
-      }
-    }
-    return null;
-  }
-
   private String DumpAst.titleCase(String s) {
     if (s.isEmpty()) {
       return s;
diff --git a/dumpAst.base/src/main/java/de/tudresden/inf/st/jastadd/dumpAst/ast/DumpBuilder.java b/dumpAst.base/src/main/java/de/tudresden/inf/st/jastadd/dumpAst/ast/DumpBuilder.java
index ed87cf24a45c0d9c8cda43c612c4450080fc8504..937cf6814ce0c39d04d62818407ef6a04490ee23 100644
--- a/dumpAst.base/src/main/java/de/tudresden/inf/st/jastadd/dumpAst/ast/DumpBuilder.java
+++ b/dumpAst.base/src/main/java/de/tudresden/inf/st/jastadd/dumpAst/ast/DumpBuilder.java
@@ -5,6 +5,7 @@ import net.sourceforge.plantuml.FileFormatOption;
 import net.sourceforge.plantuml.SourceStringReader;
 
 import java.io.IOException;
+import java.io.OutputStream;
 import java.io.Writer;
 import java.lang.reflect.InvocationTargetException;
 import java.nio.file.Files;
@@ -13,114 +14,37 @@ import java.util.ResourceBundle;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
 
-import static de.tudresden.inf.st.jastadd.dumpAst.ast.ASTNode.matches;
-
 /**
- * Building a dump.
- * <p>
- *
- * <h3>Inclusion and Exclusion of Types</h3>
- * Types can be only be disabled, see {@link #disableTypes(String, String...)}.
- *
- * <h3>Inclusion and Exclusion of children, tokens and relations</h3>
- * Children, tokens and relations are included by default.
- * This can be changed using exclusions and inclusion, both in general and per-type.
- * They are applied in the following order making later conditions take precedence over the first ones.
- * <ol>
- *   <li>Include everything as default.
- *   <li>Exclude general.
- *   <li>Include per type.
- *   <li>Exclude per type.
- * </ol>
- *
- * <h3>Inclusion and Exclusion of Attributes</h3>
- * Attributes are excluded by default, i.e., not shown.
- * This can be changed using inclusions and exclusions, both in general and per-type.
- * They are applied in the following order making later conditions take precedence over the first ones.
- * <ol>
- *   <li> Exclude everything as default.
- *   <li> Include general.
- *   <li> Exclude per type.
- *   <li> Include per type
- * </ol>
+ * Creating a snapshot of an AST.
  */
 @SuppressWarnings("UnusedReturnValue")
 public class DumpBuilder {
   private final Object target;
   private String packageName;
-  private DumpAst result;
-  private final BuildConfig buildConfig;
-  private final PrintConfig printConfig;
+  private DumpAst dumpAst;
+
+  private boolean built;
 
   protected DumpBuilder(Object target) {
     this.target = target;
-    buildConfig = new BuildConfig();
-    buildConfig.setIncludeChildMethod((parentNode, childNode, contextName) -> {
-      // level 4: excluded for type? -> return no
-      PatternCollection excludeOnType = buildConfig.matchExcludePatternCollection(parentNode.getClass().getSimpleName());
-      if (excludeOnType != null && matches(excludeOnType.childPattern(), contextName)) {
-        return false;
-      }
-      // level 3: included for type? -> return yes
-      PatternCollection includeOnType = buildConfig.matchIncludePatternCollection(parentNode.getClass().getSimpleName());
-      if (includeOnType != null && matches(includeOnType.childPattern(), contextName)) {
-        return true;
-      }
-      // level 2: globally excluded? -> return no
-      // level 1: otherwise return yes
-      return !matches(buildConfig.getGlobalPatternCollection().childPattern(), contextName);
-    });
-    buildConfig.setIncludeRelationMethod((sourceNode, targetNode, roleName) -> {
-      // level 4: excluded for type? -> return no
-      PatternCollection excludeOnType = buildConfig.matchExcludePatternCollection(sourceNode.getClass().getSimpleName());
-      if (excludeOnType != null && matches(excludeOnType.relationPattern(), roleName)) {
-        return false;
-      }
-      // level 3: included for type? -> return yes
-      PatternCollection includeOnType = buildConfig.matchIncludePatternCollection(sourceNode.getClass().getSimpleName());
-      if (includeOnType != null && matches(includeOnType.relationPattern(), roleName)) {
-        return true;
-      }
-      // level 2: globally excluded? -> return no
-      // level 1: otherwise return yes
-      return !matches(buildConfig.getGlobalPatternCollection().relationPattern(), roleName);
-    });
-    buildConfig.setIncludeTokenMethod((node, tokenName, value) -> {
-      // level 4: excluded for type? -> return no
-      PatternCollection excludeOnType = buildConfig.matchExcludePatternCollection(node.getClass().getSimpleName());
-      if (excludeOnType != null && matches(excludeOnType.tokenPattern(), tokenName)) {
-        return false;
-      }
-      // level 3: included for type? -> return yes
-      PatternCollection includeOnType = buildConfig.matchIncludePatternCollection(node.getClass().getSimpleName());
-      if (includeOnType != null && matches(includeOnType.tokenPattern(), tokenName)) {
-        return true;
-      }
-      // level 2: globally excluded? -> return no
-      // level 1: otherwise return yes
-      return !matches(buildConfig.getGlobalPatternCollection().tokenPattern(), tokenName);
-    });
-    buildConfig.setIncludeAttributeMethod((node, attributeName, isNTA, supplier) -> {
-      // level 4: included for type? -> return yes
-      PatternCollection includeOnType = buildConfig.matchIncludePatternCollection(node.getClass().getSimpleName());
-      if (includeOnType != null && matches(isNTA ? includeOnType.ntaPattern() : includeOnType.attributePattern(), attributeName)) {
-        return true;
-      }
-      // level 3: excluded for type? -> return no
-      PatternCollection excludeOnType = buildConfig.matchExcludePatternCollection(node.getClass().getSimpleName());
-      if (excludeOnType != null && matches(isNTA ? excludeOnType.ntaPattern() : excludeOnType.attributePattern(), attributeName)) {
-        return false;
-      }
-      // level 2: globally included? -> return yes
-      // level 1: otherwise return no
-      PatternCollection global = buildConfig.getGlobalPatternCollection();
-      return matches(isNTA ? global.ntaPattern() : global.attributePattern(), attributeName);
-    });
-    buildConfig.setGlobalPatternCollection(new PatternCollection());
-    buildConfig.setStyleInformation(StyleInformation.createDefault());
-    printConfig = new PrintConfig();
-    printConfig.setScale(1);
-    printConfig.setVersion(readVersion());
+    this.built = false;
+    this.dumpAst = new DumpAst();
+    dumpAst.setBuildConfig(new BuildConfig());
+    dumpAst.getBuildConfig().setIncludeChildMethod((parentNode, childNode, contextName) -> true);
+    dumpAst.getBuildConfig().setIncludeRelationMethod((sourceNode, targetNode, roleName) -> true);
+    dumpAst.getBuildConfig().setIncludeTokenMethod((node, tokenName, value) -> true);
+    dumpAst.getBuildConfig().setIncludeAttributeMethod((node, attributeName, isNTA, supplier) -> false);
+    dumpAst.getBuildConfig().setStyleInformation(StyleInformation.createDefault());
+    dumpAst.setPrintConfig(new PrintConfig());
+    dumpAst.getPrintConfig().setScale(1);
+    dumpAst.getPrintConfig().setVersion(readVersion());
+    dumpAst.getPrintConfig().setNodeStyleDefinition((node, style) -> {});
+    dumpAst.getPrintConfig().setRelationStyleDefinition((sourceNode, targetNode, isComputed, isContainment, style) -> {});
+  }
+
+  private DumpBuilder thisWithResetBuilt() {
+    this.built = false;
+    return this;
   }
 
   /**
@@ -130,8 +54,8 @@ public class DumpBuilder {
    * @return this
    */
   public DumpBuilder enableDebug() {
-    buildConfig.setDebug(true);
-    return this;
+    dumpAst.getBuildConfig().setDebug(true);
+    return thisWithResetBuilt();
   }
 
   /**
@@ -141,7 +65,7 @@ public class DumpBuilder {
    */
   public DumpBuilder setPackageName(String packageName) {
     this.packageName = packageName;
-    return this;
+    return thisWithResetBuilt();
   }
 
   /**
@@ -150,8 +74,8 @@ public class DumpBuilder {
    * @return this
    */
   public DumpBuilder includeEmptyStringsOnTokens() {
-    buildConfig.setIncludeEmptyString(true);
-    return this;
+    dumpAst.getBuildConfig().setIncludeEmptyString(true);
+    return thisWithResetBuilt();
   }
 
   /**
@@ -159,8 +83,8 @@ public class DumpBuilder {
    * @return this
    */
   public DumpBuilder enableRelationWithRank() {
-    printConfig.setRelationWithRank(true);
-    return this;
+    dumpAst.getPrintConfig().setRelationWithRank(true);
+    return thisWithResetBuilt();
   }
 
   // --- Types ---
@@ -177,353 +101,42 @@ public class DumpBuilder {
    * @see java.util.regex.Pattern#compile(String)
    */
   public DumpBuilder disableTypes(String regex, String... moreRegexes) {
-    updateRegexes(buildConfig::getTypeIgnorePattern,
-        buildConfig::setTypeIgnorePattern,
+    updateRegexes(dumpAst.getBuildConfig()::getTypeIgnorePattern,
+        dumpAst.getBuildConfig()::setTypeIgnorePattern,
         regex, moreRegexes);
-    return this;
+    return thisWithResetBuilt();
   }
 
   // --- Tokens ---
 
-  public <ASTNODE> DumpBuilder includeTokensWhen(IncludeTokenMethod<ASTNODE> spec) {
-    buildConfig.setIncludeTokenMethod(spec);
-    if (!buildConfig.getGlobalPatternCollection().getTokenPattern().isEmpty()) {
-      System.err.println("Overriding previous filters for tokens");
-    }
-    return this;
-  }
-
-  /**
-   * Exclude tokens and their value if the token name matches at least one of the given regex strings.
-   * <p>
-   * See {@link DumpBuilder} for details on inclusion and exclusion precedence.
-   *
-   * @param regex first pattern to match token names
-   * @param moreRegexes more patterns to match token names
-   * @return this
-   * @see java.util.regex.Pattern#compile(String)
-   */
-  public DumpBuilder excludeTokens(String regex, String... moreRegexes) {
-    updateRegexes(() -> buildConfig.getGlobalPatternCollection().getTokenPattern(),
-        s -> buildConfig.getGlobalPatternCollection().setTokenPattern(s),
-        regex, moreRegexes);
-    return this;
-  }
-
-  /**
-   * Exclude tokens and their value within a type if the token name matches at least one of the given regex strings.
-   * <p>
-   * See {@link DumpBuilder} for details on inclusion and exclusion precedence.
-   *
-   * @param typeRegex pattern to match a nonterminal name
-   * @param regex first pattern to match token names
-   * @param moreRegexes more patterns to match token names
-   * @return this
-   * @see java.util.regex.Pattern#compile(String)
-   */
-  public DumpBuilder excludeTokensFor(String typeRegex, String regex, String... moreRegexes) {
-    PatternCollection collection = findOrCreateExcludePatternCollection(typeRegex);
-    updateRegexes(collection::getTokenPattern,
-        collection::setTokenPattern,
-        regex, moreRegexes);
-    return this;
-  }
-
-  /**
-   * Include tokens (again) and their value within a type if the token name matches at least one of the given regex strings.
-   * <p>
-   * See {@link DumpBuilder} for details on inclusion and exclusion precedence.
-   *
-   * @param typeRegex pattern to match a nonterminal name
-   * @param regex first pattern to match token names
-   * @param moreRegexes more patterns to match token names
-   * @return this
-   * @see java.util.regex.Pattern#compile(String)
-   */
-  public DumpBuilder includeTokensFor(String typeRegex, String regex, String... moreRegexes) {
-    PatternCollection collection = findOrCreateIncludePatternCollection(typeRegex);
-    updateRegexes(collection::getTokenPattern,
-        collection::setTokenPattern,
-        regex, moreRegexes);
-    return this;
+  public <ASTNODE> DumpBuilder includeToken(IncludeTokenMethod<ASTNODE> spec) {
+    dumpAst.getBuildConfig().setIncludeTokenMethod(spec);
+    return thisWithResetBuilt();
   }
 
   // --- Children ---
 
-  public <ASTNODE> DumpBuilder includeChildWhen(IncludeChildMethod<ASTNODE> spec) {
-    buildConfig.setIncludeChildMethod(spec);
-    if (!buildConfig.getGlobalPatternCollection().getChildPattern().isEmpty()) {
-      System.err.println("Overriding previous filters for children");
-    }
-    return this;
-  }
-
-  /**
-   * Exclude every child whose name (i.e., context) matches at least on of the given regex strings.
-   * This means, that the complete object and its (transitive) children will never be included in any output.
-   * <p>
-   * See {@link DumpBuilder} for details on inclusion and exclusion precedence.
-   *
-   * @param regex first pattern to match child name
-   * @param moreRegexes more patterns to match child names
-   * @return this
-   * @see java.util.regex.Pattern#compile(String)
-   */
-  public DumpBuilder excludeChildren(String regex, String... moreRegexes) {
-    updateRegexes(() -> buildConfig.getGlobalPatternCollection().getChildPattern(),
-        s -> buildConfig.getGlobalPatternCollection().setChildPattern(s),
-        regex, moreRegexes);
-    return this;
-  }
-
-  /**
-   * Exclude every child within a type whose name (i.e., context) matches at least on of the given regex strings.
-   * This means, that the complete object and its (transitive) children will never be included in any output.
-   * <p>
-   * See {@link DumpBuilder} for details on inclusion and exclusion precedence.
-   *
-   * @param typeRegex pattern to match a nonterminal name
-   * @param regex first pattern to match child name
-   * @param moreRegexes more patterns to match child names
-   * @return this
-   * @see java.util.regex.Pattern#compile(String)
-   */
-  public DumpBuilder excludeChildrenFor(String typeRegex, String regex, String... moreRegexes) {
-    PatternCollection collection = findOrCreateExcludePatternCollection(typeRegex);
-    updateRegexes(collection::getChildPattern,
-        collection::setChildPattern,
-        regex, moreRegexes);
-    return this;
-  }
-
-  /**
-   * Include every child (again) within a type whose name (i.e., context) matches at least on of the given regex strings.
-   * This means, that the complete object and its (transitive) children will never be included in any output.
-   * <p>
-   * See {@link DumpBuilder} for details on inclusion and exclusion precedence.
-   *
-   * @param typeRegex pattern to match a nonterminal name
-   * @param regex first pattern to match child name
-   * @param moreRegexes more patterns to match child names
-   * @return this
-   * @see java.util.regex.Pattern#compile(String)
-   */
-  public DumpBuilder includeChildrenFor(String typeRegex, String regex, String... moreRegexes) {
-    PatternCollection collection = findOrCreateIncludePatternCollection(typeRegex);
-    updateRegexes(collection::getChildPattern,
-        collection::setChildPattern,
-        regex, moreRegexes);
-    return this;
+  public <ASTNODE> DumpBuilder includeChild(IncludeChildMethod<ASTNODE> spec) {
+    dumpAst.getBuildConfig().setIncludeChildMethod(spec);
+    return thisWithResetBuilt();
   }
 
   // --- Attributes ---
 
-  public <ASTNODE> DumpBuilder includeAttributeWhen(IncludeAttributeMethod<ASTNODE> spec) {
-    buildConfig.setIncludeAttributeMethod(spec);
-    if (!buildConfig.getGlobalPatternCollection().getAttributePattern().isEmpty()) {
-      System.err.println("Overriding previous filters for attributes");
-    }
-    return this;
-  }
-
-  /**
-   * Include attributes (as tokens) and their value if the attribute name matches at least on of the given regex strings.
-   * <p>
-   * See {@link DumpBuilder} for details on inclusion and exclusion precedence.
-   *
-   * @param regex first pattern to match attribute name
-   * @param moreRegexes more patterns to match attribute names
-   * @return this
-   * @see java.util.regex.Pattern#compile(String)
-   */
-  public DumpBuilder includeAttributes(String regex, String... moreRegexes) {
-    updateRegexes(() -> buildConfig.getGlobalPatternCollection().getAttributePattern(),
-        s -> buildConfig.getGlobalPatternCollection().setAttributePattern(s),
-        regex, moreRegexes);
-    return this;
-  }
-
-  /**
-   * Include attributes within a type (as tokens) and their value if the attribute name matches at least on of the given regex strings.
-   * <p>
-   * See {@link DumpBuilder} for details on inclusion and exclusion precedence.
-   *
-   * @param typeRegex pattern to match a nonterminal name
-   * @param regex first pattern to match attribute name
-   * @param moreRegexes more patterns to match attribute names
-   * @return this
-   * @see java.util.regex.Pattern#compile(String)
-   */
-  public DumpBuilder includeAttributesFor(String typeRegex, String regex, String... moreRegexes) {
-    PatternCollection collection = findOrCreateIncludePatternCollection(typeRegex);
-    updateRegexes(collection::getAttributePattern,
-        collection::setAttributePattern,
-        regex, moreRegexes);
-    return this;
-  }
-
-  /**
-   * Exclude attributes within a type (as tokens) and their value (again) if the attribute name matches at least on of the given regex strings.
-   * <p>
-   * See {@link DumpBuilder} for details on inclusion and exclusion precedence.
-   *
-   * @param typeRegex pattern to match a nonterminal name
-   * @param regex first pattern to match attribute name
-   * @param moreRegexes more patterns to match attribute names
-   * @return this
-   * @see java.util.regex.Pattern#compile(String)
-   */
-  public DumpBuilder excludeAttributesFor(String typeRegex, String regex, String... moreRegexes) {
-    PatternCollection collection = findOrCreateExcludePatternCollection(typeRegex);
-    updateRegexes(collection::getAttributePattern,
-        collection::setAttributePattern,
-        regex, moreRegexes);
-    return this;
-  }
-
-  // --- Nonterminal-Attributes ---
-
-  /**
-   * Includes nonterminal-attributes (as children) and their values if
-   * their attribute name matches at least on of the given regex strings.
-   * <br>
-   * <b>Note</b>: A leading "get" and a trailing "List" in the name will be removed prior to matching.
-   * Thus, it should not be contained in the regex either.
-   * <p>
-   * See {@link DumpBuilder} for details on inclusion and exclusion precedence.
-   *
-   * @param regex first pattern to match attribute name
-   * @param moreRegexes more patterns to match attribute names
-   * @return this
-   * @see java.util.regex.Pattern#compile(String)
-   */
-  public DumpBuilder includeNonterminalAttributes(String regex, String... moreRegexes) {
-    updateRegexes(() -> buildConfig.getGlobalPatternCollection().getNonterminalAttributePattern(),
-        s -> buildConfig.getGlobalPatternCollection().setNonterminalAttributePattern(s),
-        regex, moreRegexes);
-    return this;
-  }
-
-  /**
-   * Includes nonterminal-attributes (as children) and their values within a type if
-   * their attribute name matches at least on of the given regex strings.
-   * <br>
-   * <b>Note</b>: A leading "get" and a trailing "List" in the name will be removed prior to matching.
-   * Thus, it should not be contained in the regex either.
-   * <p>
-   * See {@link DumpBuilder} for details on inclusion and exclusion precedence.
-   *
-   * @param typeRegex pattern to match a nonterminal name
-   * @param regex first pattern to match attribute name
-   * @param moreRegexes more patterns to match attribute names
-   * @return this
-   * @see java.util.regex.Pattern#compile(String)
-   */
-  public DumpBuilder includeNonterminalAttributesFor(String typeRegex, String regex, String... moreRegexes) {
-    PatternCollection collection = findOrCreateIncludePatternCollection(typeRegex);
-    updateRegexes(collection::getNonterminalAttributePattern,
-        collection::setNonterminalAttributePattern,
-        regex, moreRegexes);
-    return this;
-  }
-
-  /**
-   * Excludes nonterminal-attributes (as children) and their values (again) within a type if
-   * their attribute name matches at least on of the given regex strings.
-   * <br>
-   * <b>Note</b>: A leading "get" and a trailing "List" in the name will be removed prior to matching.
-   * Thus, it should not be contained in the regex either.
-   * <p>
-   * See {@link DumpBuilder} for details on inclusion and exclusion precedence.
-   *
-   * @param typeRegex pattern to match a nonterminal name
-   * @param regex first pattern to match attribute name
-   * @param moreRegexes more patterns to match attribute names
-   * @return this
-   * @see java.util.regex.Pattern#compile(String)
-   */
-  public DumpBuilder excludeNonterminalAttributesFor(String typeRegex, String regex, String... moreRegexes) {
-    PatternCollection collection = findOrCreateExcludePatternCollection(typeRegex);
-    updateRegexes(collection::getNonterminalAttributePattern,
-        collection::setNonterminalAttributePattern,
-        regex, moreRegexes);
-    return this;
+  public <ASTNODE> DumpBuilder includeAttribute(IncludeAttributeMethod<ASTNODE> spec) {
+    dumpAst.getBuildConfig().setIncludeAttributeMethod(spec);
+    return thisWithResetBuilt();
   }
 
   // --- Relations ---
 
-  public <ASTNODE> DumpBuilder includeRelationsWhen(IncludeRelationMethod<ASTNODE> spec) {
-    buildConfig.setIncludeRelationMethod(spec);
-    if (!buildConfig.getGlobalPatternCollection().getRelationPattern().isEmpty()) {
-      System.err.println("Overriding previous filters for relations");
-    }
-    return this;
-  }
-
-  /**
-   * Exclude every relation whose role-name matches at least on of the given regex strings.
-   * This means two things: a) the relation to any potential target object(s) is never shown, and b) the target
-   * object(s) are not shown unless they are reachable by another relation or by containment.
-   * <p>
-   * See {@link DumpBuilder} for details on inclusion and exclusion precedence.
-   *
-   * @param regex first pattern to match child name
-   * @param moreRegexes more patterns to match child names
-   * @return this
-   * @see java.util.regex.Pattern#compile(String)
-   */
-  public DumpBuilder excludeRelations(String regex, String... moreRegexes) {
-    updateRegexes(() -> buildConfig.getGlobalPatternCollection().getRelationPattern(),
-        s -> buildConfig.getGlobalPatternCollection().setRelationPattern(s),
-        regex, moreRegexes);
-    return this;
-  }
-
-  /**
-   * Exclude every relation within a type whose role-name matches at least on of the given regex strings.
-   * This means two things: a) the relation to any potential target object(s) is never shown, and b) the target
-   * object(s) are not shown unless they are reachable by another relation or by containment.
-   * <p>
-   * See {@link DumpBuilder} for details on inclusion and exclusion precedence.
-   *
-   * @param typeRegex pattern to match a nonterminal name
-   * @param regex first pattern to match child name
-   * @param moreRegexes more patterns to match child names
-   * @return this
-   * @see java.util.regex.Pattern#compile(String)
-   */
-  public DumpBuilder excludeRelationsFor(String typeRegex, String regex, String... moreRegexes) {
-    PatternCollection collection = findOrCreateExcludePatternCollection(typeRegex);
-    updateRegexes(collection::getRelationPattern,
-        collection::setRelationPattern,
-        regex, moreRegexes);
-    return this;
-  }
-
-  /**
-   * Include every relation (again) within a type whose role-name matches at least on of the given regex strings.
-   * This means two things: a) the relation to any potential target object(s) is never shown, and b) the target
-   * object(s) are not shown unless they are reachable by another relation or by containment.
-   * <p>
-   * See {@link DumpBuilder} for details on inclusion and exclusion precedence.
-   *
-   * @param typeRegex pattern to match a nonterminal name
-   * @param regex first pattern to match child name
-   * @param moreRegexes more patterns to match child names
-   * @return this
-   * @see java.util.regex.Pattern#compile(String)
-   */
-  public DumpBuilder includeRelationsFor(String typeRegex, String regex, String... moreRegexes) {
-    PatternCollection collection = findOrCreateIncludePatternCollection(typeRegex);
-    updateRegexes(collection::getRelationPattern,
-        collection::setRelationPattern,
-        regex, moreRegexes);
-    return this;
+  public <ASTNODE> DumpBuilder includeRelation(IncludeRelationMethod<ASTNODE> spec) {
+    dumpAst.getBuildConfig().setIncludeRelationMethod(spec);
+    return thisWithResetBuilt();
   }
 
   // --- Settings ---
 
-
   /**
    * Omit children that are <code>null</code> (in a full AST, there are no such nodes).
    *
@@ -531,8 +144,8 @@ public class DumpBuilder {
    * @return this
    */
   public DumpBuilder excludeNullNodes() {
-    buildConfig.setExcludeNullNodes(true);
-    return this;
+    dumpAst.getBuildConfig().setExcludeNullNodes(true);
+    return thisWithResetBuilt();
   }
 
   /**
@@ -542,8 +155,8 @@ public class DumpBuilder {
    * @return this
    */
   public DumpBuilder includeNullNodes() {
-    buildConfig.setExcludeNullNodes(false);
-    return this;
+    dumpAst.getBuildConfig().setExcludeNullNodes(false);
+    return thisWithResetBuilt();
   }
 
   /**
@@ -552,8 +165,8 @@ public class DumpBuilder {
    * @return this
    */
   public DumpBuilder customPreamble(String option) {
-    printConfig.addHeader(new Header(option));
-    return this;
+    dumpAst.getPrintConfig().addHeader(new Header(option));
+    return thisWithResetBuilt();
   }
 
   /**
@@ -564,7 +177,7 @@ public class DumpBuilder {
    */
   public DumpBuilder skinParam(SkinParamStringSetting setting, String value) {
     customPreamble("skinparam " + setting.toString() + " " + value);
-    return this;
+    return thisWithResetBuilt();
   }
 
   /**
@@ -575,63 +188,29 @@ public class DumpBuilder {
    */
   public DumpBuilder skinParam(SkinParamBooleanSetting setting, boolean value) {
     customPreamble("skinparam " + setting.toString() + " " + value);
-    return this;
+    return thisWithResetBuilt();
   }
 
   /**
-   * Set the method defining, what name a node has (default: {@code n -> n == null ? "null" : n.getClass().getSimpleName() + "@" + Integer.toHexString(n.hashCode())}).
-   *
-   * <p>Example:<br>
-   * {@code builder.<ASTNode<?>>setNameMethod(n -> n.isA() ? "A" : "Not A")}
-   * @param nameMethod a style method
-   * @param <ASTNODE> the type of ASTNode
+   * Set the styling definition for all nodes to change appearance, e.g., of background color.
+   * @param styleDefinition the new style definition
    * @return this
+   * @param <ASTNODE> type of AstNode
    */
-  public <ASTNODE> DumpBuilder setNameMethod(StyleMethod<ASTNODE> nameMethod) {
-    buildConfig.getStyleInformation().setNameMethod(nameMethod);
-    return this;
+  public <ASTNODE> DumpBuilder nodeStyle(NodeStyleDefinition<ASTNODE> styleDefinition) {
+    dumpAst.getPrintConfig().setNodeStyleDefinition(styleDefinition);
+    return thisWithResetBuilt();
   }
 
   /**
-   * Set the method defining, what background color a node has (default: {@code n -> ""}).
-   *
-   * <p>Example:<br>
-   * {@code builder.<ASTNode<?>>setBackgroundColorMethod(n -> n.isA() ? "red" : "blue")}
-   * @param colorMethod a style method
-   * @param <ASTNODE> the type of ASTNode
+   * Set the styling definition for all relations to change appearance, e.g., of its label.
+   * @param styleDefinition the new style definition
    * @return this
+   * @param <ASTNODE> type of AstNode
    */
-  public <ASTNODE> DumpBuilder setBackgroundColorMethod(StyleMethod<ASTNODE> colorMethod) {
-    buildConfig.getStyleInformation().setBackgroundColorMethod(colorMethod);
-    return this;
-  }
-
-  /**
-   * Set the method defining, what text color a node has (default: {@code n -> ""}).
-   *
-   * <p>Example:<br>
-   * {@code builder.<ASTNode<?>>setTextColorMethod(n -> n.isA() ? "black" : "white")}
-   * @param colorMethod a style method
-   * @param <ASTNODE> the type of ASTNode
-   * @return this
-   */
-  public <ASTNODE> DumpBuilder setTextColorMethod(StyleMethod<ASTNODE> colorMethod) {
-    buildConfig.getStyleInformation().setTextColorMethod(colorMethod);
-    return this;
-  }
-
-  /**
-   * Set the method defining, what stereotype a node has (default: {@code n -> ""}).
-   *
-   * <p>Example:<br>
-   * {@code builder.<ASTNode<?>>setStereotypeMethod(n -> n.isA() ? "MyStereoType" : "")}
-   * @param stereotypeMethod a style method
-   * @param <ASTNODE> the type of ASTNode
-   * @return this
-   */
-  public <ASTNODE> DumpBuilder setStereotypeMethod(StyleMethod<ASTNODE> stereotypeMethod) {
-    buildConfig.getStyleInformation().setStereotypeMethod(stereotypeMethod);
-    return this;
+  public <ASTNODE> DumpBuilder relationStyle(RelationStyleDefinition<ASTNODE> styleDefinition) {
+    dumpAst.getPrintConfig().setRelationStyleDefinition(styleDefinition);
+    return thisWithResetBuilt();
   }
 
   /**
@@ -641,8 +220,8 @@ public class DumpBuilder {
    * @see <a href="https://plantuml.com/en/color">https://plantuml.com/en/color</a>
    */
   public DumpBuilder setComputedColor(String color) {
-    buildConfig.getStyleInformation().setComputedColor(color);
-    return this;
+    dumpAst.getBuildConfig().getStyleInformation().setComputedColor(color);
+    return thisWithResetBuilt();
   }
 
   /**
@@ -651,8 +230,8 @@ public class DumpBuilder {
    * @return this
    */
   public DumpBuilder setScale(double value) {
-    printConfig.setScale(value);
-    return this;
+    dumpAst.getPrintConfig().setScale(value);
+    return thisWithResetBuilt();
   }
 
   /**
@@ -660,8 +239,8 @@ public class DumpBuilder {
    * @return this
    */
   public DumpBuilder orderChildren() {
-    printConfig.setOrderChildren(true);
-    return this;
+    dumpAst.getPrintConfig().setOrderChildren(true);
+    return thisWithResetBuilt();
   }
 
   // --- Dump methods ---
@@ -690,6 +269,16 @@ public class DumpBuilder {
     }
   }
 
+  /**
+   * Return content as intermediate data structure used by the template engine.
+   *
+   * @return the intermediate data structure used by the template engine
+   * @throws TransformationException if {@link DumpAst#transform(TransformationTransferInformation, Object) transform} was not successful
+   */
+  public String dumpAsYaml(boolean prependCreationComment) throws TransformationException {
+    return build().printYaml(prependCreationComment);
+  }
+
   /**
    * Write out content as intermediate data structure used by the template engine.
    *
@@ -705,6 +294,17 @@ public class DumpBuilder {
     }
   }
 
+  /**
+   * Write out content as PNG image generated by plantuml.
+   *
+   * @param os a stream to write to
+   * @throws IOException if an I/O error happened during writing in that stream
+   * @throws TransformationException if {@link DumpAst#transform(TransformationTransferInformation, Object) transform} was not successful
+   */
+  public void dumpAsPNG(OutputStream os) throws TransformationException, IOException {
+    dumpAs(os, FileFormat.PNG);
+  }
+
   /**
    * Write out content as PNG image generated by plantuml.
    *
@@ -716,6 +316,17 @@ public class DumpBuilder {
     dumpAs(destination, FileFormat.PNG);
   }
 
+  /**
+   * Write out content as SVG image generated by plantuml.
+   *
+   * @param os a stream to write to
+   * @throws IOException if an I/O error happened during writing in that stream
+   * @throws TransformationException if {@link DumpAst#transform(TransformationTransferInformation, Object) transform} was not successful
+   */
+  public void dumpAsSVG(OutputStream os) throws TransformationException, IOException {
+    dumpAs(os, FileFormat.SVG);
+  }
+
   /**
    * Write out content as SVG image generated by plantuml.
    *
@@ -727,6 +338,20 @@ public class DumpBuilder {
     dumpAs(destination, FileFormat.SVG);
   }
 
+  /**
+   * Write out content as PDF image generated by plantuml.
+   *
+   * <br>
+   * <b>Note:</b> This requires additional dependencies, see <a href="https://plantuml.com/pdf">https://plantuml.com/pdf</a>
+   *
+   * @param os a stream to write to
+   * @throws IOException if an I/O error happened during writing in that stream
+   * @throws TransformationException if {@link DumpAst#transform(TransformationTransferInformation, Object) transform} was not successful
+   */
+  public void dumpAsPDF(OutputStream os) throws TransformationException, IOException {
+    dumpAs(os, FileFormat.PDF);
+  }
+
   /**
    * Write out content as PDF generated by plantuml.
    *
@@ -747,11 +372,16 @@ public class DumpBuilder {
     reader.outputImage(Files.newOutputStream(destination), new FileFormatOption(fileFormat));
   }
 
+  private void dumpAs(OutputStream os, FileFormat fileFormat) throws IOException, TransformationException {
+    String content = build().toPlantUml();
+    SourceStringReader reader = new SourceStringReader(content);
+    reader.outputImage(os, new FileFormatOption(fileFormat));
+  }
+
   // --- Helper methods ---
 
   protected DumpAst build() throws TransformationException {
-    if (result == null) {
-      result = new DumpAst();
+    if (!built) {
       final String packageNameToUse;
       if (this.packageName != null) {
         packageNameToUse = this.packageName;
@@ -762,41 +392,15 @@ public class DumpBuilder {
           packageNameToUse = this.target.getClass().getPackage().getName();
         }
       }
-      result.setPackageName(packageNameToUse);
-      result.setBuildConfig(this.buildConfig);
-      result.setPrintConfig(this.printConfig);
+      dumpAst.setPackageName(packageNameToUse);
       try {
-        result.transform(new TransformationTransferInformation(), this.target);
+        dumpAst.transform(new TransformationTransferInformation(), this.target);
       } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
-        result = null;
+        dumpAst = null;
         throw new TransformationException(e);
       }
     }
-    return result;
-  }
-
-  private PatternCollection findOrCreateIncludePatternCollection(String typeRegex) {
-    PatternCollection result = buildConfig.findIncludePatternCollection(typeRegex);
-    if (result == null) {
-      TypePatternCollectionMapping mapping = new TypePatternCollectionMapping();
-      mapping.setTypeRegex(typeRegex);
-      result = new PatternCollection();
-      mapping.setPatternCollection(result);
-      buildConfig.addIncludeTypePattern(mapping);
-    }
-    return result;
-  }
-
-  private PatternCollection findOrCreateExcludePatternCollection(String typeRegex) {
-    PatternCollection result = buildConfig.findExcludePatternCollection(typeRegex);
-    if (result == null) {
-      TypePatternCollectionMapping mapping = new TypePatternCollectionMapping();
-      mapping.setTypeRegex(typeRegex);
-      result = new PatternCollection();
-      mapping.setPatternCollection(result);
-      buildConfig.addExcludeTypePattern(mapping);
-    }
-    return result;
+    return dumpAst;
   }
 
   private void updateRegexes(Supplier<String> getter, Consumer<String> setter, String regex, String... moreRegexes) {
diff --git a/dumpAst.base/src/main/resources/RelationStyle.mustache b/dumpAst.base/src/main/resources/RelationStyle.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..b48a25121e6fa8e4d31d1eaecbb29bb5874e444f
--- /dev/null
+++ b/dumpAst.base/src/main/resources/RelationStyle.mustache
@@ -0,0 +1 @@
+{{#TextColor}}#text:{{{TextColor}}}{{/TextColor}}{{#LineColor}}{{#TextColor}};{{/TextColor}}{{^TextColor}}#{{/TextColor}}line:{{{LineColor}}}{{/LineColor}}
\ No newline at end of file
diff --git a/dumpAst.base/src/main/resources/dumpAst.mustache b/dumpAst.base/src/main/resources/dumpAst.mustache
index da993c1d4d995ea2f8b15f4129207be3bc14d08a..5785dd7460dc3c35707388cb61c515679d27498d 100644
--- a/dumpAst.base/src/main/resources/dumpAst.mustache
+++ b/dumpAst.base/src/main/resources/dumpAst.mustache
@@ -11,57 +11,64 @@ skinparam object<<NTA>> {
 hide <<NTA>> stereotype
 
 {{#PrintConfig}}
-scale {{{scale}}}
+scale {{{Scale}}}
   {{#Headers}}
-{{{value}}}
+{{{Value}}}
   {{/Headers}}
 {{/PrintConfig}}
 
 {{#DumpNodes}}
-  {{^invisible}}
+  {{^Invisible}}
     {{#isNull}}
-object "null" as {{{name}}}<<null>>
+object "null" as {{{Name}}}<<null>>
     {{/isNull}}
+    {{#isEmpty}}
+object "[]" as {{{Name}}}<<null>>
+    {{/isEmpty}}
     {{#isAstNode}}
-object "{{{labelAndTextColor}}}" as {{{name}}} {{{stereotypeList}}} {{#backgroundColor}}#{{{backgroundColor}}}{{/backgroundColor}} {
+object "{{{labelAndTextColor}}}" as {{{Name}}} {{{stereotypeList}}} {{#BackgroundColor}}#{{{BackgroundColor}}}{{/BackgroundColor}} {
       {{#DumpTokens}}
         {{#isDumpValueToken}}
-  {{{label}}} = {{{value}}}
+  {{{label}}} = {{{Value}}}
         {{/isDumpValueToken}}
       {{/DumpTokens}}
 }
     {{/isAstNode}}
-  {{/invisible}}
+  {{/Invisible}}
 {{/DumpNodes}}
 
 {{#DumpNodes}}
   {{#DumpTokens}}
-    {{^invisible}}
+    {{^Invisible}}
       {{#isList}}
+circle " " as {{{Name}}}
+{{{outerNodeName}}} .-[norank]-> {{{Name}}} {{#needRelationStyling}}{{>RelationStyle}}{{/needRelationStyling}} : "{{{label}}}"
         {{#InnerRelationDumpNode}}
           {{#bothVisible}}
-{{{outerNodeName}}} .[#black{{#computed}},#{{{computedColor}}}{{/computed}}{{#innerNotNull}},norank{{/innerNotNull}}].> {{{innerNodeName}}} : {{{label}}}
+{{{Name}}} .{{#innerNotNullOrEmpty}}[norank]{{/innerNotNullOrEmpty}}.> {{{innerNodeName}}} {{#needRelationStyling}}{{>RelationStyle}}{{/needRelationStyling}} : "{{{label}}}"
           {{/bothVisible}}
         {{/InnerRelationDumpNode}}
       {{/isList}}
       {{^isList}}
         {{^isDumpValueToken}}
-{{{outerNodeName}}} .[#black{{#computed}},#{{{computedColor}}}{{/computed}}{{#innerNotNull}},norank{{/innerNotNull}}].> {{{innerNodeName}}} : {{{label}}}
+{{{outerNodeName}}} .{{#innerNotNullOrEmpty}}[norank]{{/innerNotNullOrEmpty}}.> {{{innerNodeName}}} {{#needRelationStyling}}{{>RelationStyle}}{{/needRelationStyling}} : "{{{label}}}"
         {{/isDumpValueToken}}
       {{/isList}}
-    {{/invisible}}
+    {{/Invisible}}
   {{/DumpTokens}}
   {{#DumpChildNodes}}
     {{#isList}}
+circle " " as {{{Name}}}
+{{{outerNodeName}}} *-- {{{Name}}} {{#needRelationStyling}}{{>RelationStyle}}{{/needRelationStyling}} : "{{{label}}}"
       {{#InnerDumpNodes}}
         {{#bothVisible}}
-{{{outerNodeName}}} *-{{#computed}}[#{{{computedColor}}}]{{/computed}}- {{{innerNodeName}}} : {{{label}}}
+{{{Name}}} -- {{{innerNodeName}}} {{#needRelationStyling}}{{>RelationStyle}}{{/needRelationStyling}} : "{{{label}}}"
         {{/bothVisible}}
       {{/InnerDumpNodes}}
     {{/isList}}
     {{^isList}}
       {{#bothVisible}}
-{{{outerNodeName}}} *-{{#computed}}[#{{{computedColor}}}]{{/computed}}- {{{innerNodeName}}} : {{{label}}}
+{{{outerNodeName}}} *-- {{{innerNodeName}}} {{#needRelationStyling}}{{>RelationStyle}}{{/needRelationStyling}} : "{{{label}}}"
       {{/bothVisible}}
     {{/isList}}
   {{/DumpChildNodes}}
@@ -69,36 +76,36 @@ object "{{{labelAndTextColor}}}" as {{{name}}} {{{stereotypeList}}} {{#backgroun
     {{#isList}}
       {{#InnerRelationDumpNode}}
         {{#bothVisible}}
-{{{outerNodeName}}} {{#bidirectional}}<{{/bidirectional}}-{{#innerNotNull}}[norank]{{/innerNotNull}}-> {{{innerNodeName}}} : {{{label}}}
+{{{outerNodeName}}} {{#Bidirectional}}<{{/Bidirectional}}-{{#innerNotNullOrEmpty}}[norank]{{/innerNotNullOrEmpty}}-> {{{innerNodeName}}} {{#needRelationStyling}}{{>RelationStyle}}{{/needRelationStyling}} : "{{{label}}}"
         {{/bothVisible}}
       {{/InnerRelationDumpNode}}
     {{/isList}}
     {{^isList}}
       {{#bothVisible}}
-{{{outerNodeName}}} {{#bidirectional}}<{{/bidirectional}}-{{#innerNotNull}}[norank]{{/innerNotNull}}-> {{{innerNodeName}}} : {{{label}}}
+{{{outerNodeName}}} {{#Bidirectional}}<{{/Bidirectional}}-{{#innerNotNullOrEmpty}}[norank]{{/innerNotNullOrEmpty}}-> {{{innerNodeName}}} {{#needRelationStyling}}{{>RelationStyle}}{{/needRelationStyling}} : "{{{label}}}"
       {{/bothVisible}}
     {{/isList}}
   {{/DumpRelations}}
-  {{^invisible}}
+  {{^Invisible}}
     {{#InvisiblePath}}
       {{#InnerRelationDumpNode}}
 {{{outerNodeName}}} o.. {{{innerNodeName}}}
       {{/InnerRelationDumpNode}}
     {{/InvisiblePath}}
-  {{/invisible}}
-  {{#PrintConfig}}{{#orderChildren}}
+  {{/Invisible}}
+  {{#PrintConfig}}{{#OrderChildren}}
       {{#myChildren}}
           {{#hasSuccessor}}
-{{{name}}} -[hidden]right-> {{#successor}}{{{name}}}{{/successor}}
+{{{Name}}} -[hidden]right-> {{#successor}}{{{Name}}}{{/successor}}
           {{/hasSuccessor}}
       {{/myChildren}}
-  {{/orderChildren}}{{/PrintConfig}}
+  {{/OrderChildren}}{{/PrintConfig}}
 {{/DumpNodes}}
 {{#PrintConfig}}
   {{#debug}}
 legend right
   %date()
-  dumpAst: {{{version}}}
+  dumpAst: {{{Version}}}
   plantuml: %version()
 endlegend
   {{/debug}}
diff --git a/dumpAst.base/src/main/resources/dumpAstVersion.properties b/dumpAst.base/src/main/resources/dumpAstVersion.properties
index 810cb294c4f21dbbf55024b562596a4775489dbd..b5c044f7e5bf4da4dd7efc9699f546899abe82ff 100644
--- a/dumpAst.base/src/main/resources/dumpAstVersion.properties
+++ b/dumpAst.base/src/main/resources/dumpAstVersion.properties
@@ -1,2 +1,2 @@
-#Thu Sep 08 16:46:11 CEST 2022
-version=2.0.0
+#Tue Nov 22 10:54:37 CET 2022
+version=3.0.1
diff --git a/dumpAst.prototyping/src/main/jastadd/featureTest.relast b/dumpAst.prototyping/src/main/jastadd/featureTest.relast
index 7e108fe9f1949e8bdffd152e20c379b3635f14cb..003417c4f9a9a6e1c5b8f1bb101a43661ccecd45 100644
--- a/dumpAst.prototyping/src/main/jastadd/featureTest.relast
+++ b/dumpAst.prototyping/src/main/jastadd/featureTest.relast
@@ -24,3 +24,9 @@ T3 : AbstractT ;
 rel AbstractT.oneA -> A ;
 rel AbstractT.maybeA? -> A ;
 rel AbstractT.manyA* -> A ;
+
+OtherRoot : Nameable ::= E* F*;
+E : Nameable ;
+F : Nameable ;
+
+rel E.myF* <-> F.myE* ;
diff --git a/dumpAst.prototyping/src/main/java/de/tudresden/inf/st/jastadd/featureTest/FeatureTestMain.java b/dumpAst.prototyping/src/main/java/de/tudresden/inf/st/jastadd/featureTest/FeatureTestMain.java
index 5e758f933eedc650948ffca0992da3df19f9332b..bebca7d1cfa4301d5231bf27611d1af6c5672421 100644
--- a/dumpAst.prototyping/src/main/java/de/tudresden/inf/st/jastadd/featureTest/FeatureTestMain.java
+++ b/dumpAst.prototyping/src/main/java/de/tudresden/inf/st/jastadd/featureTest/FeatureTestMain.java
@@ -9,6 +9,7 @@ import org.jastadd.featureTest.ast.*;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.Arrays;
 
 /**
  * Main class of feature test.
@@ -18,44 +19,78 @@ import java.nio.file.Paths;
 public class FeatureTestMain {
 
   public static void main(String[] args) throws IOException, TransformationException {
+    new FeatureTestMain().run(args);
+  }
+
+  private void run(String[] args) throws TransformationException, IOException {
+    String mode = args.length == 0 ? "default" : args[0];
+    System.out.println(Arrays.toString(args) + " -> " + mode);
+
+    final DumpBuilder builder;
+
+    switch (mode) {
+      case "default":
+      case "withRoot":
+        builder = withRoot();
+        break;
+      case "withOtherRoot":
+        builder = withOtherRoot();
+        break;
+      default:
+        throw new UnsupportedOperationException("Unknown mode: " + mode);
+    }
+
+    Path pathToYaml = Paths.get("featureTest.yml");
+    Path pathToPng = Paths.get("featureTest.png");
+    Path pathToSvg = Paths.get("featureTest.svg");
+
+    builder.dumpAsYaml(pathToYaml, true);
+    builder.dumpAsPNG(pathToPng);
+    builder.dumpAsSVG(pathToSvg);
+    builder.dumpAsSource(Paths.get("featureTest.puml"));
+  }
+
+  private DumpBuilder withRoot() {
     Root root = new Root();
     root.setName("Root1");
     A a = new A().setName("A2");
     a.setB(new B().setName("B2.1"));
+    a.setD(new D());
 //    a.setMyC(new C().setName("C2.1"));
-//    B b1 = new B().setName("B3").setOtherValue("some long text");
+    B b1 = new B().setName("B3").setOtherValue("some long text");
 //    C c = new C().setName("C4");
 //    c.setA(new A().setName("A4.1").setB(new B().setName("B4.1.1")));
 //    c.setRawReference(a);
 //    b1.setOneA(a);
-//    B b2 = new B().setName("B5").setOtherValue("#ff00ff");
+    B b2 = new B().setName("B5").setOtherValue("#ff00ff");
 //    C myC = new C().setName("C6");
 //    c.setA(new A().setName("A6.1").setB(new B().setName("B6.1.1")));
 //    a.setMyC(myC);
     root.setA(a);
-//    root.addB(b1);
-//    root.addB(b2);
+    root.addB(b1);
+    root.addB(b2);
 //    root.setC(c);
 
-    Path pathToYaml = Paths.get("featureTest.yml");
-    Path pathToPng = Paths.get("featureTest.png");
-    Path pathToSvg = Paths.get("featureTest.svg");
-    DumpBuilder builder = Dumper
+    return Dumper
 //        .read(null)
         .read(root)
 //        .enableRelationWithRank()
 //        .customPreamble("hide empty members")
         .enableDebug()
         .customPreamble("title My fancy title")
-        .includeChildWhen((parentNode, childNode, contextName) -> {
-          if (parentNode instanceof A && ((A) parentNode).getName().equals("A2")) {
-            return false;
+        .includeChild((parentNode, childNode, contextName) -> {
+//          if (parentNode instanceof A && ((A) parentNode).getName().equals("A2")) {
+//            return false;
+//          }
+          if (parentNode instanceof Root && childNode instanceof B) {
+            return !"B3".equals(((B) childNode).getName());
           }
-          return !contextName.equals("MyC");
+//          return !contextName.equals("MyC");
+          return true;
         })
-        .includeRelationsWhen((sourceNode, targetNode, roleName) ->
+        .includeRelation((sourceNode, targetNode, roleName) ->
             !(sourceNode instanceof B) || !((B) sourceNode).getName().equals("B6.1.1"))
-        .includeAttributeWhen((node, attributeName, isNTA, supplier) -> {
+        .includeAttribute((node, attributeName, isNTA, supplier) -> {
           switch (attributeName) {
             case "referenceAttr":
             case "collectBs":
@@ -69,11 +104,66 @@ public class FeatureTestMain {
           }
         })
         .skinParam(SkinParamBooleanSetting.Shadowing, false)
-        .setNameMethod(node -> node.getClass().getSimpleName() + ASTNode.counter++);
+        .relationStyle((source, target, isComputed, isContainment, style) -> {
+          System.out.println(style.getLabel() + ", computed: " + isComputed + ", containment: " + isContainment + ", textC: " + style.getTextColor() + ", lineC: " + style.getLineColor());
+          if (isContainment && target != null && style.getLabel().equals(target.getClass().getSimpleName())) {
+            style.setLabel("");
+          }
+          switch (style.getLabel()) {
+            case "ManyA":
+              style.setLabel("ManyA of " + ((Nameable) source).getName());
+              break;
+            case "OneA":
+            case "MaybeC?":
+              style.setTextColor("red");
+              style.setLineColor("green");
+              break;
+            case "B":
+              style.setLineColor("orange");
+              style.setTextColor("orange");
+              break;
+            case "collectBs":
+              style.setLineColor("purple");
+              style.setTextColor("purple");
+              break;
+          }
+        })
+        .<ASTNode<?>>nodeStyle((node, style) -> {
+          if (node.isA()) {
+            style.setBackgroundColor("yellow");
+          }
+          if (node instanceof B && ((B) node).getOtherValue().startsWith("#")) {
+            style.setBackgroundColor(((B) node).getOtherValue().substring(1));
+          }
+          style.setLabel(node.getClass().getSimpleName() + ASTNode.counter++);
+        });
+  }
 
-    builder.dumpAsYaml(pathToYaml, true);
-    builder.dumpAsPNG(pathToPng);
-    builder.dumpAsSVG(pathToSvg);
-    builder.dumpAsSource(Paths.get("featureTest.puml"));
+  private DumpBuilder withOtherRoot() {
+    OtherRoot root = new OtherRoot();
+    E e1 = new E().setName("E1");
+    E e2 = new E().setName("E2");
+    E e3 = new E().setName("E3");
+    E e4 = new E().setName("E4");
+    F f1 = new F().setName("F1");
+    F f2 = new F().setName("F2");
+    F f3 = new F().setName("F3");
+    F f4 = new F().setName("F4");
+    for (E e : new E[]{e1, e2, e3, e4}) {
+      root.addE(e);
+    }
+    for (F f : new F[]{f1, f2, f3, f4}) {
+      root.addF(f);
+    }
+    e1.addMyF(f1);
+    e1.addMyF(f2);
+    e2.addMyF(f2);
+    e3.addMyF(f2);
+
+    return Dumper.read(root)
+        .includeToken((node, tokenName, value) -> false)
+        .nodeStyle((node, style) -> {
+          style.setLabel(((Nameable) node).getName());
+        });
   }
 }
diff --git a/dumpAst.tests/src/main/jastadd/testDumper.jrag b/dumpAst.tests/src/main/jastadd/testDumper.jrag
index a4e17a122827b1b54a814ddd453014a9b8beda0f..2c7c539d5311542577b0ab9f4c3f6d7d4ca8084d 100644
--- a/dumpAst.tests/src/main/jastadd/testDumper.jrag
+++ b/dumpAst.tests/src/main/jastadd/testDumper.jrag
@@ -39,12 +39,3 @@ aspect GrammarGlobal {
     return result;
   }
 }
-
-aspect GrammarTypeLevel {
-  syn int AbstractT.simpleAttr() = 43;
-  syn nta A AbstractT.getCalculated() {
-    A result = new A();
-    result.setName("Calculated-" + getName());
-    return result;
-  }
-}
diff --git a/dumpAst.tests/src/main/jastadd/testDumper.relast b/dumpAst.tests/src/main/jastadd/testDumper.relast
index cf9d37f4899b1af59099341292e3ecd111200e4d..6bdb7aabeb555cff5e8ba8b47e79dddc0bf05c1a 100644
--- a/dumpAst.tests/src/main/jastadd/testDumper.relast
+++ b/dumpAst.tests/src/main/jastadd/testDumper.relast
@@ -14,17 +14,6 @@ rel C.biA1 <-> A.biC1 ;
 rel C.biA2* <-> A.biC2 ;
 rel C.biA3? <-> A.biC3 ;
 
-// testcases with type-level inclusion/exclusion
-TRoot : Nameable ::= A T1 T2 T3 ;
-abstract AbstractT : Nameable ::= B Bee:B* <SomeValue> <Unwanted:int> ;
-T1 : AbstractT ;
-T2 : AbstractT ;
-T3 : AbstractT ;
-
-rel AbstractT.oneA -> A ;
-rel AbstractT.maybeA? -> A ;
-rel AbstractT.manyA* -> A ;
-
 Position : Nameable ::= <X:double> <Y:double> <Z:double>;
 Size : Nameable ::= <Length:double> <Width:double> <Height:double>;
 Orientation : Nameable ::= <X:double> <Y:double> <Z:double> <W:double>;
diff --git a/dumpAst.tests/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestExcluded.java b/dumpAst.tests/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestExcluded.java
index 23d161c3f760e74ec0e2f398ef7f4fd7e73185bd..3e49f20365ea41a83ec4a1cac5a544e26f8b7a5b 100644
--- a/dumpAst.tests/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestExcluded.java
+++ b/dumpAst.tests/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestExcluded.java
@@ -81,10 +81,10 @@ public class TestExcluded {
         tuple("BiC1", C_NAME), tuple("BiC2", C_NAME), tuple("BiC3", C_NAME));
     // B
     assertThatMapOf(normalRelationChildren(findByName(nodes, B_NAME))).containsExactlyInAnyOrder(
-        tuple("OneA", A_NAME), tuple("MaybeC", C_NAME));
+        tuple("OneA", A_NAME), tuple("MaybeC?", C_NAME));
     // C
     assertThatMapOf(normalRelationChildren(findByName(nodes, C_NAME))).containsExactlyInAnyOrder(
-        tuple("BiA1", A_NAME), tuple("BiA3", A_NAME));
+        tuple("BiA1", A_NAME), tuple("BiA3?", A_NAME));
     assertThatMapOf(listRelationChildren(findByName(nodes, C_NAME)), "BiA2").containsExactlyInAnyOrder(A_NAME);
     assertThatMapOf(referenceTokens(findByName(nodes, C_NAME))).containsExactlyInAnyOrder(
         tuple(TOKEN_LABEL_RAW_REFERENCE, A_NAME));
@@ -116,7 +116,7 @@ public class TestExcluded {
     assertThat(nodes).flatExtracting(NAME_EXTRACTOR).containsExactlyInAnyOrder(ROOT_NAME, B_NAME, C_NAME);
     // B
     assertThatMapOf(normalRelationChildren(findByName(nodes, B_NAME))).containsExactlyInAnyOrder(
-        tuple("MaybeC", (C_NAME)));
+        tuple("MaybeC?", (C_NAME)));
     // C
     assertThat(normalRelationChildren(findByName(nodes, C_NAME))).isEmpty();
     assertThat(listRelationChildren(findByName(nodes, C_NAME))).isEmpty();
@@ -198,7 +198,8 @@ public class TestExcluded {
       c.setBiA1(a);
     }));
 
-    List<DumpNode> nodes = TestUtils.dumpModel(root, db -> db.excludeTokens(TOKEN_LABEL_UNWANTED));
+    List<DumpNode> nodes = TestUtils.dumpModel(root, db -> db.includeToken((node, tokenName, value) ->
+        !tokenName.equals(TOKEN_LABEL_UNWANTED)));
     assertThat(nodes).flatExtracting(NAME_EXTRACTOR).containsExactlyInAnyOrder(ROOT_NAME, A_NAME, C_NAME);
     DumpNode actualC = findByName(nodes, C_NAME);
     assertThat(valueTokens(actualC)).containsOnly(
@@ -218,7 +219,8 @@ public class TestExcluded {
       c.setBiA1(a);
     }));
 
-    List<DumpNode> nodes = TestUtils.dumpModel(root, db -> db.excludeTokens(TOKEN_LABEL_UNWANTED, TOKEN_LABEL_RAW_REFERENCE));
+    List<DumpNode> nodes = TestUtils.dumpModel(root, db -> db.includeToken((node, tokenName, value) ->
+        !tokenName.equals(TOKEN_LABEL_UNWANTED) && !tokenName.equals(TOKEN_LABEL_RAW_REFERENCE)));
     assertThat(nodes).flatExtracting(NAME_EXTRACTOR).containsExactlyInAnyOrder(ROOT_NAME, A_NAME, C_NAME);
     DumpNode actualC = findByName(nodes, C_NAME);
     assertThat(valueTokens(actualC)).containsOnly(
@@ -238,7 +240,8 @@ public class TestExcluded {
       c.setBiA1(a);
     }));
 
-    List<DumpNode> nodes = TestUtils.dumpModel(root, db -> db.excludeTokens("bi.*"));
+    List<DumpNode> nodes = TestUtils.dumpModel(root, db -> db.includeToken((node, tokenName, value) ->
+        !tokenName.startsWith("bi")));
     assertThat(nodes).flatExtracting(NAME_EXTRACTOR).containsExactlyInAnyOrder(ROOT_NAME, A_NAME, C_NAME);
     DumpNode actualC = findByName(nodes, C_NAME);
     assertThat(valueTokens(actualC)).containsOnly(
@@ -258,7 +261,8 @@ public class TestExcluded {
       c.setBiA1(a);
     }));
 
-    List<DumpNode> nodes = TestUtils.dumpModel(root, db -> db.excludeTokens("Bi.*"));
+    List<DumpNode> nodes = TestUtils.dumpModel(root, db -> db.includeToken((node, tokenName, value) ->
+        !tokenName.startsWith("Bi")));
     assertThat(nodes).flatExtracting(NAME_EXTRACTOR).containsExactlyInAnyOrder(ROOT_NAME, A_NAME, C_NAME);
     DumpNode actualC = findByName(nodes, C_NAME);
     assertThat(valueTokens(actualC)).containsOnly(
@@ -312,7 +316,8 @@ public class TestExcluded {
     }), null);
     root.getA().getB().setOneA(root.getA().getMyC().getA());
 
-    List<DumpNode> nodes = TestUtils.dumpModel(root, db -> db.excludeChildren("MyC"));
+    List<DumpNode> nodes = TestUtils.dumpModel(root, db -> db.includeChild((parentNode, childNode, contextName) ->
+        !contextName.equals("MyC")));
     assertThat(nodes).flatExtracting(NAME_EXTRACTOR).containsExactlyInAnyOrder(
         ROOT_NAME, A_NAME, B_NAME, A2_Name, B2_NAME);
     DumpNode actualA = findByName(nodes, A_NAME);
@@ -333,16 +338,17 @@ public class TestExcluded {
   public void testRelationsExclude() throws TransformationException {
     Root root = setupAllRelations();
 
-    List<DumpNode> nodes = TestUtils.dumpModel(root, db -> db.excludeRelations("BiC1"));
+    List<DumpNode> nodes = TestUtils.dumpModel(root, db -> db.includeRelation((sourceNode, targetNode, roleName) ->
+        !roleName.equals("BiC1")));
     // A
     assertThatMapOf(normalRelationChildren(findByName(nodes, A_NAME))).containsExactlyInAnyOrder(
         tuple("BiC2", C_NAME), tuple("BiC3", C_NAME));
     // B
     assertThatMapOf(normalRelationChildren(findByName(nodes, B_NAME))).containsExactlyInAnyOrder(
-        tuple("OneA", A_NAME), tuple("MaybeC", C_NAME));
+        tuple("OneA", A_NAME), tuple("MaybeC?", C_NAME));
     // C
     assertThatMapOf(normalRelationChildren(findByName(nodes, C_NAME))).containsExactlyInAnyOrder(
-        tuple("BiA1", A_NAME), tuple("BiA3", A_NAME));
+        tuple("BiA1", A_NAME), tuple("BiA3?", A_NAME));
     assertThatMapOf(listRelationChildren(findByName(nodes, C_NAME)), "BiA2").containsExactlyInAnyOrder(A_NAME);
     assertThatMapOf(referenceTokens(findByName(nodes, C_NAME))).containsExactlyInAnyOrder(
         tuple(TOKEN_LABEL_RAW_REFERENCE, A_NAME));
@@ -352,15 +358,16 @@ public class TestExcluded {
   public void testRelationsExcludeRegex1() throws TransformationException {
     Root root = setupAllRelations();
 
-    List<DumpNode> nodes = TestUtils.dumpModel(root, db -> db.excludeRelations("Bi.*"));
+    List<DumpNode> nodes = TestUtils.dumpModel(root, db -> db.includeRelation((sourceNode, targetNode, roleName) ->
+        !roleName.startsWith("Bi")));
     // A
     assertThat(normalRelationChildren(findByName(nodes, A_NAME))).isEmpty();
     // B
     assertThatMapOf(normalRelationChildren(findByName(nodes, B_NAME))).containsExactlyInAnyOrder(
-        tuple("OneA", A_NAME), tuple("MaybeC", C_NAME));
+        tuple("OneA", A_NAME), tuple("MaybeC?", C_NAME));
     // C
     assertThat(normalRelationChildren(findByName(nodes, C_NAME))).isEmpty();
-    assertThat(listRelationChildren(findByName(nodes, C_NAME))).isEmpty();
+    assertEmpty(TestUtils.findByName(nodes, C_NAME).getDumpRelation(0));
     assertThatMapOf(referenceTokens(findByName(nodes, C_NAME))).containsExactlyInAnyOrder(
         tuple(TOKEN_LABEL_RAW_REFERENCE, A_NAME));
   }
@@ -369,16 +376,17 @@ public class TestExcluded {
   public void testRelationsExcludeRegex2() throws TransformationException {
     Root root = setupAllRelations();
 
-    List<DumpNode> nodes = TestUtils.dumpModel(root, db -> db.excludeRelations(".*A.*"));
+    List<DumpNode> nodes = TestUtils.dumpModel(root, db -> db.includeRelation((sourceNode, targetNode, roleName) ->
+        !roleName.contains("A")));
     // A
     assertThatMapOf(normalRelationChildren(findByName(nodes, A_NAME))).containsExactlyInAnyOrder(
         tuple("BiC1", C_NAME), tuple("BiC2", C_NAME), tuple("BiC3", C_NAME));
     // B
     assertThatMapOf(normalRelationChildren(findByName(nodes, B_NAME))).containsExactlyInAnyOrder(
-        tuple("MaybeC", C_NAME));
+        tuple("MaybeC?", C_NAME));
     // C
     assertThat(normalRelationChildren(findByName(nodes, C_NAME))).isEmpty();
-    assertThat(listRelationChildren(findByName(nodes, C_NAME))).isEmpty();
+    assertEmpty(TestUtils.findByName(nodes, C_NAME).getDumpRelation(0));
     assertThatMapOf(referenceTokens(findByName(nodes, C_NAME))).containsExactlyInAnyOrder(
         tuple(TOKEN_LABEL_RAW_REFERENCE, A_NAME));
   }
diff --git a/dumpAst.tests/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestIncluded.java b/dumpAst.tests/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestIncluded.java
index 4d9d20e75ecb438ed0b21c04d898ec7384c6b0d4..e03718c7c9e3935507de7ca670b901e31db85a5c 100644
--- a/dumpAst.tests/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestIncluded.java
+++ b/dumpAst.tests/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestIncluded.java
@@ -26,7 +26,8 @@ public class TestIncluded {
   public void testValueAttributeIncluded() throws TransformationException {
     Root root = createRoot(null, null);
 
-    List<DumpNode> nodes = TestUtils.dumpModel(root, db -> db.includeAttributes("simpleAttr"));
+    List<DumpNode> nodes = TestUtils.dumpModel(root, db ->
+        db.includeAttribute((node, attributeName, isNTA, value) -> !isNTA && attributeName.equals("simpleAttr")));
     assertThat(nodes).flatExtracting(NAME_EXTRACTOR).containsExactly(ROOT_NAME);
     DumpNode actualRoot = TestUtils.findByName(nodes, ROOT_NAME);
     assertThat(valueTokens(actualRoot)).containsOnly(entry("Name", ROOT_NAME), entry("simpleAttr", 42));
@@ -36,7 +37,8 @@ public class TestIncluded {
   public void testReferenceListAttributeIncluded() throws TransformationException {
     Root root = createRoot(null, null, createB(B1_NAME), createB(B2_NAME), createB(B3_NAME));
 
-    List<DumpNode> nodes = TestUtils.dumpModel(root, db -> db.includeAttributes("setOfBs"));
+    List<DumpNode> nodes = TestUtils.dumpModel(root, db ->
+        db.includeAttribute((node, attributeName, isNTA, value) -> !isNTA && attributeName.equals("setOfBs")));
     assertThat(nodes).flatExtracting(NAME_EXTRACTOR).containsExactly(ROOT_NAME, B1_NAME, B2_NAME, B3_NAME);
 
     DumpNode actualRoot = TestUtils.findByName(nodes, ROOT_NAME);
@@ -58,7 +60,8 @@ public class TestIncluded {
   public void testReferenceAttributeIncluded() throws TransformationException {
     Root root = createRoot(createA(A_NAME), null);
 
-    List<DumpNode> nodes = TestUtils.dumpModel(root, db -> db.includeAttributes("referenceAttr"));
+    List<DumpNode> nodes = TestUtils.dumpModel(root, db ->
+        db.includeAttribute((node, attributeName, isNTA, value) -> !isNTA && attributeName.equals("referenceAttr")));
     assertThat(nodes).flatExtracting(NAME_EXTRACTOR).containsExactly(ROOT_NAME, A_NAME);
     DumpNode actualRoot = TestUtils.findByName(nodes, ROOT_NAME);
     assertThatMapOf(referenceTokens(actualRoot)).containsOnly(tuple("referenceAttr", A_NAME));
@@ -78,7 +81,8 @@ public class TestIncluded {
   public void testNormalNTAIncluded() throws TransformationException {
     Root root = createRoot(null, createC(C_NAME));
 
-    List<DumpNode> nodes = TestUtils.dumpModel(root, db -> db.includeNonterminalAttributes("Calculated"));
+    List<DumpNode> nodes = TestUtils.dumpModel(root, db ->
+        db.includeAttribute((node, attributeName, isNTA, value) -> isNTA && attributeName.equals("Calculated")));
     assertThat(nodes).flatExtracting(NAME_EXTRACTOR).containsExactly(ROOT_NAME, C_NAME, "Calculated-" + C_NAME);
     DumpNode actualC = TestUtils.findByName(nodes, C_NAME);
     assertThatMapOf(normalChildren(actualC)).containsOnly(tuple("Calculated", "Calculated-" + C_NAME));
@@ -99,7 +103,8 @@ public class TestIncluded {
   public void testListNTAIncluded() throws TransformationException {
     Root root = createRoot(null, createC(C_NAME));
 
-    List<DumpNode> nodes = TestUtils.dumpModel(root, db -> db.includeNonterminalAttributes("AlsoCalculated"));
+    List<DumpNode> nodes = TestUtils.dumpModel(root, db ->
+        db.includeAttribute((node, attributeName, isNTA, value) -> isNTA && attributeName.equals("AlsoCalculated")));
     assertThat(nodes).flatExtracting(NAME_EXTRACTOR).containsExactly(ROOT_NAME, C_NAME, "AlsoCalculated-" + C_NAME);
     DumpNode actualC = TestUtils.findByName(nodes, C_NAME);
     assertThatMapOf(listChildren(actualC), "AlsoCalculated").containsExactly("AlsoCalculated-" + C_NAME);
diff --git a/dumpAst.tests/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestComplex.java b/dumpAst.tests/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestRegression.java
similarity index 62%
rename from dumpAst.tests/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestComplex.java
rename to dumpAst.tests/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestRegression.java
index 574ec066d9d04d22bfc0f3662058f0960c516d20..a7a35cdf0200dd9726bb4f8f79e7ecda1c544a30 100644
--- a/dumpAst.tests/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestComplex.java
+++ b/dumpAst.tests/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestRegression.java
@@ -6,22 +6,28 @@ import org.jastadd.testDumper.ast.DropOffLocation;
 import org.jastadd.testDumper.ast.Orientation;
 import org.jastadd.testDumper.ast.Position;
 import org.jastadd.testDumper.ast.Size;
+import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.List;
 
 import static de.tudresden.inf.st.jastadd.testDumper.TestUtils.*;
 import static org.assertj.core.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
 
 /**
- * More complex test cases.
+ * Regression test cases.
  *
  * @author rschoene - Initial contribution
  */
-public class TestComplex {
+public class TestRegression {
 
   @Test
-  public void testRegressionIssue16() throws TransformationException {
+  public void regressionIssue16() throws TransformationException {
     DropOffLocation location = new DropOffLocation();
     location.setName(ROOT_NAME);
     location.setPosition(new Position(T1_NAME, 1, 2, 3));
@@ -35,4 +41,11 @@ public class TestComplex {
         entry("Y", 2.0),
         entry("Z", 3.0));
   }
+
+  @Test
+  public void regressionNoNewlineForRelationStyleMustache() throws IOException {
+    Path path = Paths.get("..", "dumpAst.base", "src", "main", "resources", "RelationStyle.mustache");
+    List<String> lines = Files.readAllLines(path);
+    assertEquals(1, lines.size(), "Ensure no trailing newline in RelationStyle.mustache");
+  }
 }
diff --git a/dumpAst.tests/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestSimple.java b/dumpAst.tests/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestSimple.java
index 6d6294bea952209adffda3e65963e297e07f51dd..2f4224aeb3b54fcdff79f8afc2ef1992f27f70c7 100644
--- a/dumpAst.tests/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestSimple.java
+++ b/dumpAst.tests/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestSimple.java
@@ -28,7 +28,7 @@ public class TestSimple {
     assertThat(nodes).flatExtracting(NAME_EXTRACTOR).containsExactly(ROOT_NAME);
     DumpNode actualRoot = TestUtils.findByName(nodes, ROOT_NAME);
     assertEquals(1, actualRoot.getNumDumpToken());
-    assertEquals(0, actualRoot.getNumDumpChildNode());
+    assertEmpty(actualRoot.getDumpChildNode(0));
     assertEquals(0, actualRoot.getNumDumpRelation());
   }
 
@@ -64,7 +64,8 @@ public class TestSimple {
     assertThat(nodes).flatExtracting(NAME_EXTRACTOR).containsExactlyInAnyOrder(ROOT_NAME, A_NAME);
     DumpNode actualRoot = TestUtils.findByName(nodes, ROOT_NAME);
     assertEquals(1, actualRoot.getNumDumpToken());
-    assertEquals(1, actualRoot.getNumDumpChildNode());
+    assertEquals(2, actualRoot.getNumDumpChildNode());
+    assertEmpty(actualRoot.getDumpChildNode(1));
     assertEquals(0, actualRoot.getNumDumpRelation());
     assertThatMapOf(normalChildren(actualRoot)).containsExactlyInAnyOrder(tuple("A", A_NAME));
   }
@@ -86,7 +87,8 @@ public class TestSimple {
     );
     DumpNode actualRoot = TestUtils.findByName(nodes, ROOT_NAME);
     assertEquals(1, actualRoot.getNumDumpToken());
-    assertEquals(2, actualRoot.getNumDumpChildNode());
+    assertEquals(3, actualRoot.getNumDumpChildNode());
+    assertEmpty(actualRoot.getDumpChildNode(1));
     assertEquals(0, actualRoot.getNumDumpRelation());
 
     DumpNode actualA = TestUtils.findByName(nodes, A_NAME);
@@ -152,7 +154,7 @@ public class TestSimple {
     assertThat(nodes).flatExtracting(NAME_EXTRACTOR).containsExactlyInAnyOrder(ROOT_NAME);
     DumpNode actualRoot = TestUtils.findByName(nodes, ROOT_NAME);
     assertEquals(1, actualRoot.getNumDumpToken());
-    assertEquals(0, actualRoot.getNumDumpChildNode());
+    assertEmpty(actualRoot.getDumpChildNode(0));
     assertEquals(0, actualRoot.getNumDumpRelation());
     assertThatMapOf(normalChildren(actualRoot)).isEmpty();
   }
@@ -165,9 +167,10 @@ public class TestSimple {
     assertThat(nodes).flatExtracting(NAME_EXTRACTOR).containsExactlyInAnyOrder(ROOT_NAME, C_NAME);
     DumpNode actualRoot = TestUtils.findByName(nodes, ROOT_NAME);
     assertEquals(1, actualRoot.getNumDumpToken());
-    assertEquals(1, actualRoot.getNumDumpChildNode());
+    assertEquals(2, actualRoot.getNumDumpChildNode());
+    assertEmpty(actualRoot.getDumpChildNode(0));
     assertEquals(0, actualRoot.getNumDumpRelation());
-    assertThatMapOf(normalChildren(actualRoot)).containsExactlyInAnyOrder(tuple("C", C_NAME));
+    assertThatMapOf(normalChildren(actualRoot)).containsExactlyInAnyOrder(tuple("C?", C_NAME));
   }
 
   @Test
@@ -203,7 +206,7 @@ public class TestSimple {
     List<DumpNode> nodes = TestUtils.dumpModel(root);
     assertThat(nodes).flatExtracting(NAME_EXTRACTOR).containsExactlyInAnyOrder(ROOT_NAME, A_NAME, B_NAME, C_NAME);
     DumpNode actualB = TestUtils.findByName(nodes, B_NAME);
-    assertThatMapOf(normalRelationChildren(actualB)).containsExactlyInAnyOrder(tuple("MaybeC", C_NAME));
+    assertThatMapOf(normalRelationChildren(actualB)).containsExactlyInAnyOrder(tuple("MaybeC?", C_NAME));
   }
 
   @Test
@@ -245,7 +248,7 @@ public class TestSimple {
     DumpNode actualA = TestUtils.findByName(nodes, A_NAME);
     assertThatMapOf(normalRelationChildren(actualA)).containsExactlyInAnyOrder(tuple("BiC3", C_NAME));
     DumpNode actualC = TestUtils.findByName(nodes, C_NAME);
-    assertThatMapOf(normalRelationChildren(actualC)).containsExactlyInAnyOrder(tuple("BiA3", A_NAME));
+    assertThatMapOf(normalRelationChildren(actualC)).containsExactlyInAnyOrder(tuple("BiA3?", A_NAME));
   }
 
   @Test
@@ -282,7 +285,7 @@ public class TestSimple {
     Root root = createRoot(createA(A_NAME), null);
 
     List<DumpNode> nodes = TestUtils.dumpModel(root,
-        builder -> builder.<ASTNode<?>>setNameMethod(n -> n.isA() ? "A" : "Not A"));
+        builder -> builder.<ASTNode<?>>nodeStyle((node, style) -> style.setLabel(node.isA() ? "A" : "Not A")));
     DumpNode actualRoot = TestUtils.findByName(nodes, ROOT_NAME);
     assertEquals("Not A", actualRoot.getLabel());
 
@@ -295,7 +298,7 @@ public class TestSimple {
     Root root = createRoot(createA(A_NAME), null);
 
     List<DumpNode> nodes = TestUtils.dumpModel(root,
-        builder -> builder.<ASTNode<?>>setTextColorMethod(n -> n.isA() ? "red" : ""));
+        builder -> builder.<ASTNode<?>>nodeStyle((node, style) -> style.setTextColor(node.isA() ? "red" : "")));
     DumpNode actualRoot = TestUtils.findByName(nodes, ROOT_NAME);
     assertEquals("", actualRoot.getTextColor());
 
@@ -308,7 +311,7 @@ public class TestSimple {
     Root root = createRoot(createA(A_NAME), null);
 
     List<DumpNode> nodes = TestUtils.dumpModel(root,
-        builder -> builder.<ASTNode<?>>setBackgroundColorMethod(n -> n.isA() ? "green" : ""));
+        builder -> builder.<ASTNode<?>>nodeStyle((node, style) -> style.setBackgroundColor(node.isA() ? "green" : "")));
     DumpNode actualRoot = TestUtils.findByName(nodes, ROOT_NAME);
     assertEquals("", actualRoot.getBackgroundColor());
 
diff --git a/dumpAst.tests/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestTypeLevel3.java b/dumpAst.tests/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestTypeLevel3.java
deleted file mode 100644
index 50f27583960648d90ed9434df90f39729c49ae7d..0000000000000000000000000000000000000000
--- a/dumpAst.tests/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestTypeLevel3.java
+++ /dev/null
@@ -1,222 +0,0 @@
-package de.tudresden.inf.st.jastadd.testDumper;
-
-import de.tudresden.inf.st.jastadd.dumpAst.ast.DumpNode;
-import de.tudresden.inf.st.jastadd.dumpAst.ast.TransformationException;
-import org.jastadd.testDumper.ast.A;
-import org.jastadd.testDumper.ast.AbstractT;
-import org.jastadd.testDumper.ast.TRoot;
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Test;
-
-import java.util.List;
-import java.util.function.Consumer;
-
-import static de.tudresden.inf.st.jastadd.testDumper.TestUtils.*;
-import static org.assertj.core.api.Assertions.*;
-
-/**
- * Testing type-level exclusions.
- * <p>
- * Refer to {@link de.tudresden.inf.st.jastadd.dumpAst.ast.DumpBuilder DumpBuilder} for levels of inclusion/exclusion.
- *
- * @author rschoene - Initial contribution
- */
-public class TestTypeLevel3 {
-
-  @Test
-  public void testTokenLevel2Excluded() throws TransformationException {
-    Consumer<AbstractT> setUnwanted = t -> t.setUnwanted(5);
-    TRoot root = createTRoot(createA(A_NAME), createT1(setUnwanted), createT2(setUnwanted), createT3(setUnwanted));
-    Assertions.assertEquals(5, root.getT2().getUnwanted());
-
-    List<DumpNode> nodes = TestUtils.dumpModel(root, dp -> dp.excludeTokens("Unwanted"));
-    assertThat(nodes).flatExtracting(NAME_EXTRACTOR).containsExactlyInAnyOrder(ROOT_NAME, A_NAME, T1_NAME, T2_NAME, T3_NAME);
-    for (String name : Tx_NAMES) {
-      DumpNode actualTx = TestUtils.findByName(nodes, name);
-      assertThat(valueTokens(actualTx)).containsOnly(entry("Name", name));
-    }
-  }
-
-  @Test
-  public void testTokenLevel3SomeIncluded() throws TransformationException {
-    Consumer<AbstractT> setUnwanted = t -> t.setUnwanted(5);
-    TRoot root = createTRoot(createA(A_NAME), createT1(setUnwanted), createT2(setUnwanted), createT3(setUnwanted));
-    Assertions.assertEquals(5, root.getT2().getUnwanted());
-
-    List<DumpNode> nodesT2 = TestUtils.dumpModel(root,
-        dp -> dp.excludeTokens("Unwanted")
-            .includeTokensFor("T2", "Unwanted"));
-    assertThat(nodesT2).flatExtracting(NAME_EXTRACTOR).containsExactlyInAnyOrder(ROOT_NAME, A_NAME, T1_NAME, T2_NAME, T3_NAME);
-    assertThat(valueTokens(TestUtils.findByName(nodesT2, T1_NAME))).containsOnly(
-        entry("Name", T1_NAME));
-    assertThat(valueTokens(TestUtils.findByName(nodesT2, T2_NAME))).containsOnly(
-        entry("Name", T2_NAME), entry("Unwanted", 5));
-    assertThat(valueTokens(TestUtils.findByName(nodesT2, T3_NAME))).containsOnly(
-        entry("Name", T3_NAME));
-
-    List<DumpNode> nodesT2T3 = TestUtils.dumpModel(root,
-        dp -> dp.excludeTokens("Unwanted")
-            .includeTokensFor("T2|T3", "Unwanted"));
-    assertThat(nodesT2T3).flatExtracting(NAME_EXTRACTOR).containsExactlyInAnyOrder(ROOT_NAME, A_NAME, T1_NAME, T2_NAME, T3_NAME);
-    assertThat(valueTokens(TestUtils.findByName(nodesT2T3, T1_NAME))).containsOnly(
-        entry("Name", T1_NAME));
-    assertThat(valueTokens(TestUtils.findByName(nodesT2T3, T2_NAME))).containsOnly(
-        entry("Name", T2_NAME), entry("Unwanted", 5));
-    assertThat(valueTokens(TestUtils.findByName(nodesT2T3, T3_NAME))).containsOnly(
-        entry("Name", T3_NAME), entry("Unwanted", 5));
-  }
-
-  @Test
-  public void testNormalChildLevel2Excluded() throws TransformationException {
-    Consumer<AbstractT> setB = t -> t.setB(createB(t.getName() + B_NAME));
-    TRoot root = createTRoot(createA(A_NAME), createT1(setB), createT2(setB), createT3(setB));
-    Assertions.assertEquals(T2_NAME + B_NAME, root.getT2().getB().getName());
-
-    List<DumpNode> nodes = TestUtils.dumpModel(root, dp -> dp.excludeChildren("B"));
-    assertThat(nodes).flatExtracting(NAME_EXTRACTOR).containsExactlyInAnyOrder(ROOT_NAME, A_NAME, T1_NAME, T2_NAME, T3_NAME);
-    for (String name : Tx_NAMES) {
-      DumpNode actualTx = TestUtils.findByName(nodes, name);
-      assertThat(normalChildren(actualTx)).isEmpty();
-    }
-  }
-
-  @Test
-  public void testNormalChildLevel3SomeIncluded() throws TransformationException {
-    Consumer<AbstractT> setB = t -> t.setB(createB(t.getName() + B_NAME));
-    TRoot root = createTRoot(createA(A_NAME), createT1(setB), createT2(setB), createT3(setB));
-    Assertions.assertEquals(T2_NAME + B_NAME, root.getT2().getB().getName());
-
-    List<DumpNode> nodes = TestUtils.dumpModel(root,
-        dp -> dp.excludeChildren("B")
-            .includeChildrenFor("T2", "B"));
-    assertThat(nodes).flatExtracting(NAME_EXTRACTOR).containsExactlyInAnyOrder(ROOT_NAME, A_NAME, T1_NAME, T2_NAME, T3_NAME, T2_NAME + B_NAME);
-    assertThat(normalChildren(TestUtils.findByName(nodes, T1_NAME))).isEmpty();
-    assertThatMapOf(normalChildren(TestUtils.findByName(nodes, T2_NAME))).containsOnly(
-        tuple("B", T2_NAME + B_NAME));
-    assertThat(normalChildren(TestUtils.findByName(nodes, T3_NAME))).isEmpty();
-  }
-
-  @Test
-  public void testListChildLevel2Excluded() throws TransformationException {
-    Consumer<AbstractT> addBee = t -> t.addBee(createB(t.getName() + B_NAME));
-    TRoot root = createTRoot(createA(A_NAME), createT1(addBee), createT2(addBee), createT3(addBee));
-    Assertions.assertEquals(T2_NAME + B_NAME, root.getT2().getBee(0).getName());
-
-    List<DumpNode> nodes = TestUtils.dumpModel(root,
-        dp -> dp.excludeChildren("Bee"));
-    assertThat(nodes).flatExtracting(NAME_EXTRACTOR).containsExactlyInAnyOrder(ROOT_NAME, A_NAME, T1_NAME, T2_NAME, T3_NAME);
-    for (String name : Tx_NAMES) {
-      assertThat(listChildren(TestUtils.findByName(nodes, name))).isEmpty();
-    }
-  }
-
-  @Test
-  public void testListChildLevel3SomeIncluded() throws TransformationException {
-    Consumer<AbstractT> addBee = t -> t.addBee(createB(t.getName() + B_NAME));
-    TRoot root = createTRoot(createA(A_NAME), createT1(addBee), createT2(addBee), createT3(addBee));
-    Assertions.assertEquals(T2_NAME + B_NAME, root.getT2().getBee(0).getName());
-
-    List<DumpNode> nodes = TestUtils.dumpModel(root,
-        dp -> dp.excludeChildren("Bee")
-                .includeChildrenFor("T2", "Bee"));
-    assertThat(nodes).flatExtracting(NAME_EXTRACTOR).containsExactlyInAnyOrder(ROOT_NAME, A_NAME, T1_NAME, T2_NAME, T3_NAME, T2_NAME + B_NAME);
-    assertThat(listChildren(TestUtils.findByName(nodes, T1_NAME))).isEmpty();
-    assertThatMapOf(listChildren(TestUtils.findByName(nodes, T2_NAME)), "Bee").containsOnly(
-        T2_NAME + B_NAME);
-    assertThat(listChildren(TestUtils.findByName(nodes, T3_NAME))).isEmpty();
-  }
-
-  @Test
-  public void testRelationLevel2Excluded() throws TransformationException {
-    final A a = createA(A_NAME);
-    Consumer<AbstractT> setOneA = t -> t.setOneA(a);
-    TRoot root = createTRoot(a, createT1(setOneA), createT2(setOneA), createT3(setOneA));
-    Assertions.assertEquals(a, root.getT2().getOneA());
-
-    List<DumpNode> nodes = TestUtils.dumpModel(root, dp -> dp.excludeRelations("OneA"));
-    assertThat(nodes).flatExtracting(NAME_EXTRACTOR).containsExactlyInAnyOrder(ROOT_NAME, A_NAME, T1_NAME, T2_NAME, T3_NAME);
-    for (String name : Tx_NAMES) {
-      DumpNode actualTx = TestUtils.findByName(nodes, name);
-      assertThat(normalRelationChildren(actualTx)).isEmpty();
-    }
-  }
-
-  @Test
-  public void testRelationLevel3SomeIncluded() throws TransformationException {
-    final A a = createA(A_NAME);
-    Consumer<AbstractT> setOneA = t -> t.setOneA(a);
-    TRoot root = createTRoot(a, createT1(setOneA), createT2(setOneA), createT3(setOneA));
-    Assertions.assertEquals(a, root.getT2().getOneA());
-
-    List<DumpNode> nodes = TestUtils.dumpModel(root,
-        dp -> dp.excludeRelations("OneA")
-            .includeRelationsFor("T2", "OneA"));
-    assertThat(nodes).flatExtracting(NAME_EXTRACTOR).containsExactlyInAnyOrder(ROOT_NAME, A_NAME, T1_NAME, T2_NAME, T3_NAME);
-    assertThat(normalRelationChildren(TestUtils.findByName(nodes, T1_NAME))).isEmpty();
-    assertThatMapOf(normalRelationChildren(TestUtils.findByName(nodes, T2_NAME))).containsOnly(
-        tuple("OneA", A_NAME));
-    assertThat(normalRelationChildren(TestUtils.findByName(nodes, T3_NAME))).isEmpty();
-  }
-
-  @Test
-  public void testAttributeLevel2Included() throws TransformationException {
-    TRoot root = createTRoot(createA(A_NAME), createT1(), createT2(), createT3());
-
-    List<DumpNode> nodes = dumpModel(root,
-        dp -> dp.includeAttributes("simpleAttr"));
-    assertThat(nodes).flatExtracting(NAME_EXTRACTOR).containsExactlyInAnyOrder(ROOT_NAME, A_NAME, T1_NAME, T2_NAME, T3_NAME);
-    for (String name : Tx_NAMES) {
-      DumpNode actualTx = findByName(nodes, name);
-      assertThat(valueTokens(actualTx)).containsOnly(
-          entry("Name", name),
-          entry(TOKEN_LABEL_UNWANTED, 0),
-          entry("simpleAttr", 43));
-    }
-  }
-
-  @Test
-  public void testAttributeLevel3SomeExcluded() throws TransformationException {
-    TRoot root = createTRoot(createA(A_NAME), createT1(), createT2(), createT3());
-
-    List<DumpNode> nodes = dumpModel(root,
-        dp -> dp.includeAttributes("simpleAttr")
-                .excludeAttributesFor("T2", "simpleAttr"));
-    assertThat(nodes).flatExtracting(NAME_EXTRACTOR).containsExactlyInAnyOrder(ROOT_NAME, A_NAME, T1_NAME, T2_NAME, T3_NAME);
-    assertThat(valueTokens(findByName(nodes, T1_NAME))).containsOnly(
-        entry("Name", T1_NAME), entry(TOKEN_LABEL_UNWANTED, 0), entry("simpleAttr", 43));
-    assertThat(valueTokens(findByName(nodes, T2_NAME))).containsOnly(
-        entry("Name", T2_NAME), entry(TOKEN_LABEL_UNWANTED, 0));
-    assertThat(valueTokens(findByName(nodes, T3_NAME))).containsOnly(
-        entry("Name", T3_NAME), entry(TOKEN_LABEL_UNWANTED, 0), entry("simpleAttr", 43));
-  }
-
-  @Test
-  public void testNTALevel2Included() throws TransformationException {
-    TRoot root = createTRoot(createA(A_NAME), createT1(), createT2(), createT3());
-
-    List<DumpNode> nodes = dumpModel(root,
-        dp -> dp.includeNonterminalAttributes("Calculated"));
-    assertThat(nodes).flatExtracting(NAME_EXTRACTOR).containsExactlyInAnyOrder(ROOT_NAME, A_NAME, T1_NAME, T2_NAME, T3_NAME, "Calculated-" + T1_NAME, "Calculated-" + T2_NAME, "Calculated-" + T3_NAME);
-    for (String name : Tx_NAMES) {
-      assertThatMapOf(normalChildren(findByName(nodes, name))).containsOnly(
-          tuple("Calculated", "Calculated-" + name));
-    }
-  }
-
-  @Test
-  public void testNTALevel3SomeExcluded() throws TransformationException {
-    TRoot root = createTRoot(createA(A_NAME), createT1(), createT2(), createT3());
-
-    List<DumpNode> nodes = dumpModel(root,
-        dp -> dp.includeNonterminalAttributes("Calculated")
-            .excludeNonterminalAttributesFor("T2", "Calculated"));
-    assertThat(nodes).flatExtracting(NAME_EXTRACTOR).containsExactlyInAnyOrder(ROOT_NAME, A_NAME, T1_NAME, T2_NAME, T3_NAME, "Calculated-" + T1_NAME, "Calculated-" + T3_NAME);
-    assertThatMapOf(normalChildren(findByName(nodes, T1_NAME))).containsOnly(
-        tuple("Calculated", "Calculated-" + T1_NAME));
-    assertThat(normalChildren(findByName(nodes, T2_NAME))).isEmpty();
-    assertThatMapOf(normalChildren(findByName(nodes, T3_NAME))).containsOnly(
-        tuple("Calculated", "Calculated-" + T3_NAME));
-  }
-
-}
diff --git a/dumpAst.tests/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestTypeLevel4.java b/dumpAst.tests/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestTypeLevel4.java
deleted file mode 100644
index 8e9220a270c3fe96df2a767d37b565f077c64d55..0000000000000000000000000000000000000000
--- a/dumpAst.tests/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestTypeLevel4.java
+++ /dev/null
@@ -1,245 +0,0 @@
-package de.tudresden.inf.st.jastadd.testDumper;
-
-import de.tudresden.inf.st.jastadd.dumpAst.ast.DumpNode;
-import de.tudresden.inf.st.jastadd.dumpAst.ast.TransformationException;
-import org.jastadd.testDumper.ast.A;
-import org.jastadd.testDumper.ast.AbstractT;
-import org.jastadd.testDumper.ast.TRoot;
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.Test;
-
-import java.util.List;
-import java.util.function.Consumer;
-
-import static de.tudresden.inf.st.jastadd.testDumper.TestUtils.*;
-import static org.assertj.core.api.Assertions.*;
-
-/**
- * Testing type-level inclusions.
- * <p>
- * Refer to {@link de.tudresden.inf.st.jastadd.dumpAst.ast.DumpBuilder DumpBuilder} for levels of inclusion/exclusion.
- *
- * @author rschoene - Initial contribution
- */
-public class TestTypeLevel4 {
-
-  @Test
-  public void testTokenLevel3Included() throws TransformationException {
-    Consumer<AbstractT> setUnwanted = t -> t.setUnwanted(5);
-    TRoot root = createTRoot(createA(A_NAME, createC(C_NAME, c -> c.setUnwanted(6))),
-        createT1(setUnwanted), createT2(setUnwanted), createT3(setUnwanted));
-    Assertions.assertEquals(5, root.getT2().getUnwanted());
-
-    List<DumpNode> nodes = dumpModel(root,
-        dp -> dp.excludeTokens(TOKEN_LABEL_UNWANTED)
-            .includeTokensFor("T.", TOKEN_LABEL_UNWANTED));
-    assertThat(nodes).flatExtracting(NAME_EXTRACTOR).containsExactlyInAnyOrder(
-        ROOT_NAME, A_NAME, C_NAME, T1_NAME, T2_NAME, T3_NAME);
-    for (String name : Tx_NAMES) {
-      DumpNode actualTx = findByName(nodes, name);
-      assertThat(valueTokens(actualTx)).containsOnly(entry("Name", name), entry(TOKEN_LABEL_UNWANTED, 5));
-    }
-    assertThat(valueTokens(findByName(nodes, C_NAME))).containsOnly(
-        entry("Name", C_NAME));
-  }
-
-  @Test
-  public void testTokenLevel4SomeExcluded() throws TransformationException {
-    Consumer<AbstractT> setUnwanted = t -> t.setUnwanted(5);
-    TRoot root = createTRoot(createA(A_NAME, createC(C_NAME, c -> c.setUnwanted(6))),
-        createT1(setUnwanted), createT2(setUnwanted), createT3(setUnwanted));
-    Assertions.assertEquals(5, root.getT2().getUnwanted());
-
-    List<DumpNode> nodesT2 = dumpModel(root,
-        dp -> dp.excludeTokens(TOKEN_LABEL_UNWANTED)
-            .includeTokensFor("T.", TOKEN_LABEL_UNWANTED)
-            .excludeTokensFor("T3", TOKEN_LABEL_UNWANTED));
-    assertThat(nodesT2).flatExtracting(NAME_EXTRACTOR).containsExactlyInAnyOrder(
-        ROOT_NAME, A_NAME, C_NAME, T1_NAME, T2_NAME, T3_NAME);
-    assertThat(valueTokens(findByName(nodesT2, T1_NAME))).containsOnly(
-        entry("Name", T1_NAME), entry(TOKEN_LABEL_UNWANTED, 5));
-    assertThat(valueTokens(findByName(nodesT2, T2_NAME))).containsOnly(
-        entry("Name", T2_NAME), entry(TOKEN_LABEL_UNWANTED, 5));
-    assertThat(valueTokens(findByName(nodesT2, T3_NAME))).containsOnly(
-        entry("Name", T3_NAME));
-    assertThat(valueTokens(findByName(nodesT2, C_NAME))).containsOnly(
-        entry("Name", C_NAME));
-
-    List<DumpNode> nodesT2T3 = dumpModel(root,
-        dp -> dp.excludeTokens(TOKEN_LABEL_UNWANTED)
-            .includeTokensFor("T.", TOKEN_LABEL_UNWANTED)
-            .excludeTokensFor("T2|T3", TOKEN_LABEL_UNWANTED));
-    assertThat(nodesT2T3).flatExtracting(NAME_EXTRACTOR).containsExactlyInAnyOrder(
-        ROOT_NAME, A_NAME, C_NAME, T1_NAME, T2_NAME, T3_NAME);
-    assertThat(valueTokens(findByName(nodesT2T3, T1_NAME))).containsOnly(
-        entry("Name", T1_NAME), entry(TOKEN_LABEL_UNWANTED, 5));
-    assertThat(valueTokens(findByName(nodesT2T3, T2_NAME))).containsOnly(
-        entry("Name", T2_NAME));
-    assertThat(valueTokens(findByName(nodesT2T3, T3_NAME))).containsOnly(
-        entry("Name", T3_NAME));
-    assertThat(valueTokens(findByName(nodesT2T3, C_NAME))).containsOnly(
-        entry("Name", C_NAME));
-  }
-
-  @Test
-  public void testNormalChildLevel3Included() throws TransformationException {
-    Consumer<AbstractT> setB = t -> t.setB(createB(t.getName() + B_NAME));
-    TRoot root = createTRoot(createA(A_NAME), createT1(setB), createT2(setB), createT3(setB));
-    Assertions.assertEquals(T2_NAME + B_NAME, root.getT2().getB().getName());
-
-    List<DumpNode> nodes = dumpModel(root,
-        dp -> dp.excludeChildren("B")
-            .includeChildrenFor("T.", "B"));
-    assertThat(nodes).flatExtracting(NAME_EXTRACTOR).containsExactlyInAnyOrder(ROOT_NAME, A_NAME, T1_NAME, T2_NAME, T3_NAME, T1_NAME + B_NAME, T2_NAME + B_NAME, T3_NAME + B_NAME);
-    for (String name : Tx_NAMES) {
-      assertThatMapOf(normalChildren(findByName(nodes, name))).containsOnly(
-          tuple("B", name + B_NAME));
-    }
-  }
-
-  @Test
-  public void testNormalChildLevel4SomeExcluded() throws TransformationException {
-    Consumer<AbstractT> setB = t -> t.setB(createB(t.getName() + B_NAME));
-    TRoot root = createTRoot(createA(A_NAME), createT1(setB), createT2(setB), createT3(setB));
-    Assertions.assertEquals(T2_NAME + B_NAME, root.getT2().getB().getName());
-
-    List<DumpNode> nodes = dumpModel(root,
-        dp -> dp.excludeChildren("B")
-            .includeChildrenFor("T.", "B")
-            .excludeChildrenFor("T3", "B"));
-    assertThat(nodes).flatExtracting(NAME_EXTRACTOR).containsExactlyInAnyOrder(ROOT_NAME, A_NAME, T1_NAME, T2_NAME, T3_NAME, T1_NAME + B_NAME, T2_NAME + B_NAME);
-    assertThatMapOf(normalChildren(findByName(nodes, T1_NAME))).containsOnly(
-        tuple("B", T1_NAME + B_NAME));
-    assertThatMapOf(normalChildren(findByName(nodes, T2_NAME))).containsOnly(
-        tuple("B", T2_NAME + B_NAME));
-    assertThat(normalChildren(findByName(nodes, T3_NAME))).isEmpty();
-  }
-
-  @Test
-  public void testListChildLevel3Included() throws TransformationException {
-    Consumer<AbstractT> addBee = t -> t.addBee(createB(t.getName() + B_NAME));
-    TRoot root = createTRoot(createA(A_NAME), createT1(addBee), createT2(addBee), createT3(addBee));
-    Assertions.assertEquals(T3_NAME + B_NAME, root.getT3().getBee(0).getName());
-
-    List<DumpNode> nodes = dumpModel(root,
-        dp -> dp.excludeChildren("Bee")
-            .includeChildrenFor("T.", "Bee"));
-    assertThat(nodes).flatExtracting(NAME_EXTRACTOR).containsExactlyInAnyOrder(ROOT_NAME, A_NAME, T1_NAME, T2_NAME, T3_NAME, T1_NAME + B_NAME, T2_NAME + B_NAME, T3_NAME + B_NAME);
-    for (String name : Tx_NAMES) {
-      assertThatMapOf(listChildren(findByName(nodes, name)), "Bee").containsOnly(
-          name + B_NAME);
-    }
-  }
-
-  @Test
-  public void testListChildLevel4SomeExcluded() throws TransformationException {
-    Consumer<AbstractT> addBee = t -> t.addBee(createB(t.getName() + B_NAME));
-    TRoot root = createTRoot(createA(A_NAME), createT1(addBee), createT2(addBee), createT3(addBee));
-    Assertions.assertEquals(T3_NAME + B_NAME, root.getT3().getBee(0).getName());
-
-    List<DumpNode> nodes = dumpModel(root,
-        dp -> dp.excludeChildren("Bee")
-            .includeChildrenFor("T.", "Bee")
-            .excludeChildrenFor("T3", "Bee"));
-    assertThat(nodes).flatExtracting(NAME_EXTRACTOR).containsExactlyInAnyOrder(ROOT_NAME, A_NAME, T1_NAME, T2_NAME, T3_NAME, T1_NAME + B_NAME, T2_NAME + B_NAME);
-    assertThatMapOf(listChildren(findByName(nodes, T1_NAME)), "Bee").containsOnly(
-        T1_NAME + B_NAME);
-    assertThatMapOf(listChildren(findByName(nodes, T2_NAME)), "Bee").containsOnly(
-        T2_NAME + B_NAME);
-    assertThat(listChildren(findByName(nodes, T3_NAME))).isEmpty();
-  }
-
-  @Test
-  public void testRelationLevel3Included() throws TransformationException {
-    final A a = createA(A_NAME);
-    Consumer<AbstractT> setOneA = t -> t.setOneA(a);
-    TRoot root = createTRoot(a, createT1(setOneA), createT2(setOneA), createT3(setOneA));
-    Assertions.assertEquals(a, root.getT2().getOneA());
-
-    List<DumpNode> nodes = dumpModel(root,
-        dp -> dp.excludeRelations("OneA")
-            .includeRelationsFor("T2", "OneA"));
-    assertThat(nodes).flatExtracting(NAME_EXTRACTOR).containsExactlyInAnyOrder(ROOT_NAME, A_NAME, T1_NAME, T2_NAME, T3_NAME);
-    assertThat(normalRelationChildren(findByName(nodes, T1_NAME))).isEmpty();
-    assertThatMapOf(normalRelationChildren(findByName(nodes, T2_NAME))).containsOnly(
-        tuple("OneA", A_NAME));
-    assertThat(normalRelationChildren(findByName(nodes, T3_NAME))).isEmpty();
-  }
-
-  @Test
-  public void testRelationLevel4SomeExcluded() throws TransformationException {
-    final A a = createA(A_NAME);
-    Consumer<AbstractT> setOneA = t -> t.setOneA(a);
-    TRoot root = createTRoot(a, createT1(setOneA), createT2(setOneA), createT3(setOneA));
-    Assertions.assertEquals(a, root.getT2().getOneA());
-
-    List<DumpNode> nodes = dumpModel(root,
-        dp -> dp.excludeRelations("OneA")
-            .includeRelationsFor("T.", "OneA")
-            .excludeRelationsFor("T3", "OneA"));
-    assertThat(nodes).flatExtracting(NAME_EXTRACTOR).containsExactlyInAnyOrder(ROOT_NAME, A_NAME, T1_NAME, T2_NAME, T3_NAME);
-    assertThatMapOf(normalRelationChildren(findByName(nodes, T1_NAME))).containsOnly(
-        tuple("OneA", A_NAME));
-    assertThatMapOf(normalRelationChildren(findByName(nodes, T2_NAME))).containsOnly(
-        tuple("OneA", A_NAME));
-    assertThat(normalRelationChildren(findByName(nodes, T3_NAME))).isEmpty();
-  }
-
-  @Test
-  public void testAttributeLevel3Excluded() throws TransformationException {
-    TRoot root = createTRoot(createA(A_NAME), createT1(), createT2(), createT3());
-
-    List<DumpNode> nodes = TestUtils.dumpModel(root,
-        dp -> dp.includeAttributes("simpleAttr")
-                .excludeAttributesFor("T.", "simpleAttr"));
-    assertThat(nodes).flatExtracting(NAME_EXTRACTOR).containsExactlyInAnyOrder(ROOT_NAME, A_NAME, T1_NAME, T2_NAME, T3_NAME);
-    for (String name : Tx_NAMES) {
-      DumpNode actualTx = TestUtils.findByName(nodes, name);
-      assertThat(valueTokens(actualTx)).containsOnly(
-          entry("Name", name), entry(TOKEN_LABEL_UNWANTED, 0));
-    }
-  }
-
-  @Test
-  public void testAttributeLevel4SomeIncluded() throws TransformationException {
-    TRoot root = createTRoot(createA(A_NAME), createT1(), createT2(), createT3());
-
-    List<DumpNode> nodes = TestUtils.dumpModel(root,
-        dp -> dp.includeAttributes("simpleAttr")
-                .excludeAttributesFor("T.", "simpleAttr")
-                .includeAttributesFor("T3", "simpleAttr"));
-    assertThat(nodes).flatExtracting(NAME_EXTRACTOR).containsExactlyInAnyOrder(ROOT_NAME, A_NAME, T1_NAME, T2_NAME, T3_NAME);
-    assertThat(valueTokens(TestUtils.findByName(nodes, T1_NAME))).containsOnly(
-        entry("Name", T1_NAME), entry(TOKEN_LABEL_UNWANTED, 0));
-    assertThat(valueTokens(TestUtils.findByName(nodes, T2_NAME))).containsOnly(
-        entry("Name", T2_NAME), entry(TOKEN_LABEL_UNWANTED, 0));
-    assertThat(valueTokens(TestUtils.findByName(nodes, T3_NAME))).containsOnly(
-        entry("Name", T3_NAME), entry(TOKEN_LABEL_UNWANTED, 0), entry("simpleAttr", 43));
-  }
-
-  @Test
-  public void testNTALevel3Excluded() throws TransformationException {
-    TRoot root = createTRoot(createA(A_NAME), createT1(), createT2(), createT3());
-
-    List<DumpNode> nodes = TestUtils.dumpModel(root,
-        dp -> dp.includeNonterminalAttributes("Calculated")
-                .excludeNonterminalAttributesFor("T.", "Calculated"));
-    assertThat(nodes).flatExtracting(NAME_EXTRACTOR).containsExactlyInAnyOrder(ROOT_NAME, A_NAME, T1_NAME, T2_NAME, T3_NAME);
-    DumpNode actualT3 = TestUtils.findByName(nodes, T3_NAME);
-    assertThat(normalChildren(actualT3)).isEmpty();
-  }
-
-  @Test
-  public void testNTALevel4SomeIncluded() throws TransformationException {
-    TRoot root = createTRoot(createA(A_NAME), createT1(), createT2(), createT3());
-
-    List<DumpNode> nodes = TestUtils.dumpModel(root,
-        dp -> dp.includeNonterminalAttributes("Calculated")
-            .excludeNonterminalAttributesFor("T.", "Calculated")
-            .includeNonterminalAttributesFor("T3", "Calculated"));
-    assertThat(nodes).flatExtracting(NAME_EXTRACTOR).containsExactlyInAnyOrder(ROOT_NAME, A_NAME, T1_NAME, T2_NAME, T3_NAME, "Calculated-" + T3_NAME);
-    DumpNode actualT3 = TestUtils.findByName(nodes, T3_NAME);
-    assertThatMapOf(normalChildren(actualT3)).containsOnly(tuple("Calculated", "Calculated-" + T3_NAME));
-  }
-
-}
diff --git a/dumpAst.tests/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestUtils.java b/dumpAst.tests/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestUtils.java
index 70213c50de832d945eca1cb8afa891656fc78896..c1228e049963fbd6ff22c85eea49e50b9a58af98 100644
--- a/dumpAst.tests/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestUtils.java
+++ b/dumpAst.tests/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestUtils.java
@@ -11,7 +11,7 @@ import java.util.*;
 import java.util.function.Consumer;
 import java.util.function.Function;
 
-import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.*;
 
 public class TestUtils {
   public static final Random rand = new Random();
@@ -30,7 +30,6 @@ public class TestUtils {
   public static final String T1_NAME = "T1" + Integer.toHexString(rand.nextInt(0xFFFFFF));
   public static final String T2_NAME = "T2" + Integer.toHexString(rand.nextInt(0xFFFFFF));
   public static final String T3_NAME = "T3" + Integer.toHexString(rand.nextInt(0xFFFFFF));
-  public static final String[] Tx_NAMES = {T1_NAME, T2_NAME, T3_NAME};
 
   public static Root createRoot(A a, C c, B... bs) {
     Root result = new Root();
@@ -92,50 +91,9 @@ public class TestUtils {
     return result;
   }
 
-  public static TRoot createTRoot(A a, T1 t1, T2 t2, T3 t3) {
-    TRoot result = new TRoot();
-    result.setName(ROOT_NAME);
-    if (a != null) {
-      result.setA(a);
-    }
-    if (t1 != null) {
-      result.setT1(t1);
-    }
-    if (t2 != null) {
-      result.setT2(t2);
-    }
-    if (t3 != null) {
-      result.setT3(t3);
-    }
-    return result;
-  }
-
-  @SafeVarargs
-  public static T1 createT1(Consumer<AbstractT>... additionalSettings) {
-    return setupAbstractT(new T1(), T1_NAME, additionalSettings);
-  }
-
-  @SafeVarargs
-  public static T2 createT2(Consumer<AbstractT>... additionalSettings) {
-    return setupAbstractT(new T2(), T2_NAME, additionalSettings);
-  }
-
-  @SafeVarargs
-  public static T3 createT3(Consumer<AbstractT>... additionalSettings) {
-    return setupAbstractT(new T3(), T3_NAME, additionalSettings);
-  }
-
-  private static <T extends AbstractT> T setupAbstractT(T t, String name, Consumer<AbstractT>[] additionalSettings) {
-    t.setName(name);
-    for (Consumer<AbstractT> setting : additionalSettings) {
-      setting.accept(t);
-    }
-    return t;
-  }
-
   public static final Function<DumpNode, String> NAME_EXTRACTOR = node -> {
     for (DumpToken dumpToken : node.getDumpTokenList()) {
-      if (dumpToken.getName().equals("Name")) {
+      if (dumpToken.getLabel().equals("Name")) {
         return dumpToken.asDumpValueToken().getValue().toString();
       }
     }
@@ -150,6 +108,16 @@ public class TestUtils {
     return Assertions.assertThat(map.get(key)).flatExtracting(NAME_EXTRACTOR);
   }
 
+  public static void assertEmpty(DumpChildNode node) {
+    assertEquals(1, node.asDumpListChildNode().getNumInnerDumpNode());
+    assertTrue(node.asDumpListChildNode().getInnerDumpNode(0).getDumpNode().isEmpty());
+  }
+
+  public static void assertEmpty(DumpRelation node) {
+    assertEquals(1, node.asDumpListRelation().getNumInnerRelationDumpNode());
+    assertTrue(node.asDumpListRelation().getInnerRelationDumpNode(0).getDumpNode().isEmpty());
+  }
+
   public static List<DumpNode> dumpModel(Object target) throws TransformationException {
     return dumpModel(target, db -> {});
   }
@@ -188,7 +156,7 @@ public class TestUtils {
         // then it is a DumpNormalChildNode
         DumpNode target = ((DumpNormalChildNode) dumpChildNode).getDumpNode();
         if (target != null && !target.getInvisible()) {
-          result.put(dumpChildNode.getName(), target);
+          result.put(dumpChildNode.getLabel(), target);
         }
       }
     }
@@ -202,7 +170,7 @@ public class TestUtils {
         // then it is a DumpListChildNode
         ((DumpListChildNode) dumpChildNode).getInnerDumpNodeList().forEach(inner -> {
           if (inner.getDumpNode() != null && !inner.getDumpNode().getInvisible()) {
-            result.computeIfAbsent(dumpChildNode.getName(), key -> new ArrayList<>()).add(inner.getDumpNode());
+            result.computeIfAbsent(dumpChildNode.getLabel(), key -> new ArrayList<>()).add(inner.getDumpNode());
           }
         });
       }
@@ -217,7 +185,7 @@ public class TestUtils {
         // then it is a DumpNormalRelation
         DumpNode target = ((DumpNormalRelation) dumpRelation).getDumpNode();
         if (!target.getInvisible()) {
-          result.put(dumpRelation.getName(), target);
+          result.put(dumpRelation.getLabel(), target);
         }
       }
     }
@@ -231,7 +199,7 @@ public class TestUtils {
         // then it is a DumpListRelation
         ((DumpListRelation) dumpRelation).getInnerRelationDumpNodeList().forEach(inner -> {
           if (!inner.getDumpNode().getInvisible()) {
-            result.computeIfAbsent(dumpRelation.getName(), key -> new ArrayList<>()).add(inner.getDumpNode());
+            result.computeIfAbsent(dumpRelation.getLabel(), key -> new ArrayList<>()).add(inner.getDumpNode());
           }
         });
       }
@@ -244,9 +212,9 @@ public class TestUtils {
     for (DumpToken dumpToken : node.getDumpTokenList()) {
       if (!dumpToken.isDumpValueToken() && !dumpToken.isList()) {
         // then it is a DumpReferenceToken
-        DumpNode target = ((DumpReferenceToken) dumpToken).getValue();
+        DumpNode target = ((DumpReferenceToken) dumpToken).getDumpNode();
         if (!target.getInvisible()) {
-          result.put(dumpToken.getName(), target);
+          result.put(dumpToken.getLabel(), target);
         }
       }
     }
@@ -260,7 +228,7 @@ public class TestUtils {
         // then it is a DumpReferenceListToken
         ((DumpReferenceListToken) dumpToken).getInnerRelationDumpNodeList().forEach(inner -> {
           if (!inner.getDumpNode().getInvisible()) {
-            result.computeIfAbsent(dumpToken.getName(), key -> new ArrayList<>()).add(inner.getDumpNode());
+            result.computeIfAbsent(dumpToken.getLabel(), key -> new ArrayList<>()).add(inner.getDumpNode());
           }
         });
       }
@@ -274,7 +242,7 @@ public class TestUtils {
       if (dumpToken.isDumpValueToken()) {
         // then it is a DumpValueToken
         DumpValueToken dumpValueToken = (DumpValueToken) dumpToken;
-        result.put(dumpValueToken.getName(), dumpValueToken.getValue());
+        result.put(dumpValueToken.getLabel(), dumpValueToken.getValue());
       }
     }
     return result;
diff --git a/pages/docs/adding.md b/pages/docs/adding.md
index 2e2f9a84375a501e95467de1af6bd37e7f53d265..1babfde845699342c88a73f89413c80b72970496 100644
--- a/pages/docs/adding.md
+++ b/pages/docs/adding.md
@@ -4,7 +4,7 @@ Check the [package overview page](https://git-st.inf.tu-dresden.de/jastadd/dumpA
 
 To use `dumpAst`, adjust your `build.gradle` as follows.
 
-Set up the maven package source as repository:
+Set up the maven package source as a repository:
 
 ```
 repositories {
@@ -19,7 +19,7 @@ Add `dumpAst` as a dependency:
 
 ```
 dependencies {
-    implementation group: 'de.tudresden.inf.st', name: 'dumpAst', version: '1.2.0'
+    implementation group: 'de.tudresden.inf.st', name: 'dumpAst', version: '{{dumpAst_version()}}'
 }
 ```
 
@@ -38,7 +38,7 @@ ls dumpAst.base/build/libs/
 This jar can then be copied to your project.
 
 ```bash
-cp dumpAst.base/build/libs/dumpAst.base-fatJar-<version>.jar ../your-project/libs/dumpAst.jar
+cp dumpAst.base/build/libs/dumpAst.base-fatJar-{{dumpAst_version()}}.jar ../your-project/libs/dumpAst.jar
 cd ../your-project/
 ```
 
diff --git a/pages/docs/img/ST-black.png b/pages/docs/img/ST-black.png
new file mode 100644
index 0000000000000000000000000000000000000000..1dff814753e73b1d10ddd456817e6d72ee7b82e2
Binary files /dev/null and b/pages/docs/img/ST-black.png differ
diff --git a/pages/docs/index.md b/pages/docs/index.md
index 0910060203c01660960cf1904dcb8fe0a5ee3537..3d61a112efba6697ffab2feda8a1ff0b859ff515 100644
--- a/pages/docs/index.md
+++ b/pages/docs/index.md
@@ -1,6 +1,6 @@
 # DumpAst
 
-The tool called `DumpAst` ([see in repo](https://git-st.inf.tu-dresden.de/jastadd/dumpAst)) is used to create a snapshot of an AST and visualize it.
+The tool called `DumpAst` ([see in repo](https://git-st.inf.tu-dresden.de/jastadd/dumpAst)) is used to create a snapshot of an AST and visualise it.
 
 ![](img/dumpAst.png)
 
@@ -17,6 +17,6 @@ Then, read in the ASTNode in question:
 Dumper.read(astNode)
 ```
 
-Using the return value (a `DumpBuilder`), use methods to filter out unwanted parts, add styling or other settings.
+Using the return value (a `DumpBuilder`), use methods to filter out unwanted parts and add styling or other settings.
 All methods can be chained together.
-Please see the [API documentation](ragdoc/index.html) for more details.
+Please see [the subpage on using DumpAst](using.md) and the [API documentation](ragdoc/index.html) for more details.
diff --git a/pages/docs/using.md b/pages/docs/using.md
new file mode 100644
index 0000000000000000000000000000000000000000..f01ec77314b2cd245ca6e197dd0019852f99a2ca
--- /dev/null
+++ b/pages/docs/using.md
@@ -0,0 +1,65 @@
+# Using DumpAst
+
+After [adding DumpAst to your project](adding.md), you can start getting snapshot of some (part of an) AST.
+Here is one example with a `Model` containing nodes of type `Robot`, `Joint` and `EndEffector` amongst others.
+It is explained in detail below.
+
+```java
+import de.tudresden.inf.st.jastadd.dumpAst.ast.*;
+
+import your.model.Robot;
+import your.model.Model;
+import your.model.ASTNode;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+public class Main {
+  public static void main(String[] args) throws IOException, TransformationException {
+    Model model = createModel();  // (1)
+
+    DumpBuilder builder = Dumper.read(model)  // (2)
+        .includeChild((parentNode, childNode, contextName) -> {  // (3)
+          if (parentNode instanceof Robot && ((Robot) parentNode).getName().equals("Robot2")) {
+            return false;
+          }
+          return !contextName.equals("EndEffector");
+        })
+        .nodeStyle((node, style) -> {
+          style.useSimpleName();  // (4)
+          style.setBackgroundColor(node.size() > 30 ? "blue" : "white");  // (5)
+        })
+        .relationStyle((source, target, isComputed, isContainment, style) -> {
+          if (style.getLabel().equals("EndEffector")) {
+            style.setLabel("");  // (6)
+          }
+        })
+        .skinParam(SkinParamBooleanSetting.Shadowing, false);  // (7)
+    builder.dumpAsPng(Paths.get("featureTest.png"));  // (8)
+    builder.dumpAsSVG(Paths.get("featureTest.svg"));  // (9)
+    OutputStream os = getMyOutputStream();
+    builder.dumpAsPNG(os);  // (10)
+  }
+}
+```
+
+DumpAst uses the [Builder design pattern](https://en.wikipedia.org/wiki/Builder_pattern) as well as a [fluent API](https://en.wikipedia.org/wiki/Fluent_interface).
+That means, first, all settings are specified (2-6), and only after a "dump"-method is called (7,8) the actual snapshot is taken.
+For filtering the output, there are various lambda functions (`include___`).
+Those methods are available for children, attributes, relations and tokens.
+By default, all children, relations and tokens are included in the output, whereas all attributes are excluded.
+
+The steps in detail:
+
+- (1) In the beginning, your model is somehow constructed and changed. This is indicated with the method `createModel`.
+- (2) The first call is always `Dumper.read()` to which the (part of the) AST is provided as an argument. It returns an object of type [DumpBuilder](../ragdoc/#/type/DumpBuilder). With this object, the output can be changed.
+- (3) [Optional] You can only include nodes of certain types by using `includeChild` and passing a lambda function, which is called for every node and should return `true` if the `childNode` shall be included in the output. It defaults to returning `true` for every node. Here, all children of a `Robot` node (except if the name of the robot is `"Robot2"`) and all children of the context named `"EndEffector"` are excluded.
+- (4) and (5) [Optional] To style a node, the method `nodeStyle` is used. It accepts a lambda function, in which the style of every node can be customized by calling methods on the given `Style` object, e.g. setting the name (5) or background color (6). The default name is to use the class name along with the hashcode of the object (`node == null ? "null" : node.getClass().getSimpleName() + "@" + Integer.toHexString(node.hashCode())`).
+- (6) [Optional] To style a relation, the method `relationStyle` is used. It is used similar to `nodeStyle`, but is called with source and target node of a relation (containment and non-containment). Note, that (a) the default label can be retrieved with `style.getLabel()`, (b) `target` can be `null` for lists, (c) secondary connections from a list node to the target nodes use the style of the relation and use their index as label.
+- (7) [Optional] There are a few ways to style the output in general through [SkinParam](https://plantuml.com/de/skinparam). Not all of those parameters are supported; see `SkinParamBooleanSetting` and `SkinParamStringSetting`.
+- (8), (9) and (10) To create an output image, use `dumpAsPng` for PNG (`dumpAsSVG` for SVG). Those methods do not return the builder object, as they produce an output. They potentially throw two kinds of exception: `IOException` (when the file could not be written) and `TransformationException` (when the AST could not be processed). As shown here, the builder object must be stored in a variable to create multiple outputs. Alternatively to a file, the result can be written to a given `OutputStream` (10).
+
+To summarise the required steps: Specify the AST to read in, configure the output, and write ("dump") the output.
+For more configuration options, please consult the [API Docs of DumpBuilder](../ragdoc/#/type/DumpBuilder) or check out [FeatureTestMain](https://git-st.inf.tu-dresden.de/jastadd/dumpAst/-/blob/main/dumpAst.prototyping/src/main/java/de/tudresden/inf/st/jastadd/featureTest/FeatureTestMain.java).
diff --git a/pages/mkdocs.yml b/pages/mkdocs.yml
index ac59250d32152419ed9a44f44d83207f2d954397..6f315aca9d3fd598aeae76d279fc9d57b9315fea 100644
--- a/pages/mkdocs.yml
+++ b/pages/mkdocs.yml
@@ -5,11 +5,26 @@ site_dir: ../public
 nav:
   - "DumpAst": index.md
   - "Add to your project": adding.md
+  - "Using DumpAst": using.md
   - "API documentation": ragdoc/index.html
 
 theme:
-  name: readthedocs
-  custom_dir: custom_theme/
+  name: material
+  font: false
+  palette: 
+    # Palette toggle for light mode
+    - scheme: default
+      toggle:
+        icon: material/brightness-7 
+        name: Switch to dark mode
+    # Palette toggle for dark mode
+    - scheme: slate
+      toggle:
+        icon: material/brightness-4
+        name: Switch to light mode
+  favicon: img/ST-black.png
+  icon:
+    logo: material/dump-truck
 
 markdown_extensions:
   - toc:
diff --git a/pages/requirements.txt b/pages/requirements.txt
index d5ae2cc06eb26afe413c3bb61d067fb9f4d1db58..25d7d3180e71c0bcdc15dc8bf7d53d08dec06173 100644
--- a/pages/requirements.txt
+++ b/pages/requirements.txt
@@ -1,5 +1,7 @@
-mkdocs==1.2.2
-mkdocs-git-revision-date-localized-plugin==0.10.3
-mkdocs-macros-plugin==0.6.3
-Jinja2==2.11.2
-MarkupSafe==1.1.1
+mkdocs==1.4.2
+mkdocs-git-revision-date-localized-plugin==1.1.0
+mkdocs-macros-plugin==0.7.0
+mkdocs-material==8.5.10
+mkdocs-material-extensions==1.1
+Jinja2==3.1.2
+MarkupSafe==2.1.1