diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index d07299bade9f50d37d4f4f7631238f18a08caeca..52200cdef6c313fa1c9122dccb9495960b49d7c0 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -45,6 +45,7 @@ publish_master:
   script:
     - "./gradlew publish"
   only:
+    - main
     - master
 
 ragdoc_build:
@@ -55,7 +56,7 @@ ragdoc_build:
   needs:
     - build
   script:
-    - JAVA_FILES=$(find dumpAstWithPlantuml/src/ -name '*.java')
+    - JAVA_FILES=$(find dumpAst/src/ -name '*.java')
     - /ragdoc-builder/start-builder.sh -excludeGenerated -d data/ $JAVA_FILES
   artifacts:
     paths:
@@ -77,6 +78,7 @@ ragdoc_view:
   only:
     - dev
     - main
+    - master
   artifacts:
     paths:
       - "pages/docs/ragdoc"
@@ -96,3 +98,4 @@ pages:
       - public/
   only:
     - main
+    - master
diff --git a/README.md b/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..1b001cb8f7f67d2362aff1ea9f911948dbd1adde
--- /dev/null
+++ b/README.md
@@ -0,0 +1,3 @@
+# DumpAst
+
+For documentation, please see https://jastadd.pages.st.inf.tu-dresden.de/relast2uml/
diff --git a/buildSrc/src/main/groovy/relast2uml.java-application-conventions.gradle b/buildSrc/src/main/groovy/relast2uml.java-application-conventions.gradle
index a32686b88da9b321aa62cb18ddc319a12b9e483c..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644
--- a/buildSrc/src/main/groovy/relast2uml.java-application-conventions.gradle
+++ b/buildSrc/src/main/groovy/relast2uml.java-application-conventions.gradle
@@ -1,4 +0,0 @@
-plugins {
-  id 'relast2uml.java-common-conventions'
-  id 'application'
-}
diff --git a/buildSrc/src/main/groovy/relast2uml.java-common-conventions.gradle b/buildSrc/src/main/groovy/relast2uml.java-common-conventions.gradle
index 644c9bd4588c608573019e72d799a760d75a88dc..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644
--- a/buildSrc/src/main/groovy/relast2uml.java-common-conventions.gradle
+++ b/buildSrc/src/main/groovy/relast2uml.java-common-conventions.gradle
@@ -1,19 +0,0 @@
-plugins {
-  id 'java'
-  id 'idea'
-  id 'com.github.ben-manes.versions'
-}
-
-repositories {
-  mavenCentral()
-}
-
-dependencies {
-  testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: "${jupiter_version}"
-  testImplementation group: 'org.assertj', name: 'assertj-core', version: '3.18.1'
-  testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: "${jupiter_version}"
-}
-
-tasks.named('test') {
-  useJUnitPlatform()
-}
diff --git a/buildSrc/src/main/groovy/relast2uml.java-jastadd-conventions.gradle b/buildSrc/src/main/groovy/relast2uml.java-jastadd-conventions.gradle
index d773a87cb157572d2712c04e39b91ad4a4169c0b..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644
--- a/buildSrc/src/main/groovy/relast2uml.java-jastadd-conventions.gradle
+++ b/buildSrc/src/main/groovy/relast2uml.java-jastadd-conventions.gradle
@@ -1,12 +0,0 @@
-plugins {
-  id 'relast2uml.java-common-conventions'
-  id 'java-library'
-}
-
-dependencies {
-  api group: 'net.sf.beaver', name: 'beaver-rt', version: '0.9.11'
-}
-
-File genSrc = file("src/gen/java")
-sourceSets.main.java.srcDir genSrc
-idea.module.generatedSourceDirs += genSrc
diff --git a/buildSrc/src/main/groovy/relast2uml.java-publishing-conventions.gradle b/buildSrc/src/main/groovy/relast2uml.java-publishing-conventions.gradle
index e3130c9008342d8dc2be8d80e2b29ba064cb31fa..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644
--- a/buildSrc/src/main/groovy/relast2uml.java-publishing-conventions.gradle
+++ b/buildSrc/src/main/groovy/relast2uml.java-publishing-conventions.gradle
@@ -1,88 +0,0 @@
-plugins {
-  id 'java'
-  id 'idea'
-  id 'com.github.ben-manes.versions'
-  id 'maven-publish'
-}
-
-repositories {
-  mavenCentral()
-}
-
-dependencies {
-  testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: "${jupiter_version}"
-  testImplementation group: 'org.assertj', name: 'assertj-core', version: '3.18.1'
-  testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: "${jupiter_version}"
-}
-
-jar {
-  from {
-    configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
-  }
-}
-
-def versionFile = "src/main/resources/${project.getName()}Version.properties"
-def oldProps = new Properties()
-
-try {
-  file(versionFile).withInputStream { stream -> oldProps.load(stream) }
-  version = oldProps['version']
-} catch (e) {
-  // this happens, if either the properties file is not present, or cannot be read from
-  throw new GradleException("File ${versionFile} not found or unreadable. Aborting.", e)
-}
-
-task printVersion() {
-  doLast {
-    println(version)
-  }
-}
-
-task newVersion() {
-  doFirst {
-    def props = new Properties()
-    props['version'] = value
-    props.store(file(versionFile).newWriter(), null)
-  }
-}
-
-task setDevVersionForCI() {
-  doFirst {
-    def props = new Properties()
-    props['version'] = version + "-$System.env.CI_PIPELINE_IID"
-    props.store(file(versionFile).newWriter(), null)
-  }
-}
-
-//679
-publishing {
-  publications {
-    maven(MavenPublication) {
-      groupId = 'de.tudresden.inf.st'
-//      from components.java
-      artifact("build/libs/${project.getName()}-${project.getVersion()}.jar") {
-          extension 'jar'
-      }
-    }
-  }
-  repositories {
-    maven {
-      url "https://git-st.inf.tu-dresden.de/api/v4/projects/679/packages/maven"
-      // Uncomment the following lines to publish manually (and comment out the other credentials section)
-//            credentials(HttpHeaderCredentials) {
-//                name = "Private-Token"
-//                value = gitLabPrivateToken // the variable resides in ~/.gradle/gradle.properties
-//            }
-      credentials(HttpHeaderCredentials) {
-        name = 'Job-Token'
-        value = System.getenv("CI_JOB_TOKEN")
-      }
-      authentication {
-        header(HttpHeaderAuthentication)
-      }
-    }
-
-  }
-}
-
-publish.dependsOn jar
diff --git a/dumpAst/build.gradle b/dumpAst/build.gradle
index b6793149b06ba134d59ad0886d55eda83be58b20..39e370c1df74809b6993201fd2082b35294e63b3 100644
--- a/dumpAst/build.gradle
+++ b/dumpAst/build.gradle
@@ -1,3 +1,4 @@
+// --- Buildscripts (must be at the top) ---
 buildscript {
     repositories.mavenLocal()
     repositories.mavenCentral()
@@ -6,18 +7,35 @@ buildscript {
     }
 }
 
+// --- Plugin definitions ---
 plugins {
-    id 'relast2uml.java-jastadd-conventions'
-    id 'relast2uml.java-publishing-conventions'
+    id 'java'
+    id 'idea'
+    id 'com.github.ben-manes.versions'
+    id 'java-library'
+    id 'maven-publish'
 }
 
 apply plugin: 'jastadd'
 
+// --- Dependencies ---
+repositories {
+    mavenCentral()
+}
+
 dependencies {
-    jastadd2 "org.jastadd:jastadd:2.3.4"
-    implementation group: 'com.github.spullara.mustache.java', name: 'compiler', version: "${mustache_java_version}"
+    jastadd2 "org.jastadd:jastadd:2.3.5"
+    implementation fileTree(include: ['plantuml.jar'], dir: '../libs')
+    api group: 'net.sf.beaver', name: 'beaver-rt', version: '0.9.11'
+    implementation group: 'com.github.spullara.mustache.java', name: 'compiler', version: "0.9.10"
+    implementation group: 'org.yaml', name: 'snakeyaml', version: '1.27'
 }
 
+// --- Preprocessors ---
+File genSrc = file("src/gen/java")
+sourceSets.main.java.srcDir genSrc
+idea.module.generatedSourceDirs += genSrc
+
 File dumpAstGrammar = file('./src/main/jastadd/DumpAst.relast')
 File mustacheGrammar = file('./src/main/jastadd/mustache/Mustache.relast')
 
@@ -54,6 +72,7 @@ task relast(type: JavaExec) {
             file('./src/gen/jastadd/DumpAstResolverStubs.jrag'))
 }
 
+// --- JastAdd ---
 jastadd {
     configureModuleBuild()
     modules {
@@ -96,4 +115,70 @@ jastadd {
     jastaddOptions = ["--lineColumnNumbers", "--List=JastAddList", "--safeLazy", "--visitCheck=true", "--rewrite=cnta", "--cache=all"]
 }
 
+// --- Tests ---
+
+// --- Versioning and Publishing ---
+group = 'de.tudresden.inf.st'
+
+jar {
+    from {
+        configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
+    }
+}
+
+def versionFile = "src/main/resources/${project.getName()}Version.properties"
+
+try {
+    def oldProps = new Properties()
+    file(versionFile).withInputStream { stream -> oldProps.load(stream) }
+    version = oldProps['version']
+} catch (e) {
+    // this happens, if either the properties file is not present, or cannot be read from
+    throw new GradleException("File ${versionFile} not found or unreadable. Aborting.", e)
+}
+
+task printVersion() {
+    doLast {
+        println(version)
+    }
+}
+
+task newVersion() {
+    doFirst {
+        def props = new Properties()
+        props['version'] = value
+        props.store(file(versionFile).newWriter(), null)
+    }
+}
+
+task setDevVersionForCI() {
+    doFirst {
+        def props = new Properties()
+        props['version'] = version + "-$System.env.CI_PIPELINE_IID"
+        props.store(file(versionFile).newWriter(), null)
+    }
+}
+
+publishing {
+    publications {
+        maven(MavenPublication) {
+            from components.java
+        }
+    }
+    repositories {
+        maven {
+            url "https://git-st.inf.tu-dresden.de/api/v4/projects/$System.env.CI_PROJECT_ID/packages/maven"
+            credentials(HttpHeaderCredentials) {
+                name = 'Job-Token'
+                value = System.getenv("CI_JOB_TOKEN")
+            }
+            authentication {
+                header(HttpHeaderAuthentication)
+            }
+        }
+    }
+}
+
+// --- Task order ---
 generateAst.dependsOn relast
+publish.dependsOn jar
diff --git a/dumpAst/src/main/jastadd/DumpAst.relast b/dumpAst/src/main/jastadd/DumpAst.relast
index 2fad59a121f72fb3629dda4bdbf7733a797ae1fa..3d76a4b0c5fb31fd428b970c8248da607f3126a8 100644
--- a/dumpAst/src/main/jastadd/DumpAst.relast
+++ b/dumpAst/src/main/jastadd/DumpAst.relast
@@ -1,18 +1,8 @@
 DumpAst ::= DumpNode* <PackageName> BuildConfig PrintConfig ;
-rel DumpAst.RootNode -> DumpNode ;
-
-BuildConfig ::= StyleInformation GlobalPatternCollection:PatternCollection
- ExcludeTypePattern:TypePatternCollectionMapping* IncludeTypePattern:TypePatternCollectionMapping*
- <TypeIgnorePattern> <IncludeEmptyString:boolean> <ExcludeNullNodes:boolean> <Debug:boolean>;
-TypePatternCollectionMapping ::= <TypeRegex> PatternCollection ;
-PatternCollection ::= <TokenPattern> <ChildPattern> <RelationPattern> <AttributePattern> <NonterminalAttributePattern> ;
-StyleInformation ::= <NameMethod:StyleMethod> <BackgroundColorMethod:StyleMethod> <TextColorMethod:StyleMethod>;
-
-PrintConfig ::= <Scale:double> <Version> <OrderChildren:boolean> Header* ;
-Header ::= <Value> ;
+rel DumpAst.RootNode? -> DumpNode ;
 
 DumpNode ::=  DumpChildNode* DumpToken* DumpRelation*
- <Name> <Label> <BackgroundColor> <TextColor> <Object:Object> <Invisible:boolean>
+ <Name> <Label> <BackgroundColor> <TextColor> <Object:Object> <Invisible:boolean> <Computed:boolean> <ManualStereotypes>
  /InvisiblePath/ ;
 InnerDumpNode ;
 rel InnerDumpNode.DumpNode <-> DumpNode.ContainerOfInner ;
@@ -35,7 +25,7 @@ DumpListRelation : DumpRelation ::= InnerDumpNode* ;
 // type of NTA
 InvisiblePath ::= InnerDumpNode* ;
 
-ClassAnalysisResult ::= AnalysedMethod* ;
+ClassAnalysisResult ::= ContainmentMethod:AnalysedMethod* OtherMethod:AnalysedMethod* ;
 abstract AnalysedMethod ::= <Method:java.lang.reflect.Method> <Name> ;
 
 abstract SingleChildMethod : AnalysedMethod ;
@@ -52,3 +42,21 @@ ListRelationMethod : AnalysedMethod ;
 abstract TokenMethod : AnalysedMethod ;
 IntrinsicTokenMethod : TokenMethod ;
 AttributeMethod : TokenMethod ;
+
+BuildConfig ::= StyleInformation
+ GlobalPatternCollection:PatternCollection
+ ExcludeTypePattern:TypePatternCollectionMapping*
+ IncludeTypePattern:TypePatternCollectionMapping*
+ <TypeIgnorePattern>
+ <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>;
+
+PrintConfig ::= Header*
+ <Scale:double>
+ <Version>
+ <OrderChildren:boolean> ;
+Header ::= <Value> ;
diff --git a/dumpAst/src/main/jastadd/GeneratedNavigation.jrag b/dumpAst/src/main/jastadd/GeneratedNavigation.jrag
new file mode 100644
index 0000000000000000000000000000000000000000..be7166a650a2409f5b92c914c348ca1972a64a59
--- /dev/null
+++ b/dumpAst/src/main/jastadd/GeneratedNavigation.jrag
@@ -0,0 +1,224 @@
+aspect Navigation {
+
+  /** Tests if TokenMethod is a IntrinsicTokenMethod.
+  *  @return 'true' if this is a IntrinsicTokenMethod, otherwise 'false'
+  */
+  syn boolean TokenMethod.isIntrinsicTokenMethod() = false;
+  eq IntrinsicTokenMethod.isIntrinsicTokenMethod() = true;
+
+  /** Tests if TokenMethod is a AttributeMethod.
+  *  @return 'true' if this is a AttributeMethod, otherwise 'false'
+  */
+  syn boolean TokenMethod.isAttributeMethod() = false;
+  eq AttributeMethod.isAttributeMethod() = true;
+
+  /** Tests if AnalysedMethod is a SingleChildMethod.
+  *  @return 'true' if this is a SingleChildMethod, otherwise 'false'
+  */
+  syn boolean AnalysedMethod.isSingleChildMethod() = false;
+  eq SingleChildMethod.isSingleChildMethod() = true;
+
+  /** Tests if AnalysedMethod is a ListChildMethod.
+  *  @return 'true' if this is a ListChildMethod, otherwise 'false'
+  */
+  syn boolean AnalysedMethod.isListChildMethod() = false;
+  eq ListChildMethod.isListChildMethod() = true;
+
+  /** Tests if AnalysedMethod is a SingleRelationMethod.
+  *  @return 'true' if this is a SingleRelationMethod, otherwise 'false'
+  */
+  syn boolean AnalysedMethod.isSingleRelationMethod() = false;
+  eq SingleRelationMethod.isSingleRelationMethod() = true;
+
+  /** Tests if AnalysedMethod is a ListRelationMethod.
+  *  @return 'true' if this is a ListRelationMethod, otherwise 'false'
+  */
+  syn boolean AnalysedMethod.isListRelationMethod() = false;
+  eq ListRelationMethod.isListRelationMethod() = true;
+
+  /** Tests if AnalysedMethod is a TokenMethod.
+  *  @return 'true' if this is a TokenMethod, otherwise 'false'
+  */
+  syn boolean AnalysedMethod.isTokenMethod() = false;
+  eq TokenMethod.isTokenMethod() = true;
+
+  /** Tests if DumpRelation is a DumpNormalRelation.
+  *  @return 'true' if this is a DumpNormalRelation, otherwise 'false'
+  */
+  syn boolean DumpRelation.isDumpNormalRelation() = false;
+  eq DumpNormalRelation.isDumpNormalRelation() = true;
+
+  /** Tests if DumpRelation is a DumpListRelation.
+  *  @return 'true' if this is a DumpListRelation, otherwise 'false'
+  */
+  syn boolean DumpRelation.isDumpListRelation() = false;
+  eq DumpListRelation.isDumpListRelation() = true;
+
+  /** Tests if DumpChildNode is a DumpNormalChildNode.
+  *  @return 'true' if this is a DumpNormalChildNode, otherwise 'false'
+  */
+  syn boolean DumpChildNode.isDumpNormalChildNode() = false;
+  eq DumpNormalChildNode.isDumpNormalChildNode() = true;
+
+  /** Tests if DumpChildNode is a DumpListChildNode.
+  *  @return 'true' if this is a DumpListChildNode, otherwise 'false'
+  */
+  syn boolean DumpChildNode.isDumpListChildNode() = false;
+  eq DumpListChildNode.isDumpListChildNode() = true;
+
+  /** Tests if ListChildMethod is a NormalListChildMethod.
+  *  @return 'true' if this is a NormalListChildMethod, otherwise 'false'
+  */
+  syn boolean ListChildMethod.isNormalListChildMethod() = false;
+  eq NormalListChildMethod.isNormalListChildMethod() = true;
+
+  /** Tests if ListChildMethod is a NTAListChildMethod.
+  *  @return 'true' if this is a NTAListChildMethod, otherwise 'false'
+  */
+  syn boolean ListChildMethod.isNTAListChildMethod() = false;
+  eq NTAListChildMethod.isNTAListChildMethod() = true;
+
+  /** Tests if SingleChildMethod is a NormalSingleChildMethod.
+  *  @return 'true' if this is a NormalSingleChildMethod, otherwise 'false'
+  */
+  syn boolean SingleChildMethod.isNormalSingleChildMethod() = false;
+  eq NormalSingleChildMethod.isNormalSingleChildMethod() = true;
+
+  /** Tests if SingleChildMethod is a NTASingleChildMethod.
+  *  @return 'true' if this is a NTASingleChildMethod, otherwise 'false'
+  */
+  syn boolean SingleChildMethod.isNTASingleChildMethod() = false;
+  eq NTASingleChildMethod.isNTASingleChildMethod() = true;
+
+  /** Tests if DumpToken is a DumpReferenceToken.
+  *  @return 'true' if this is a DumpReferenceToken, otherwise 'false'
+  */
+  syn boolean DumpToken.isDumpReferenceToken() = false;
+  eq DumpReferenceToken.isDumpReferenceToken() = true;
+
+  /** Tests if DumpToken is a DumpValueToken.
+  *  @return 'true' if this is a DumpValueToken, otherwise 'false'
+  */
+  syn boolean DumpToken.isDumpValueToken() = false;
+  eq DumpValueToken.isDumpValueToken() = true;
+
+  /** casts a TokenMethod into a IntrinsicTokenMethod if possible.
+   *  @return 'this' cast to a IntrinsicTokenMethod or 'null'
+   */
+  syn IntrinsicTokenMethod TokenMethod.asIntrinsicTokenMethod();
+  eq TokenMethod.asIntrinsicTokenMethod() = null;
+  eq IntrinsicTokenMethod.asIntrinsicTokenMethod() = this;
+
+  /** casts a TokenMethod into a AttributeMethod if possible.
+   *  @return 'this' cast to a AttributeMethod or 'null'
+   */
+  syn AttributeMethod TokenMethod.asAttributeMethod();
+  eq TokenMethod.asAttributeMethod() = null;
+  eq AttributeMethod.asAttributeMethod() = this;
+
+  /** casts a AnalysedMethod into a SingleChildMethod if possible.
+   *  @return 'this' cast to a SingleChildMethod or 'null'
+   */
+  syn SingleChildMethod AnalysedMethod.asSingleChildMethod();
+  eq AnalysedMethod.asSingleChildMethod() = null;
+  eq SingleChildMethod.asSingleChildMethod() = this;
+
+  /** casts a AnalysedMethod into a ListChildMethod if possible.
+   *  @return 'this' cast to a ListChildMethod or 'null'
+   */
+  syn ListChildMethod AnalysedMethod.asListChildMethod();
+  eq AnalysedMethod.asListChildMethod() = null;
+  eq ListChildMethod.asListChildMethod() = this;
+
+  /** casts a AnalysedMethod into a SingleRelationMethod if possible.
+   *  @return 'this' cast to a SingleRelationMethod or 'null'
+   */
+  syn SingleRelationMethod AnalysedMethod.asSingleRelationMethod();
+  eq AnalysedMethod.asSingleRelationMethod() = null;
+  eq SingleRelationMethod.asSingleRelationMethod() = this;
+
+  /** casts a AnalysedMethod into a ListRelationMethod if possible.
+   *  @return 'this' cast to a ListRelationMethod or 'null'
+   */
+  syn ListRelationMethod AnalysedMethod.asListRelationMethod();
+  eq AnalysedMethod.asListRelationMethod() = null;
+  eq ListRelationMethod.asListRelationMethod() = this;
+
+  /** casts a AnalysedMethod into a TokenMethod if possible.
+   *  @return 'this' cast to a TokenMethod or 'null'
+   */
+  syn TokenMethod AnalysedMethod.asTokenMethod();
+  eq AnalysedMethod.asTokenMethod() = null;
+  eq TokenMethod.asTokenMethod() = this;
+
+  /** casts a DumpRelation into a DumpNormalRelation if possible.
+   *  @return 'this' cast to a DumpNormalRelation or 'null'
+   */
+  syn DumpNormalRelation DumpRelation.asDumpNormalRelation();
+  eq DumpRelation.asDumpNormalRelation() = null;
+  eq DumpNormalRelation.asDumpNormalRelation() = this;
+
+  /** casts a DumpRelation into a DumpListRelation if possible.
+   *  @return 'this' cast to a DumpListRelation or 'null'
+   */
+  syn DumpListRelation DumpRelation.asDumpListRelation();
+  eq DumpRelation.asDumpListRelation() = null;
+  eq DumpListRelation.asDumpListRelation() = this;
+
+  /** casts a DumpChildNode into a DumpNormalChildNode if possible.
+   *  @return 'this' cast to a DumpNormalChildNode or 'null'
+   */
+  syn DumpNormalChildNode DumpChildNode.asDumpNormalChildNode();
+  eq DumpChildNode.asDumpNormalChildNode() = null;
+  eq DumpNormalChildNode.asDumpNormalChildNode() = this;
+
+  /** casts a DumpChildNode into a DumpListChildNode if possible.
+   *  @return 'this' cast to a DumpListChildNode or 'null'
+   */
+  syn DumpListChildNode DumpChildNode.asDumpListChildNode();
+  eq DumpChildNode.asDumpListChildNode() = null;
+  eq DumpListChildNode.asDumpListChildNode() = this;
+
+  /** casts a ListChildMethod into a NormalListChildMethod if possible.
+   *  @return 'this' cast to a NormalListChildMethod or 'null'
+   */
+  syn NormalListChildMethod ListChildMethod.asNormalListChildMethod();
+  eq ListChildMethod.asNormalListChildMethod() = null;
+  eq NormalListChildMethod.asNormalListChildMethod() = this;
+
+  /** casts a ListChildMethod into a NTAListChildMethod if possible.
+   *  @return 'this' cast to a NTAListChildMethod or 'null'
+   */
+  syn NTAListChildMethod ListChildMethod.asNTAListChildMethod();
+  eq ListChildMethod.asNTAListChildMethod() = null;
+  eq NTAListChildMethod.asNTAListChildMethod() = this;
+
+  /** casts a SingleChildMethod into a NormalSingleChildMethod if possible.
+   *  @return 'this' cast to a NormalSingleChildMethod or 'null'
+   */
+  syn NormalSingleChildMethod SingleChildMethod.asNormalSingleChildMethod();
+  eq SingleChildMethod.asNormalSingleChildMethod() = null;
+  eq NormalSingleChildMethod.asNormalSingleChildMethod() = this;
+
+  /** casts a SingleChildMethod into a NTASingleChildMethod if possible.
+   *  @return 'this' cast to a NTASingleChildMethod or 'null'
+   */
+  syn NTASingleChildMethod SingleChildMethod.asNTASingleChildMethod();
+  eq SingleChildMethod.asNTASingleChildMethod() = null;
+  eq NTASingleChildMethod.asNTASingleChildMethod() = this;
+
+  /** casts a DumpToken into a DumpReferenceToken if possible.
+   *  @return 'this' cast to a DumpReferenceToken or 'null'
+   */
+  syn DumpReferenceToken DumpToken.asDumpReferenceToken();
+  eq DumpToken.asDumpReferenceToken() = null;
+  eq DumpReferenceToken.asDumpReferenceToken() = this;
+
+  /** casts a DumpToken into a DumpValueToken if possible.
+   *  @return 'this' cast to a DumpValueToken or 'null'
+   */
+  syn DumpValueToken DumpToken.asDumpValueToken();
+  eq DumpToken.asDumpValueToken() = null;
+  eq DumpValueToken.asDumpValueToken() = this;
+
+}
diff --git a/dumpAst/src/main/jastadd/GenerationBackend.jadd b/dumpAst/src/main/jastadd/GenerationBackend.jadd
index beeb183de52a4adccbdb8413309b16c418b55511..be9aaff9334285645c3e4581838566d683879f5c 100644
--- a/dumpAst/src/main/jastadd/GenerationBackend.jadd
+++ b/dumpAst/src/main/jastadd/GenerationBackend.jadd
@@ -1,14 +1,62 @@
 aspect GenerationBackend {
   class DumpAst {
-    private enum Source {
-      RELATION, INVISIBLE_PARENT, PARENT, ROOT
+    enum Source {
+      ROOT, NORMAL, RELATION
+    }
+    class TransformationOptions {
+      // todo should be read-only, i.e., copy-on-write
+      public Source source;
+      public boolean invisible;
+      public boolean allowNullObjects;
+      public boolean computed;
+
+      public TransformationOptions asRelation() {
+        return fromSource(Source.RELATION, false);
+      }
+
+      public TransformationOptions asRoot() {
+        return fromSource(Source.ROOT, false).allowNullObjectsOnce();
+      }
+
+      public TransformationOptions asNormal(boolean shouldBeInvisible) {
+        return fromSource(Source.NORMAL, shouldBeInvisible);
+      }
+
+      public TransformationOptions computed(boolean computed) {
+        if (computed == this.computed) {
+          return this;
+        }
+        return new TransformationOptions(this.source, this.invisible, this.allowNullObjects, computed);
+      }
+
+      public TransformationOptions allowNullObjectsOnce() {
+        return new TransformationOptions(this.source, this.invisible, !DumpAst.this.getBuildConfig().getExcludeNullNodes(), this.computed);
+      }
+
+      private TransformationOptions fromSource(Source source, boolean shouldBeInvisible) {
+        return new TransformationOptions(source, this.invisible || shouldBeInvisible, this.computed);
+      }
+
+      private TransformationOptions(Source source, boolean invisible, boolean computed) {
+        this(source, invisible, false, computed);
+      }
+      private TransformationOptions(Source source, boolean invisible, boolean allowNullObjects, boolean computed) {
+        this.source = source;
+        this.invisible = invisible;
+        this.allowNullObjects = allowNullObjects;
+        this.computed = computed;
+      }
     }
   }
 
+  private TransformationOptions DumpAst.options() {
+    return new TransformationOptions(Source.NORMAL, false, false);
+  }
+
   // --- 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 {
-    DumpNode result = transform(tti, obj, Source.ROOT);
+    DumpNode result = transform(tti, obj, options().asRoot());
     setRootNode(result);
     // post-process relationTargetsUnprocessed
     boolean someAreUnprocessed = true;
@@ -17,21 +65,18 @@ aspect GenerationBackend {
       java.util.Map<DumpNode, Boolean> copy = new java.util.HashMap<>(tti.relationTargetsUnprocessed);
       for (java.util.Map.Entry<DumpNode, Boolean> entry : copy.entrySet()) {
         if (entry.getValue()) {
-          transform(tti, entry.getKey().getObject(), Source.ROOT);
+          transform(tti, entry.getKey().getObject(), options().asRoot());
           someAreUnprocessed = true;
         }
       }
     }
     return result;
   }
-  protected DumpNode DumpAst.transform(TransformationTransferInformation tti, Object obj, Source source)
-      throws java.lang.reflect.InvocationTargetException, IllegalAccessException, NoSuchMethodException {
-    return transform(tti, obj, source, false);
-  }
+
   protected DumpNode DumpAst.transform(
-      TransformationTransferInformation tti, Object obj, Source source, boolean allowNullObj)
+      TransformationTransferInformation tti, Object obj, TransformationOptions options)
       throws java.lang.reflect.InvocationTargetException, IllegalAccessException, NoSuchMethodException {
-    if (obj == null && !allowNullObj) {
+    if (obj == null && !options.allowNullObjects) {
       return null;
     }
     DumpNode node;
@@ -44,7 +89,7 @@ aspect GenerationBackend {
       objClassName = obj.getClass().getSimpleName();
     }
     if (node != null) {
-      if (source == Source.RELATION) {
+      if (options.source == Source.RELATION) {
         return node;
       }
       // either processing as parent, or later as root-node. so mark it as processed.
@@ -52,7 +97,8 @@ aspect GenerationBackend {
     } else {
       node = new DumpNode();
       node.setObject(obj);
-      node.setName("node" + tti.transformed.size());
+      node.setName("node" + (tti.nodeCounter++));
+      node.setComputed(options.computed);
       tti.transformed.put(obj, node);
       this.addDumpNode(node);
     }
@@ -61,11 +107,11 @@ aspect GenerationBackend {
     }
     applyStyle(node);
     // do not process node further if coming from a relation
-    if (source == Source.RELATION) {
+    if (options.source == Source.RELATION) {
       tti.relationTargetsUnprocessed.put(node, true);
       return node;
     }
-    if (source == Source.INVISIBLE_PARENT || !isTypeEnabled(objClassName)) {
+    if (options.invisible || !isTypeEnabled(objClassName)) {
       node.setInvisible(true);
     }
     if (obj == null) {
@@ -73,85 +119,121 @@ aspect GenerationBackend {
       return node;
     }
     final ClassAnalysisResult car = analyzeClass(obj.getClass());
-    // -- singleChild --
-    for (SingleChildMethod singleChildMethod : car.singleChildMethods()) {
-      Object target = singleChildMethod.getMethod().invoke(obj);
-      String childName = singleChildMethod.getName();
-      DumpNode targetNode = transform(tti, target, nextSource(source, !isChildEnabled(objClassName, childName)), !getBuildConfig().getExcludeNullNodes());
-      if (targetNode != null) {
-        DumpNormalChildNode normalChild = new DumpNormalChildNode();
-        normalChild.setName(childName);
-        normalChild.setDumpNode(targetNode);
-        normalChild.setComputed(singleChildMethod.isNTASingleChildMethod());
-        node.addDumpChildNode(normalChild);
-      }
-    }
-    // -- listChild --
-    for (ListChildMethod listChildMethod : car.listChildMethods()) {
-      Iterable<?> targetList = (Iterable<?>) listChildMethod.getMethod().invoke(obj);
-      DumpListChildNode listChild = new DumpListChildNode();
-      listChild.setComputed(listChildMethod.isNTAListChildMethod());
-      String childName = listChildMethod.getName();
-      boolean shouldBeInvisisble = !isChildEnabled(objClassName, childName);
-      listChild.setName(childName);
-      for (Object target : targetList) {
-        DumpNode targetNode = transform(tti, target, nextSource(source, shouldBeInvisisble));
-        if (target != null && targetNode != null) {
-          listChild.addInnerDumpNode(new InnerDumpNode(targetNode));
+    for (AnalysedMethod containmentMethod : car.getContainmentMethodList()) {
+      if (containmentMethod.isSingleChildMethod()) {
+        // -- singleChild --
+        Object target = containmentMethod.getMethod().invoke(obj);
+        String childName = containmentMethod.getName();
+        DumpNode targetNode = transform(tti, target, options.asNormal(!isChildEnabled(objClassName, childName)).allowNullObjectsOnce());
+        if (targetNode != null) {
+          DumpNormalChildNode normalChild = new DumpNormalChildNode();
+          normalChild.setName(childName);
+          normalChild.setDumpNode(targetNode);
+          normalChild.setComputed(false);
+          node.addDumpChildNode(normalChild);
         }
-      }
-      if (listChild.getNumInnerDumpNode() > 0) {
-        node.addDumpChildNode(listChild);
-      }
-    }
-    // -- singleRelation --
-    for (SingleRelationMethod singleRelationMethod : car.singleRelationMethods()) {
-      Object target = singleRelationMethod.getMethod().invoke(obj);
-      DumpNode targetNode = transform(tti, target, Source.RELATION);
-      if (target != null && targetNode != null) {
-        DumpNormalRelation normalRelation = new DumpNormalRelation();
-        normalRelation.setName(singleRelationMethod.getName());
-        normalRelation.setDumpNode(targetNode);
-        node.addDumpRelation(normalRelation);
+      } else if (containmentMethod.isListChildMethod()) {
+        // -- listChild --
+        Iterable<?> targetList = (Iterable<?>) containmentMethod.getMethod().invoke(obj);
+        DumpListChildNode listChild = new DumpListChildNode();
+        listChild.setComputed(false);
+        String childName = containmentMethod.getName();
+        boolean shouldBeInvisisble = !isChildEnabled(objClassName, childName);
+        listChild.setName(childName);
+        for (Object target : targetList) {
+          DumpNode targetNode = transform(tti, target, options.asNormal(shouldBeInvisisble));
+          if (target != null && targetNode != null) {
+            listChild.addInnerDumpNode(new InnerDumpNode().setDumpNode(targetNode));
+          }
+        }
+        if (listChild.getNumInnerDumpNode() > 0) {
+          node.addDumpChildNode(listChild);
+        }
+      } else {
+        throw new RuntimeException("Unknown containment method type " + containmentMethod);
       }
     }
-    // -- listRelation --
-    for (ListRelationMethod listRelationMethod : car.listRelationMethods()) {
-      Iterable<?> targetList = (Iterable<?>) listRelationMethod.getMethod().invoke(obj);
-      DumpListRelation listRelation = new DumpListRelation();
-      listRelation.setName(listRelationMethod.getName());
-      for (Object target : targetList) {
-        DumpNode targetNode = transform(tti, target, Source.RELATION);
+    for (AnalysedMethod otherMethod : car.getOtherMethodList()) {
+      if (otherMethod.isSingleChildMethod()) {
+        // -- singleChild --
+        Object target = otherMethod.getMethod().invoke(obj);
+        String childName = otherMethod.getName();
+        boolean computed = otherMethod.asSingleChildMethod().isNTASingleChildMethod();
+        DumpNode targetNode = transform(tti, target, options.asNormal(!isChildEnabled(objClassName, childName)).computed(computed).allowNullObjectsOnce());
+        if (targetNode != null) {
+          DumpNormalChildNode normalChild = new DumpNormalChildNode();
+          normalChild.setName(childName);
+          normalChild.setDumpNode(targetNode);
+          normalChild.setComputed(computed);
+          node.addDumpChildNode(normalChild);
+        }
+      } else if (otherMethod.isListChildMethod()) {
+        // -- listChild --
+        Iterable<?> targetList = (Iterable<?>) otherMethod.getMethod().invoke(obj);
+        DumpListChildNode listChild = new DumpListChildNode();
+        boolean computed = otherMethod.asListChildMethod().isNTAListChildMethod();
+        listChild.setComputed(computed);
+        String childName = otherMethod.getName();
+        boolean shouldBeInvisisble = !isChildEnabled(objClassName, childName);
+        listChild.setName(childName);
+        for (Object target : targetList) {
+          DumpNode targetNode = transform(tti, target, options.asNormal(shouldBeInvisisble).computed(computed));
+          if (target != null && targetNode != null) {
+            listChild.addInnerDumpNode(new InnerDumpNode().setDumpNode(targetNode));
+          }
+        }
+        if (listChild.getNumInnerDumpNode() > 0) {
+          node.addDumpChildNode(listChild);
+        }
+      } else if (otherMethod.isSingleRelationMethod()) {
+        // -- singleRelation --
+        Object target = otherMethod.getMethod().invoke(obj);
+        DumpNode targetNode = transform(tti, target, options.asRelation());
         if (target != null && targetNode != null) {
-          listRelation.addInnerDumpNode(new InnerDumpNode(targetNode));
+          DumpNormalRelation normalRelation = new DumpNormalRelation();
+          normalRelation.setName(otherMethod.getName());
+          normalRelation.setDumpNode(targetNode);
+          node.addDumpRelation(normalRelation);
         }
-      }
-      if (listRelation.getNumInnerDumpNode() > 0) {
-        node.addDumpRelation(listRelation);
-      }
-    }
-    // -- token --
-    for (TokenMethod tokenMethod : car.tokenMethods()) {
-      Object target = tokenMethod.getMethod().invoke(obj);
-      if (target != null) {
-        DumpNode targetNode = transform(tti, target, Source.RELATION);
-        DumpToken token = null;
-        // TODO check, if Iterable.isAssignableFrom(target.getClass()).
-        //      if so, check isAstNode for first non-null to add DumpReferenceToken's, otherwise DumpValueToken's
-        if (targetNode != null && targetNode.isAstNode()) {
-          token = new DumpReferenceToken().setValue(targetNode);
-        } else {
-          if (target != null && (getBuildConfig().getIncludeEmptyString() || !target.toString().isEmpty())) {
-            DumpValueToken valueToken = new DumpValueToken();
-            valueToken.setValue(target);
-            token = valueToken;
+      } else if (otherMethod.isListRelationMethod()) {
+        // -- listRelation --
+        Iterable<?> targetList = (Iterable<?>) otherMethod.getMethod().invoke(obj);
+        DumpListRelation listRelation = new DumpListRelation();
+        listRelation.setName(otherMethod.getName());
+        for (Object target : targetList) {
+          DumpNode targetNode = transform(tti, target, options.asRelation());
+          if (target != null && targetNode != null) {
+            listRelation.addInnerDumpNode(new InnerDumpNode(targetNode));
           }
         }
-        if (token != null) {
-          token.setName(tokenMethod.getName());
-          token.setComputed(tokenMethod.isAttributeMethod());
-          node.addDumpToken(token);
+        if (listRelation.getNumInnerDumpNode() > 0) {
+          node.addDumpRelation(listRelation);
+        }
+      } else if (otherMethod.isTokenMethod()) {
+        // -- token --
+        Object target = otherMethod.getMethod().invoke(obj);
+        if (target != null) {
+          DumpNode targetNode = transform(tti, target, options.asRelation());
+          DumpToken token = null;
+          // TODO check, if Iterable.isAssignableFrom(target.getClass()).
+          //      if so, check isAstNode for first non-null to add DumpReferenceToken's, otherwise DumpValueToken's
+          if (targetNode != null && targetNode.isAstNode()) {
+            token = new DumpReferenceToken().setValue(targetNode);
+          } else {
+            if (target != null && (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);
+          }
         }
+      } else {
+        throw new RuntimeException("Unknown other method type " + otherMethod);
       }
     }
     return node;
@@ -162,49 +244,49 @@ aspect GenerationBackend {
     node.setLabel(getBuildConfig().getStyleInformation().getNameMethod().get(obj));
     node.setBackgroundColor(getBuildConfig().getStyleInformation().getBackgroundColorMethod().get(obj));
     node.setTextColor(getBuildConfig().getStyleInformation().getTextColorMethod().get(obj));
-  }
-
-  private Source DumpAst.nextSource(Source currentSource, boolean shouldBeInvisible) {
-    return currentSource == Source.INVISIBLE_PARENT ? Source.INVISIBLE_PARENT :
-        (shouldBeInvisible ? Source.INVISIBLE_PARENT : Source.PARENT);
+    node.setManualStereotypes(getBuildConfig().getStyleInformation().getStereotypeMethod().get(obj));
   }
 
   syn nta ClassAnalysisResult DumpAst.analyzeClass(java.lang.Class<?> clazz) {
     ClassAnalysisResult result = new ClassAnalysisResult();
     String clazzName = clazz.getSimpleName();
+    java.util.List<String> targetOrder = targetOrder(clazz);
     for (java.lang.reflect.Method method : clazz.getMethods()) {
       for (java.lang.annotation.Annotation annotation : method.getAnnotations()) {
         String canonicalName = annotation.annotationType().getCanonicalName();
         if (canonicalName.startsWith(astNodeAnnotationPrefix())) {
           String simpleName = annotation.annotationType().getSimpleName();
+          String contextNameToAdd = null;
+          AnalysedMethod containmentMethodToAdd = null;
           switch (simpleName) {
             case "Child":
-              String singleChildName = invokeName(annotation);
+              contextNameToAdd = invokeName(annotation);
               NormalSingleChildMethod singleChildMethod = new NormalSingleChildMethod();
               singleChildMethod.setMethod(method);
-              singleChildMethod.setName(singleChildName);
-              result.addAnalysedMethod(singleChildMethod);
+              singleChildMethod.setName(contextNameToAdd);
+              containmentMethodToAdd = singleChildMethod;
               break;
             case "OptChild":
-              String optChildName = invokeName(annotation);
+              contextNameToAdd = invokeName(annotation);
               try {
                 // the annotated method is "get???Opt", but we want "get???"
-                java.lang.reflect.Method realGetter = clazz.getMethod("get" + optChildName);
+                java.lang.reflect.Method realGetter = clazz.getMethod("get" + contextNameToAdd);
                 NormalSingleChildMethod normalSingleChildMethod = new NormalSingleChildMethod();
                 normalSingleChildMethod.setMethod(realGetter);
-                normalSingleChildMethod.setName(optChildName);
-                result.addAnalysedMethod(normalSingleChildMethod);
+                normalSingleChildMethod.setName(contextNameToAdd);
+                containmentMethodToAdd = normalSingleChildMethod;
               } catch (NoSuchMethodException e) {
-                System.err.println("Could not find getter for Opt-child " + optChildName + " in " + clazzName);
+                System.err.println("Could not find getter for Opt-child " + contextNameToAdd + " in " + clazzName);
                 throw new RuntimeException(e);
               }
               break;
             case "ListChild":
               String listChildName = invokeName(annotation);
+              contextNameToAdd = listChildName;
               NormalListChildMethod normalListChildMethod = new NormalListChildMethod();
               normalListChildMethod.setMethod(method);
               normalListChildMethod.setName(listChildName);
-              result.addAnalysedMethod(normalListChildMethod);
+              containmentMethodToAdd = normalListChildMethod;
               break;
             case "Token":
               // heuristic for relations
@@ -218,7 +300,7 @@ aspect GenerationBackend {
                     SingleRelationMethod singleRelationMethod = new SingleRelationMethod();
                     singleRelationMethod.setMethod(relationMethod);
                     singleRelationMethod.setName(relationName);
-                    result.addAnalysedMethod(singleRelationMethod);
+                    result.addOtherMethod(singleRelationMethod);
                   }
                   continue;
                 } catch (NoSuchMethodException e) {
@@ -232,7 +314,7 @@ aspect GenerationBackend {
                     ListRelationMethod listRelationMethod = new ListRelationMethod();
                     listRelationMethod.setMethod(relationMethod);
                     listRelationMethod.setName(relationName);
-                    result.addAnalysedMethod(listRelationMethod);
+                    result.addOtherMethod(listRelationMethod);
                   }
                   continue;
                 } catch (NoSuchMethodException e) {
@@ -243,7 +325,7 @@ aspect GenerationBackend {
                 IntrinsicTokenMethod tokenMethod = new IntrinsicTokenMethod();
                 tokenMethod.setMethod(method);
                 tokenMethod.setName(tokenName);
-                result.addAnalysedMethod(tokenMethod);
+                result.addOtherMethod(tokenMethod);
               }
               break;
             case "Attribute":
@@ -263,12 +345,12 @@ aspect GenerationBackend {
                     NTAListChildMethod ntaListChildMethod = new NTAListChildMethod();
                     ntaListChildMethod.setMethod(method);
                     ntaListChildMethod.setName(attributeName);
-                    result.addAnalysedMethod(ntaListChildMethod);
+                    result.addOtherMethod(ntaListChildMethod);
                   } else {
                     NTASingleChildMethod ntaSingleChildMethod = new NTASingleChildMethod();
                     ntaSingleChildMethod.setMethod(method);
                     ntaSingleChildMethod.setName(attributeName);
-                    result.addAnalysedMethod(ntaSingleChildMethod);
+                    result.addOtherMethod(ntaSingleChildMethod);
                   }
                 }
               } else if (isAttributeEnabled(clazzName, attributeName)) {
@@ -276,16 +358,53 @@ aspect GenerationBackend {
                 AttributeMethod attributeMethod = new AttributeMethod();
                 attributeMethod.setMethod(method);
                 attributeMethod.setName(attributeName);
-                result.addAnalysedMethod(attributeMethod);
+                result.addOtherMethod(attributeMethod);
               }
               break;
           }
+          if (containmentMethodToAdd != null) {
+            int indexOfContextInTarget = targetOrder.indexOf(contextNameToAdd);
+            if (indexOfContextInTarget == 0) {
+              result.getContainmentMethodList().insertChild(containmentMethodToAdd, 0);
+              continue;
+            }
+            if (indexOfContextInTarget == targetOrder.size() - 1) {
+              result.addContainmentMethod(containmentMethodToAdd);
+              continue;
+            }
+
+            for (int i = 0, size = result.getNumContainmentMethod(); i < size; i++) {
+              String currentContextName = result.getContainmentMethod(i).getName();
+              int indexOfCurrentInTarget = targetOrder.indexOf(currentContextName);
+              if (indexOfCurrentInTarget > indexOfContextInTarget) {
+                result.getContainmentMethodList().insertChild(containmentMethodToAdd, i);
+                break;
+              }
+            }
+            result.addContainmentMethod(containmentMethodToAdd);
+          }
         }
       }
     }
     return result;
   }
 
+  syn java.util.List<String> DumpAst.targetOrder(Class<?> clazz) {
+    for (java.lang.reflect.Constructor<?> method : clazz.getConstructors()) {
+      for (java.lang.annotation.Annotation annotation : method.getAnnotations()) {
+        String canonicalName = annotation.annotationType().getCanonicalName();
+        if (canonicalName.startsWith(astNodeAnnotationPrefix())) {
+          String simpleName = annotation.annotationType().getSimpleName();
+          if (simpleName.equals("Constructor")) {
+            return java.util.Arrays.asList((String[]) invokeMethod("name", annotation));
+          }
+        }
+      }
+    }
+    // there is no constructor with an annotation, iff the nonterminal has no children
+    return null;
+  }
+
   private String DumpAst.titleCase(String s) {
     if (s.isEmpty()) {
       return s;
@@ -416,8 +535,8 @@ aspect GenerationBackend {
     return p.matcher(input).matches();
   }
 
-  // --- version --- (mustache has buildConfig as context)
-  syn String BuildConfig.version() = printConfig().getVersion();
+  // --- debug --- (mustache has printConfig as context)
+  syn boolean PrintConfig.debug() = buildConfig().getDebug();
 
   private static String DumpAst.invokeName(java.lang.annotation.Annotation annotation) {
     return (String) invokeMethod("name", annotation);
@@ -434,11 +553,15 @@ aspect GenerationBackend {
     }
   }
 
+  // --- isNull ---
+  syn boolean DumpNode.isNull() {
+    return getObject() == null;
+  }
+
   // --- isAstNode ---
   syn boolean DumpNode.isAstNode() {
     if (getObject() == null) {
-      // this is only possible for normal child nodes, and they are ast nodes
-      return true;
+      return false;
     }
     Class<?> clazz = getObject().getClass();
     for (java.lang.reflect.Method method : clazz.getMethods()) {
@@ -468,7 +591,7 @@ aspect GenerationBackend {
     java.util.List<DumpNode> result = new java.util.ArrayList<>();
     for (DumpChildNode childNode : getDumpChildNodeList()) {
       for (DumpNode inner : childNode.innerNodes(false)) {
-        if (inner.getInvisible()) {
+        if (inner != null && inner.getInvisible()) {
           result.addAll(inner.reachableThroughInvisible());
         } else if (this.getInvisible()) {
           result.add(inner);
@@ -487,6 +610,30 @@ aspect GenerationBackend {
     }
   }
 
+  // --- stereotypeList ---
+  syn String DumpNode.stereotypeList() {
+    StringBuilder sb = new StringBuilder(getManualStereotypes());
+    if (!automaticStereotypes().isEmpty()) {
+      // add automatic stereotypes, if there are any
+      if (!getManualStereotypes().isEmpty()) {
+        // add a comma between manual and automatic, if both are present
+        sb.append(",");
+      }
+      sb.append(automaticStereotypes());
+    }
+    String manualAndAutomaticStereotypes = sb.toString();
+    if (manualAndAutomaticStereotypes.isEmpty()) {
+      return "";
+    }
+    sb = new StringBuilder();
+    for (String stereotype : manualAndAutomaticStereotypes.split(",")) {
+      sb.append(" <<").append(stereotype).append(">>");
+    }
+    return sb.toString();
+  }
+  // --- manualAndAutomaticStereotypes ---
+  syn String DumpNode.automaticStereotypes() = getComputed() ? "NTA" : "";
+
   // --- myChildren ---
   syn java.util.List<DumpNode> DumpNode.myChildren() {
     java.util.List<DumpNode> result = new java.util.ArrayList<>();
@@ -519,96 +666,22 @@ aspect GenerationBackend {
   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;
   }
 
-  syn String DumpAst.toYaml(boolean prependCreationComment) {
-    Document doc = new Document();
-    doc.setRootElement(getRootNode().toYaml());
-    return doc.prettyPrint(prependCreationComment);
-  }
-
-  syn MappingElement DumpNode.toYaml() {
-    MappingElement result = new MappingElement();
-    // tokens are key-value-pairs
-    for (DumpToken token : getDumpTokenList()) {
-      if (token.isDumpValueToken()) {
-        result.put(token.getName(), makeValueElement(token.asDumpValueToken().getValue()));
-      } else {
-        result.put(token.getName(), token.asDumpReferenceToken().getValue().toYaml());
-      }
-    }
-    // children
-    for (DumpChildNode child : getDumpChildNodeList()) {
-      ListElement list = new ListElement();
-      for (DumpNode inner : child.innerNodes(true)) {
-        list.add(inner.toYaml());
-      }
-      if (child.isList()) {
-        result.put(child.getName(), list);
-      } else if (list.getNumElement() == 1) {
-        result.put(child.getName(), list.getElement(0));
-      }
-    }
-    // relations
-    for (DumpRelation relation : getDumpRelationList()) {
-      ListElement list = new ListElement();
-      for (DumpNode inner : relation.innerNodes(true)) {
-        list.add(inner.toYaml());
-      }
-      if (relation.isList()) {
-        result.put(relation.getName(), list);
-      } else if (list.getNumElement() == 1) {
-        result.put(relation.getName(), list.getElement(0));
-      }
-    }
-    return result;
-  }
-
-  private static ComplexElement ASTNode.HELPER_COMPLEX_ELEMENT = new MappingElement();
-  protected static Element ASTNode.makeValueElement(Object obj) {
-    if (obj instanceof Integer) {
-      return ValueElement.of((int) obj);
-    } else if (obj instanceof Boolean) {
-      return ValueElement.of((boolean) obj);
-    } else {
-      return HELPER_COMPLEX_ELEMENT.makeStringElement(obj.toString());
-    }
-  }
-
-  public class AppendableWriter extends java.io.Writer {
-    private final StringBuilder sb;
-
-    public AppendableWriter(StringBuilder sb) {
-      this.sb = sb;
-    }
-
-    @Override
-    public void write(char[] chars, int off, int len) {
-      sb.append(chars, off, len);
-    }
-
-    @Override
-    public void write(String str) {
-      sb.append(str);
-    }
-
-    @Override
-    public void flush() {
-    }
-
-    @Override
-    public void close() {
-    }
-  }
 
   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;
   }
 
+  syn String DumpAst.computedColor() = getBuildConfig().getStyleInformation().getComputedColor();
+
   @FunctionalInterface
   public interface StyleMethod<ASTNODE> {
     String get(ASTNODE node);
diff --git a/dumpAst/src/main/jastadd/GenerationFrontend.jadd b/dumpAst/src/main/jastadd/GenerationFrontend.jadd
index 97b622a1ba1d333ad23e44dabb0a8571721cf6ab..3e6fcf0a93911c07bc48e6571a9764776da6c291 100644
--- a/dumpAst/src/main/jastadd/GenerationFrontend.jadd
+++ b/dumpAst/src/main/jastadd/GenerationFrontend.jadd
@@ -347,6 +347,16 @@ public class DumpBuilder {
       return this;
     }
 
+    public <ASTNODE> DumpBuilder setStereotypeMethod(StyleMethod<ASTNODE> stereotypeMethod) {
+      buildConfig.getStyleInformation().setStereotypeMethod(stereotypeMethod);
+      return this;
+    }
+
+    public DumpBuilder setComputedColor(String color) {
+      buildConfig.getStyleInformation().setComputedColor(color);
+      return this;
+    }
+
     public DumpBuilder setScale(double value) {
       printConfig.setScale(value);
       return this;
@@ -369,7 +379,17 @@ public class DumpBuilder {
     protected DumpAst build() {
       if (result == null) {
         result = new DumpAst();
-        result.setPackageName(this.packageName == null ? this.target.getClass().getPackage().getName() : this.packageName);
+        final String packageNameToUse;
+        if (this.packageName != null) {
+          packageNameToUse = this.packageName;
+        } else {
+          if (this.target == null) {
+            packageNameToUse = null;
+          } else {
+            packageNameToUse = this.target.getClass().getPackage().getName();
+          }
+        }
+        result.setPackageName(packageNameToUse);
         result.setBuildConfig(this.buildConfig);
         result.setPrintConfig(this.printConfig);
         try {
@@ -400,11 +420,38 @@ public class DumpBuilder {
     }
 
     public DumpBuilder dumpAsYaml(java.nio.file.Path destination, boolean prependCreationComment) throws java.io.IOException {
-      String content = build().toYaml(prependCreationComment);
+      String content = build().printYaml(prependCreationComment);
       try (java.io.Writer writer = java.nio.file.Files.newBufferedWriter(destination)) {
         writer.write(content);
       }
       return this;
     }
+
+    /**
+     * Write out content as PNG image generated by plantuml.
+     * @param destination path of destination file
+     * @return this
+     * @throws java.io.IOException if an I/O error happend during opening or writing in that file
+     */
+    public DumpBuilder dumpAsPNG(java.nio.file.Path destination) throws java.io.IOException {
+      String content = build().toPlantUml();
+      net.sourceforge.plantuml.SourceStringReader reader = new net.sourceforge.plantuml.SourceStringReader(content);
+      reader.outputImage(java.nio.file.Files.newOutputStream(destination));
+      return this;
+    }
+
+    /**
+     * Write out content as SVG image generated by plantuml.
+     * @param destination path of destination file
+     * @return this
+     * @throws java.io.IOException if an I/O error happend during opening or writing in that file
+     */
+    public DumpBuilder dumpAsSVG(java.nio.file.Path destination) throws java.io.IOException {
+      String content = build().toPlantUml();
+      net.sourceforge.plantuml.SourceStringReader reader = new net.sourceforge.plantuml.SourceStringReader(content);
+      reader.outputImage(java.nio.file.Files.newOutputStream(destination),
+          new net.sourceforge.plantuml.FileFormatOption(net.sourceforge.plantuml.FileFormat.SVG));
+      return this;
+    }
   }
 }
diff --git a/dumpAst/src/main/jastadd/GenerationMustache.jrag b/dumpAst/src/main/jastadd/GenerationMustache.jrag
index d69f4fba573580285101ce63946da0fda5ce52fd..cf9f53455aab8a2cef109e9e8d53a27e8fac4951 100644
--- a/dumpAst/src/main/jastadd/GenerationMustache.jrag
+++ b/dumpAst/src/main/jastadd/GenerationMustache.jrag
@@ -19,7 +19,35 @@ aspect GenerationMustache {
     com.github.mustachejava.DefaultMustacheFactory mf = new com.github.mustachejava.DefaultMustacheFactory();
     mf.setObjectHandler(roh);
     com.github.mustachejava.Mustache m = mf.compile("dumpAst.mustache");
-    m.execute(new java.io.PrintWriter(new AppendableWriter(sb)), this);
+    String yaml = this.printYaml(false);
+    Object context = new org.yaml.snakeyaml.Yaml().load(new StringReader(yaml));
+    m.execute(new java.io.PrintWriter(new AppendableWriter(sb)), context);
     return sb.toString();
   }
+
+  public class AppendableWriter extends java.io.Writer {
+    private final StringBuilder sb;
+
+    public AppendableWriter(StringBuilder sb) {
+      this.sb = sb;
+    }
+
+    @Override
+    public void write(char[] chars, int off, int len) {
+      sb.append(chars, off, len);
+    }
+
+    @Override
+    public void write(String str) {
+      sb.append(str);
+    }
+
+    @Override
+    public void flush() {
+    }
+
+    @Override
+    public void close() {
+    }
+  }
 }
diff --git a/dumpAst/src/main/jastadd/GenerationToYaml.jrag b/dumpAst/src/main/jastadd/GenerationToYaml.jrag
new file mode 100644
index 0000000000000000000000000000000000000000..30a093c9f2495e68f4b552cedf9ce16d77e7ed06
--- /dev/null
+++ b/dumpAst/src/main/jastadd/GenerationToYaml.jrag
@@ -0,0 +1,221 @@
+aspect GenerationToYaml {
+  syn String DumpAst.printYaml(boolean prependCreationComment) {
+    Document doc = new Document();
+    doc.setRootElement(this.toYaml(false));
+    return doc.prettyPrint(prependCreationComment);
+  }
+
+  // todo: default impl should actually be abstract instead
+  syn MappingElement ASTNode.toYaml(boolean fromRelation) = new MappingElement();
+
+  static MappingElement ASTNode.safeToYaml(ASTNode node, boolean fromRelation) {
+    if (node == null) {
+      return null;
+    }
+    return node.toYaml(fromRelation);
+  }
+
+  syn MappingElement DumpAst.toYaml(boolean fromRelation) {
+    MappingElement result = new MappingElement();
+    // children
+    result.put("PrintConfig", safeToYaml(getPrintConfig(), fromRelation));
+    addYamledList(result, "DumpNodes", getDumpNodeList(), fromRelation);
+
+    // attributes
+    result.put("computedColor", computedColor());
+    return result;
+  }
+
+  static void ASTNode.addYamledList(MappingElement base, String key, Iterable<? extends ASTNode<?>> iterable, boolean fromRelation) {
+    ListElement innerList = new ListElement();
+    for (ASTNode node : iterable) {
+      innerList.add(safeToYaml(node, fromRelation));
+    }
+    base.put(key, innerList);
+  }
+
+  syn MappingElement PrintConfig.toYaml(boolean fromRelation) {
+    MappingElement result = new MappingElement();
+    // children
+    addYamledList(result, "Headers", getHeaderList(), fromRelation);
+
+    // tokens
+    result.put("scale", getScale());
+    result.put("version", getVersion());
+    result.put("orderChildren", getOrderChildren());
+
+    // attributes
+    result.put("debug", debug());
+    return result;
+  }
+
+  syn MappingElement Header.toYaml(boolean fromRelation) {
+    MappingElement result = new MappingElement();
+    // tokens
+    result.put("value", getValue());
+    return result;
+  }
+
+  syn MappingElement DumpNode.toYaml(boolean fromRelation) {
+    MappingElement result = new MappingElement();
+
+    // children
+    if (!fromRelation) {
+      addYamledList(result, "DumpChildNodes", getDumpChildNodeList(), fromRelation);
+      addYamledList(result, "DumpTokens", getDumpTokenList(), fromRelation);
+      addYamledList(result, "DumpRelations", getDumpRelationList(), fromRelation);
+    }
+
+    // tokens
+    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());
+    }
+
+    // attributes
+    if (!fromRelation) {
+      result.put("isNull", isNull());
+      result.put("isAstNode", isAstNode());
+      result.put("labelAndTextColor", labelAndTextColor());
+      result.put("stereotypeList", stereotypeList());
+      addYamledList(result, "myChildren", myChildren(), true);
+    }
+    result.put("hasSuccessor", hasSuccessor());
+    result.put("successor", safeToYaml(successor(), true));
+
+    // NTAs
+    if (!fromRelation) {
+      result.put("InvisiblePath", safeToYaml(getInvisiblePath(), true));
+    }
+    return result;
+  }
+
+  syn MappingElement DumpChildNode.toYaml(boolean fromRelation) {
+    MappingElement result = new MappingElement();
+    // tokens
+    result.put("name", getName());
+    result.put("computed", getComputed());
+    // attributes
+    result.put("label", label());
+    result.put("isList", isList());
+    result.put("outerNodeName", outerNodeName());
+
+    return result;
+  }
+
+  syn MappingElement DumpNormalChildNode.toYaml(boolean fromRelation) {
+    MappingElement result = super.toYaml(fromRelation);
+    // attributes
+    result.put("bothVisible", bothVisible());
+    result.put("innerNodeName", innerNodeName());
+    return result;
+  }
+
+  syn MappingElement DumpListChildNode.toYaml(boolean fromRelation) {
+    MappingElement result = super.toYaml(fromRelation);
+    // children
+    addYamledList(result, "InnerDumpNodes", getInnerDumpNodeList(), fromRelation);
+    return result;
+  }
+
+  syn MappingElement DumpToken.toYaml(boolean fromRelation) {
+    MappingElement result = new MappingElement();
+    // tokens
+    result.put("name", getName());
+    result.put("computed", getComputed());
+    // attributes
+    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());
+    return result;
+  }
+
+  syn MappingElement DumpReferenceToken.toYaml(boolean fromRelation) {
+    MappingElement result = super.toYaml(fromRelation);
+    // tokens
+    result.put("innerNodeName", innerNodeName());
+    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());
+    // attributes
+    result.put("isList", isList());
+    result.put("label", label());
+    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());
+    return result;
+  }
+
+  syn MappingElement DumpListRelation.toYaml(boolean fromRelation) {
+    MappingElement result = super.toYaml(fromRelation);
+    // children
+    addYamledList(result, "InnerDumpNodes", getInnerDumpNodeList(), fromRelation);
+    return result;
+  }
+
+  syn MappingElement InnerDumpNode.toYaml(boolean fromRelation) {
+    MappingElement result = new MappingElement();
+    // attributes
+    result.put("bothVisible", bothVisible());
+    result.put("innerNodeName", innerNodeName());
+    result.put("outerNodeName", outerNodeName());
+    result.put("label", label());
+    return result;
+  }
+
+  syn MappingElement InvisiblePath.toYaml(boolean fromRelation) {
+    MappingElement result = super.toYaml(fromRelation);
+    // children
+    addYamledList(result, "InnerDumpNodes", getInnerDumpNodeList(), fromRelation);
+    return result;
+  }
+
+  // extension for mustache
+  public static ValueElement ValueElement.of(double value) {
+    return new ValueElement(false, String.valueOf(value));
+  }
+  public MappingElement MappingElement.put(String key, double value) {
+    addKeyValuePair(key, ValueElement.of(value));
+    return this;
+  }
+
+  refine Helpers public void MappingElement.addKeyValuePair(String key, Element value) {
+    if (value == null) {
+      return;
+    }
+    refined(key, value);
+  }
+
+  refine Helpers protected SimpleElement ComplexElement.makeStringElement(String value) {
+    if (value == null || value.equals("null")) {
+      return StringElement.of("null");
+    }
+    if (value.isEmpty()) {
+      return StringElement.of(value);
+    }
+    return refined(value);
+  }
+
+}
diff --git a/dumpAst/src/main/jastadd/Imports.jadd b/dumpAst/src/main/jastadd/Imports.jadd
new file mode 100644
index 0000000000000000000000000000000000000000..41dcef642656cbdbe35aac8444a7d2c1dc2e7110
--- /dev/null
+++ b/dumpAst/src/main/jastadd/Imports.jadd
@@ -0,0 +1,4 @@
+import java.io.*;
+import java.util.*;
+
+aspect Imports {}
diff --git a/dumpAst/src/main/jastadd/Navigation.jrag b/dumpAst/src/main/jastadd/Navigation.jrag
index 3f04c9c4392d6b02f4fa85b7329e9ae36fe140b9..c69141d3208bc5f99ee866d6417b043daaac9a03 100644
--- a/dumpAst/src/main/jastadd/Navigation.jrag
+++ b/dumpAst/src/main/jastadd/Navigation.jrag
@@ -5,20 +5,9 @@ aspect Navigation {
   syn boolean DumpRelation.isList() = false;
   eq DumpListRelation.isList() = true;
 
-  // --- isDumpValueToken ---
-  syn boolean DumpToken.isDumpValueToken() = false;
-  eq DumpValueToken.isDumpValueToken() = true;
-
-  // --- asDumpValueToken ---
-  syn DumpValueToken DumpToken.asDumpValueToken() = null;
-  eq DumpValueToken.asDumpValueToken() = this;
-
-  // --- asDumpReferenceToken ---
-  syn DumpReferenceToken DumpToken.asDumpReferenceToken() = null;
-  eq DumpReferenceToken.asDumpReferenceToken() = this;
-
   // --- buildConfig ---
   inh BuildConfig DumpNode.buildConfig();
+  inh BuildConfig PrintConfig.buildConfig();
   eq DumpAst.getChild().buildConfig() = getBuildConfig();
 
   // --- printConfig ---
@@ -50,7 +39,7 @@ aspect Navigation {
 
   // --- innerNodes ---
   syn java.util.List<DumpNode> DumpChildNode.innerNodes(boolean onlyVisible);
-  eq DumpNormalChildNode.innerNodes(boolean onlyVisible) = onlyVisible && (containingDumpNode().getInvisible() || getDumpNode().getInvisible()) ?
+  eq DumpNormalChildNode.innerNodes(boolean onlyVisible) = getDumpNode() == null || onlyVisible && (containingDumpNode().getInvisible() || getDumpNode().getInvisible()) ?
       java.util.Collections.emptyList() :
       java.util.Collections.singletonList(getDumpNode());
   eq DumpListChildNode.innerNodes(boolean onlyVisible) {
@@ -59,9 +48,15 @@ aspect Navigation {
     }
     java.util.List<DumpNode> result = new java.util.ArrayList<>();
     getInnerDumpNodeList().forEach(inner -> {
-      if (!onlyVisible || !inner.getDumpNode().getInvisible()) {
-        result.add(inner.getDumpNode());
+      if (inner == null || inner.getDumpNode() == null) {
+        //noinspection UnnecessaryReturnStatement
+        return;
       }
+      if (onlyVisible && inner.getDumpNode().getInvisible()) {
+        //noinspection UnnecessaryReturnStatement
+        return;
+      }
+      result.add(inner.getDumpNode());
     });
     return result;
   }
@@ -97,17 +92,4 @@ aspect Navigation {
 
   coll java.util.List<TokenMethod> ClassAnalysisResult.tokenMethods() [new java.util.ArrayList<>()] root ClassAnalysisResult;
   TokenMethod contributes this to ClassAnalysisResult.tokenMethods();
-
-  // --- isAttributeMethod ---
-  syn boolean TokenMethod.isAttributeMethod() = false;
-  eq AttributeMethod.isAttributeMethod() = true;
-
-  // --- isNTASingleChildMethod ---
-  syn boolean SingleChildMethod.isNTASingleChildMethod() = false;
-  eq NTASingleChildMethod.isNTASingleChildMethod() = true;
-
-  // --- isNTAListChildMethod ---
-  syn boolean ListChildMethod.isNTAListChildMethod() = false;
-  eq NTAListChildMethod.isNTAListChildMethod() = true;
-
 }
diff --git a/dumpAst/src/main/jastadd/Printing.jrag b/dumpAst/src/main/jastadd/Printing.jrag
index 362451e3dfd3873f481ef6939793ba04c5dc7628..e4565af809152de0c654dd6bc613dd5aab2c3e02 100644
--- a/dumpAst/src/main/jastadd/Printing.jrag
+++ b/dumpAst/src/main/jastadd/Printing.jrag
@@ -7,9 +7,9 @@ aspect Printing {
   eq DumpNode.getChild().outerNodeName() = name();
 
   // --- innerNodeName ---
-  syn String InnerDumpNode.innerNodeName() = getDumpNode().name();
-  syn String DumpNormalChildNode.innerNodeName() = getDumpNode().name();
-  syn String DumpNormalRelation.innerNodeName() = getDumpNode().name();
+  syn String InnerDumpNode.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 ---
@@ -20,9 +20,16 @@ aspect Printing {
   syn String DumpRelation.label() = getName();
   syn String DumpToken.label() = getName() + (getComputed() ? "()" : "");
   syn String DumpNode.label() = getLabel();
+  inh String InnerDumpNode.label();
+  eq DumpListChildNode.getInnerDumpNode(int index).label() = label() + "[" + index + "]";
+  eq DumpListRelation.getInnerDumpNode(int index).label() = label() + "[" + index + "]";
+  eq InvisiblePath.getInnerDumpNode(int index).label() = null;
 
   // --- bothVisible ---
-  syn boolean InnerDumpNode.bothVisible() = !containingDumpNode().getInvisible() && !getDumpNode().getInvisible();
-  syn boolean DumpNormalChildNode.bothVisible() = !containingDumpNode().getInvisible() && !getDumpNode().getInvisible();
-  syn boolean DumpNormalRelation.bothVisible() = !containingDumpNode().getInvisible() && !getDumpNode().getInvisible();
+  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 DumpNormalChildNode.bothVisible() = bothVisible(containingDumpNode(), getDumpNode());
+  syn boolean DumpNormalRelation.bothVisible() = bothVisible(containingDumpNode(), getDumpNode());
 }
diff --git a/dumpAst/src/main/resources/dumpAst.mustache b/dumpAst/src/main/resources/dumpAst.mustache
index 9dd0d02e100d308c175e494648008aebb663e5b0..a4c15afde2619529e104c4288b5d5fe80cf9244e 100644
--- a/dumpAst/src/main/resources/dumpAst.mustache
+++ b/dumpAst/src/main/resources/dumpAst.mustache
@@ -1,44 +1,58 @@
 @startuml
+skinparam object<<null>> {
+   BorderColor transparent
+   BackgroundColor transparent
+   shadowing false
+}
+hide <<null>> stereotype
+skinparam object<<NTA>> {
+    BorderColor {{{computedColor}}}
+}
+hide <<NTA>> stereotype
+
 {{#PrintConfig}}
-scale {{Scale}}
+scale {{{scale}}}
   {{#Headers}}
-{{Value}}
+{{{value}}}
   {{/Headers}}
 {{/PrintConfig}}
 
 {{#DumpNodes}}
-  {{#isAstNode}}
-    {{^Invisible}}
-object "{{{labelAndTextColor}}}" as {{name}} {{#backgroundColor}}#{{{backgroundColor}}}{{/backgroundColor}} {
+  {{^invisible}}
+    {{#isNull}}
+object "null" as {{{name}}}<<null>>
+    {{/isNull}}
+    {{#isAstNode}}
+object "{{{labelAndTextColor}}}" as {{{name}}} {{{stereotypeList}}} {{#backgroundColor}}#{{{backgroundColor}}}{{/backgroundColor}} {
       {{#DumpTokens}}
         {{#isDumpValueToken}}
-  {{label}} = {{{Value}}}
+  {{{label}}} = {{{value}}}
         {{/isDumpValueToken}}
       {{/DumpTokens}}
 }
-    {{/Invisible}}
-  {{/isAstNode}}
+    {{/isAstNode}}
+  {{/invisible}}
 {{/DumpNodes}}
 
 {{#DumpNodes}}
   {{#DumpTokens}}
-    {{^Invisible}}
+    {{^invisible}}
       {{^isDumpValueToken}}
-{{outerNodeName}} ..> {{innerNodeName}} : {{label}}
+{{{outerNodeName}}} .{{#computed}}[#{{{computedColor}}}]{{/computed}}.> {{{innerNodeName}}} : {{{label}}}
       {{/isDumpValueToken}}
-    {{/Invisible}}
+    {{/invisible}}
   {{/DumpTokens}}
   {{#DumpChildNodes}}
     {{#isList}}
       {{#InnerDumpNodes}}
         {{#bothVisible}}
-{{outerNodeName}} *-- {{innerNodeName}} : {{label}}
+{{{outerNodeName}}} *-{{#computed}}[#{{{computedColor}}}]{{/computed}}- {{{innerNodeName}}} : {{{label}}}
         {{/bothVisible}}
       {{/InnerDumpNodes}}
     {{/isList}}
     {{^isList}}
       {{#bothVisible}}
-{{outerNodeName}} *-- {{innerNodeName}} : {{label}}
+{{{outerNodeName}}} *-{{#computed}}[#{{{computedColor}}}]{{/computed}}- {{{innerNodeName}}} : {{{label}}}
       {{/bothVisible}}
     {{/isList}}
   {{/DumpChildNodes}}
@@ -46,38 +60,38 @@ object "{{{labelAndTextColor}}}" as {{name}} {{#backgroundColor}}#{{{backgroundC
     {{#isList}}
       {{#InnerDumpNodes}}
         {{#bothVisible}}
-{{outerNodeName}} {{#Bidirectional}}<{{/Bidirectional}}--> {{innerNodeName}} : {{label}}
+{{{outerNodeName}}} {{#bidirectional}}<{{/bidirectional}}--> {{{innerNodeName}}} : {{{label}}}
         {{/bothVisible}}
       {{/InnerDumpNodes}}
     {{/isList}}
     {{^isList}}
       {{#bothVisible}}
-{{outerNodeName}} {{#Bidirectional}}<{{/Bidirectional}}--> {{innerNodeName}} : {{label}}
+{{{outerNodeName}}} {{#bidirectional}}<{{/bidirectional}}--> {{{innerNodeName}}} : {{{label}}}
       {{/bothVisible}}
     {{/isList}}
   {{/DumpRelations}}
-  {{^Invisible}}
+  {{^invisible}}
     {{#InvisiblePath}}
       {{#InnerDumpNodes}}
-{{outerNodeName}} o.. {{innerNodeName}}
+{{{outerNodeName}}} o.. {{{innerNodeName}}}
       {{/InnerDumpNodes}}
     {{/InvisiblePath}}
-  {{/Invisible}}
+  {{/invisible}}
   {{#PrintConfig}}{{#orderChildren}}
       {{#myChildren}}
           {{#hasSuccessor}}
-{{name}} -[hidden]right-> {{#successor}}{{name}}{{/successor}}
+{{{name}}} -[hidden]right-> {{#successor}}{{{name}}}{{/successor}}
           {{/hasSuccessor}}
       {{/myChildren}}
   {{/orderChildren}}{{/PrintConfig}}
 {{/DumpNodes}}
-{{#BuildConfig}}
-  {{#Debug}}
+{{#PrintConfig}}
+  {{#debug}}
 legend right
   %date()
-  dumpAst: {{version}}
+  dumpAst: {{{version}}}
   plantuml: %version()
 endlegend
-  {{/Debug}}
-{{/BuildConfig}}
+  {{/debug}}
+{{/PrintConfig}}
 @enduml
diff --git a/dumpAst/src/main/resources/dumpAstVersion.properties b/dumpAst/src/main/resources/dumpAstVersion.properties
index e0a9ca936f23641719ffee3bfed385ca97d946b8..4f787e6a9ba7ce9674d053068c4f55768e8c5294 100644
--- a/dumpAst/src/main/resources/dumpAstVersion.properties
+++ b/dumpAst/src/main/resources/dumpAstVersion.properties
@@ -1,2 +1,2 @@
-#Thu Feb 24 09:45:15 CET 2022
-version=0.3.6
+#Fri Mar 11 12:41:31 CET 2022
+version=1.0.0
diff --git a/dumpAstWithPlantuml/build.gradle b/dumpAstWithPlantuml/build.gradle
deleted file mode 100644
index 03e3324a59b067b1c8f45dc1f7b44975194b779e..0000000000000000000000000000000000000000
--- a/dumpAstWithPlantuml/build.gradle
+++ /dev/null
@@ -1,99 +0,0 @@
-buildscript {
-    repositories.mavenLocal()
-    repositories.mavenCentral()
-    dependencies {
-        classpath group: 'org.jastadd', name: 'jastaddgradle', version: '1.13.3'
-    }
-}
-
-plugins {
-    id 'relast2uml.java-jastadd-conventions'
-    id 'relast2uml.java-publishing-conventions'
-}
-
-apply plugin: 'jastadd'
-
-dependencies {
-    implementation fileTree(include: ['plantuml.jar'], dir: '../libs')
-
-    implementation group: 'com.github.spullara.mustache.java', name: 'compiler', version: "${mustache_java_version}"
-}
-
-File dumpAstGrammar = file('../dumpAst/src/main/jastadd/DumpAst.relast')
-
-task relast(type: JavaExec) {
-    group = 'Build'
-    main = "-jar"
-
-    doFirst {
-        delete "src/gen/jastadd/*.ast"
-        delete "src/gen/jastadd/DumpAst.jadd"
-        delete "src/gen/jastadd/DumpAstRefResolver.jadd"
-        delete "src/gen/jastadd/DumpAstResolverStubs.jrag"
-        mkdir  "src/gen/jastadd/"
-    }
-
-    args = [
-            "../libs/relast.jar",
-            dumpAstGrammar,
-//            "./src/main/jastadd/MustacheNodes.relast",
-            "--listClass=java.util.ArrayList",
-            "--jastAddList=JastAddList",
-            "--useJastAddNames",
-            "--file",
-            "--resolverHelper",
-            "--grammarName=./src/gen/jastadd/DumpAst"
-    ]
-
-    inputs.files(file("../libs/relast.jar"),
-            dumpAstGrammar)
-    outputs.files(file("./src/gen/jastadd/DumpAst.ast"),
-            file("./src/gen/jastadd/DumpAst.jadd"),
-            file("./src/gen/jastadd/DumpAstRefResolver.jadd"),
-            file('./src/gen/jastadd/DumpAstResolverStubs.jrag'))
-}
-
-jastadd {
-    configureModuleBuild()
-    modules {
-        //noinspection GroovyAssignabilityCheck
-        module("DumpAstWithPlantuml") {
-
-            java {
-                basedir "."
-                include "src/main/**/*.java"
-                include "src/gen/**/*.java"
-            }
-
-            jastadd {
-                basedir ".."
-                include "dumpAst/src/main/jastadd/**/*.ast"
-                include "dumpAst/src/main/jastadd/**/*.jadd"
-                include "dumpAst/src/main/jastadd/**/*.jrag"
-                include "dumpAstWithPlantuml/src/main/jastadd/**/*.jadd"
-                include "dumpAst/src/gen/jastadd/**/*.ast"
-                include "dumpAst/src/gen/jastadd/**/*.jadd"
-                include "dumpAst/src/gen/jastadd/**/*.jrag"
-            }
-        }
-    }
-
-    cleanGen.doFirst {
-        delete "src/gen/java/de"
-        delete "src/gen-res/BuildInfo.properties"
-    }
-
-    preprocessParser.doFirst {
-
-        args += ["--no-beaver-symbol"]
-
-    }
-
-    module = "DumpAstWithPlantuml"
-    astPackage = 'de.tudresden.inf.st.jastadd.dumpAst.ast'
-    genDir = 'src/gen/java'
-    buildInfoDir = 'src/gen-res'
-    jastaddOptions = ["--lineColumnNumbers", "--List=JastAddList", "--safeLazy", "--visitCheck=true", "--rewrite=cnta", "--cache=all"]
-}
-
-generateAst.dependsOn relast
diff --git a/dumpAstWithPlantuml/src/main/jastadd/Generation.jadd b/dumpAstWithPlantuml/src/main/jastadd/Generation.jadd
deleted file mode 100644
index 35905af0dc789adfc17971ed23ede287db032d52..0000000000000000000000000000000000000000
--- a/dumpAstWithPlantuml/src/main/jastadd/Generation.jadd
+++ /dev/null
@@ -1,29 +0,0 @@
-aspect Generation {
-
-    /**
-     * Write out content as PNG image generated by plantuml.
-     * @param destination path of destination file
-     * @return this
-     * @throws java.io.IOException if an I/O error happend during opening or writing in that file
-     */
-    public DumpBuilder DumpBuilder.dumpAsPNG(java.nio.file.Path destination) throws java.io.IOException {
-      String content = build().toPlantUml();
-      net.sourceforge.plantuml.SourceStringReader reader = new net.sourceforge.plantuml.SourceStringReader(content);
-      reader.outputImage(java.nio.file.Files.newOutputStream(destination));
-      return this;
-    }
-
-    /**
-     * Write out content as SVG image generated by plantuml.
-     * @param destination path of destination file
-     * @return this
-     * @throws java.io.IOException if an I/O error happend during opening or writing in that file
-     */
-    public DumpBuilder DumpBuilder.dumpAsSVG(java.nio.file.Path destination) throws java.io.IOException {
-      String content = build().toPlantUml();
-      net.sourceforge.plantuml.SourceStringReader reader = new net.sourceforge.plantuml.SourceStringReader(content);
-      reader.outputImage(java.nio.file.Files.newOutputStream(destination),
-          new net.sourceforge.plantuml.FileFormatOption(net.sourceforge.plantuml.FileFormat.SVG));
-      return this;
-    }
-}
\ No newline at end of file
diff --git a/dumpAstWithPlantuml/src/main/resources/dumpAst.mustache b/dumpAstWithPlantuml/src/main/resources/dumpAst.mustache
deleted file mode 120000
index e549bf371db8fa4554a0b276e52a3c5b978af0a3..0000000000000000000000000000000000000000
--- a/dumpAstWithPlantuml/src/main/resources/dumpAst.mustache
+++ /dev/null
@@ -1 +0,0 @@
-../../../../dumpAst/src/main/resources/dumpAst.mustache
\ No newline at end of file
diff --git a/dumpAstWithPlantuml/src/main/resources/dumpAstWithPlantumlVersion.properties b/dumpAstWithPlantuml/src/main/resources/dumpAstWithPlantumlVersion.properties
deleted file mode 120000
index 19186b38f76bfc2fb8f855e0c5bafe020db4bae4..0000000000000000000000000000000000000000
--- a/dumpAstWithPlantuml/src/main/resources/dumpAstWithPlantumlVersion.properties
+++ /dev/null
@@ -1 +0,0 @@
-../../../../dumpAst/src/main/resources/dumpAstVersion.properties
\ No newline at end of file
diff --git a/dumpAstWithPlantuml/.gitignore b/featureTest/.gitignore
similarity index 54%
rename from dumpAstWithPlantuml/.gitignore
rename to featureTest/.gitignore
index 87b4cdd3d7c6a41502ca98703abeeb69a1d536fb..7609b2344767a4c3fd8857201dac2c4d1dff200c 100644
--- a/dumpAstWithPlantuml/.gitignore
+++ b/featureTest/.gitignore
@@ -3,3 +3,5 @@ src/gen-res/
 src/gen/
 out/
 *.class
+/featureTest.png
+/featureTest.yml
diff --git a/featureTest/build.gradle b/featureTest/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..ee343385c7fc3719b4420d063d34db968fb346bb
--- /dev/null
+++ b/featureTest/build.gradle
@@ -0,0 +1,111 @@
+// --- Buildscripts (must be at the top) ---
+buildscript {
+    repositories.mavenCentral()
+    dependencies {
+        classpath group: 'org.jastadd', name: 'jastaddgradle', version: '1.13.3'
+    }
+}
+
+// --- Plugin definitions ---
+plugins {
+    id 'java'
+    id 'java-library'
+    id 'idea'
+    id 'application'
+    id 'com.github.ben-manes.versions'
+}
+
+apply plugin: 'jastadd'
+
+// --- Dependencies ---
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    implementation project(":dumpAst")
+
+    api group: 'net.sf.beaver', name: 'beaver-rt', version: '0.9.11'
+
+    testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: "${jupiter_version}"
+    testImplementation group: 'org.assertj', name: 'assertj-core', version: '3.18.1'
+    testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: "${jupiter_version}"
+}
+
+// --- Preprocessors ---
+File genSrc = file("src/gen/java")
+sourceSets.main.java.srcDir genSrc
+idea.module.generatedSourceDirs += genSrc
+
+File testingGrammar = file('./src/main/jastadd/featureTest.relast')
+
+task relast(type: JavaExec) {
+    group = 'Build'
+    main = "-jar"
+
+    doFirst {
+        delete "src/gen/jastadd/*.ast"
+        delete "src/gen/jastadd/featureTest.jadd"
+        delete "src/gen/jastadd/featureTestRefResolver.jadd"
+        delete "src/gen/jastadd/featureTestResolverStubs.jrag"
+        mkdir  "src/gen/jastadd/"
+    }
+
+    args = [
+            "../libs/relast.jar",
+            testingGrammar,
+//            "./src/main/jastadd/MustacheNodes.relast",
+            "--listClass=java.util.ArrayList",
+            "--jastAddList=JastAddList",
+            "--useJastAddNames",
+            "--file",
+            "--resolverHelper",
+            "--grammarName=./src/gen/jastadd/featureTest"
+    ]
+
+    inputs.files(file("../libs/relast.jar"),
+            testingGrammar)
+    outputs.files(file("./src/gen/jastadd/featureTest.ast"),
+            file("./src/gen/jastadd/featureTest.jadd"),
+            file("./src/gen/jastadd/featureTestRefResolver.jadd"),
+            file('./src/gen/jastadd/featureTestResolverStubs.jrag'))
+}
+
+// --- JastAdd ---
+jastadd {
+    configureModuleBuild()
+    modules {
+        //noinspection GroovyAssignabilityCheck
+        module("featureTest") {
+            jastadd {
+                basedir ".."
+                include "featureTest/src/main/jastadd/**/*.ast"
+                include "featureTest/src/main/jastadd/**/*.jadd"
+                include "featureTest/src/main/jastadd/**/*.jrag"
+                include "featureTest/src/gen/jastadd/**/*.ast"
+                include "featureTest/src/gen/jastadd/**/*.jadd"
+                include "featureTest/src/gen/jastadd/**/*.jrag"
+            }
+        }
+    }
+
+    cleanGen.doFirst {
+        delete "src/gen/java/org"
+        delete "src/gen-res/BuildInfo.properties"
+    }
+
+    module = "featureTest"
+    astPackage = 'org.jastadd.featureTest.ast'
+    genDir = 'src/gen/java'
+    buildInfoDir = 'src/gen-res'
+    jastaddOptions = ["--lineColumnNumbers", "--List=JastAddList", "--safeLazy", "--visitCheck=true", "--rewrite=cnta", "--cache=all"]
+}
+
+// --- Tests ---
+test.useJUnitPlatform()
+
+// --- Versioning and Publishing ---
+mainClassName = 'de.tudresden.inf.st.jastadd.featureTest.FeatureTestMain'
+
+// --- Task order ---
+generateAst.dependsOn relast
diff --git a/featureTest/src/main/jastadd/featureTest.jrag b/featureTest/src/main/jastadd/featureTest.jrag
new file mode 100644
index 0000000000000000000000000000000000000000..4001126135dcb55faee01ca8edfc8c8f959f019f
--- /dev/null
+++ b/featureTest/src/main/jastadd/featureTest.jrag
@@ -0,0 +1,46 @@
+aspect GrammarGlobal {
+  syn A C.getCalculated() {
+    A result = new A();
+    result.setName("Calculated-" + getName());
+    D innerD = new D();
+    result.setD(innerD);
+    return result;
+  }
+
+  syn JastAddList<B> C.getAlsoCalculatedList() {
+    JastAddList<B> result = new JastAddList<>();
+    B inner = new B();
+    inner.setName("AlsoCalculated-" + getName());
+    result.add(inner);
+    return result;
+  }
+
+  syn nta A C.getCalculatedNewSyntax() {
+    A result = new A();
+    result.setName("Calculated-" + getName());
+    return result;
+  }
+
+  syn nta JastAddList<B> C.getAlsoCalculatedListNewSyntax() {
+    JastAddList<B> result = new JastAddList<>();
+    B inner = new B();
+    inner.setName("AlsoCalculated-" + getName());
+    result.add(inner);
+    return result;
+  }
+
+  syn int Root.simpleAttr() = 42;
+  syn A Root.referenceAttr() = getA();
+
+  syn boolean ASTNode.isA() = false;
+  eq A.isA() = true;
+}
+
+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/featureTest/src/main/jastadd/featureTest.relast b/featureTest/src/main/jastadd/featureTest.relast
new file mode 100644
index 0000000000000000000000000000000000000000..7b66cc96e3e0af88dd7c124928fca2ae86fa5e53
--- /dev/null
+++ b/featureTest/src/main/jastadd/featureTest.relast
@@ -0,0 +1,26 @@
+// testcases with global inclusion/exclusion
+Nameable ::= <Name> ;
+Root : Nameable ::= A B* [C];
+A : Nameable ::= B MyC:C D;
+B : Nameable ::= <OtherValue> ;
+C : Nameable ::= [A] <Unwanted:int> <RawReference:A> /Calculated:A/ /AlsoCalculated:B*/ ;
+SubC : C ::= <RawReference:A> <Unwanted:int> [A] /Calculated:A/ /AlsoCalculated:B*/ ;
+D;
+
+rel B.oneA -> A ;
+rel B.maybeC? -> C ;
+rel B.manyA* -> A ;
+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 ;
diff --git a/featureTest/src/main/java/de/tudresden/inf/st/jastadd/featureTest/FeatureTestMain.java b/featureTest/src/main/java/de/tudresden/inf/st/jastadd/featureTest/FeatureTestMain.java
new file mode 100644
index 0000000000000000000000000000000000000000..298298c64c1ce5ff4f1a646c9aaacc2c465cf70f
--- /dev/null
+++ b/featureTest/src/main/java/de/tudresden/inf/st/jastadd/featureTest/FeatureTestMain.java
@@ -0,0 +1,59 @@
+package de.tudresden.inf.st.jastadd.featureTest;
+
+import de.tudresden.inf.st.jastadd.dumpAst.ast.Dumper;
+import de.tudresden.inf.st.jastadd.dumpAst.ast.SkinParamBooleanSetting;
+import de.tudresden.inf.st.jastadd.dumpAst.ast.SkinParamStringSetting;
+import org.jastadd.featureTest.ast.*;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+/**
+ * Main class of feature test.
+ *
+ * @author rschoene - Initial contribution
+ */
+public class FeatureTestMain {
+
+  public static void main(String[] args) throws IOException {
+    Root root = new Root();
+    root.setName("Root1");
+    A a = new A();
+    a.setName("A2");
+    B b1 = new B();
+    b1.setName("B3");
+    C c = new C();
+    c.setName("C4");
+    c.setRawReference(a);
+    b1.setOneA(a);
+    B b2 = new B();
+    b2.setName("B5");
+    C myC = new C();
+    myC.setName("C6");
+    a.setMyC(myC);
+    root.setA(a);
+    root.addB(b1);
+    root.addB(b2);
+    root.setC(c);
+
+    Path pathToYaml = Paths.get("featureTest.yml");
+    Path pathToPng = Paths.get("featureTest.png");
+//    Dumper.read(null).dumpAsPNG(pathToPng);
+    Dumper
+//        .read(null)
+        .read(root)
+//        .customPreamble("hide empty members")
+        .skinParam(SkinParamBooleanSetting.Shadowing, false)
+        .includeAttributes("referenceAttr")
+        .includeNonterminalAttributes("Calculated")
+        .includeNonterminalAttributes("AlsoCalculatedListNewSyntax")
+        .dumpAsYaml(pathToYaml, true)
+        .dumpAsPNG(pathToPng);
+  }
+
+  private void m() {
+    //noinspection UnnecessaryReturnStatement
+    return;
+  }
+}
diff --git a/pages/docs/adding.md b/pages/docs/adding.md
index 2822997b946d501554f3644f016452e95a1ee0a7..8bfc64a25902b465da7c07f6da1a73e7979258b2 100644
--- a/pages/docs/adding.md
+++ b/pages/docs/adding.md
@@ -1,12 +1,12 @@
-# Add relast2uml to your project
+# Add DumpAst to your project
 
-If you want to use `Relast2Uml`, either use the latest [pre-build version](#use-packaged-version) or clone the repository and [build it yourself](#build-from-source).
+If you want to use `DumpAst`, either use the latest [pre-build version](#use-packaged-version) or clone the repository and [build it yourself](#build-from-source).
 
 ## Use packaged version
 
 Check the [package overview page](https://git-st.inf.tu-dresden.de/jastadd/relast2uml/-/packages) to find the latest versions of the individual packages.
 
-First add this GitLab as a repository in your `build.gradle`:
+Add this GitLab as a repository, and `dumpAst` as a dependency in your `build.gradle`:
 
 ```
 repositories {
@@ -15,68 +15,32 @@ repositories {
         url "https://git-st.inf.tu-dresden.de/api/v4/groups/jastadd/-/packages/maven"
     }
 }
-```
-
-### grammar2uml
-
-To use `Grammar2uml`, add it as a dependency:
-
-```
-configurations {
-    grammar2umlClasspath
-}
-
-dependencies {
-    grammar2umlClasspath group: 'de.tudresden.inf.st', name: 'grammar2uml', version: '0.1.1'
-}
-```
 
-Finally, add a task to create your visualization:
-
-```
-task grammar2uml(type: JavaExec) {
-    main = 'de.tudresden.inf.st.jastadd.grammar2uml.compiler.Compiler'
-    classpath = configurations.grammar2umlClasspath
-
-    args([
-            '--verbose',
-            'src/main/jastadd/GoalModel.relast'
-    ])
-}
-```
-
-### dumpAst
-
-To use `DumpAst` or `DumpAstWithPlantuml`, add it as a dependency:
-
-```
 dependencies {
-    implementation group: 'de.tudresden.inf.st', name: 'dumpAst', version: '0.3.4'
-    // or
-    implementation group: 'de.tudresden.inf.st', name: 'dumpAstWithPlantuml', version: '0.3.4'
+    implementation group: 'de.tudresden.inf.st', name: 'dumpAst', version: '0.3.7'
 }
 ```
 
-
 ## Build from source
 
-If you want to build the tools of `Relast2Uml` from source, first build the jar from the [Relast2Uml repository](https://git-st.inf.tu-dresden.de/jastadd/relast2uml):
+If you want to build the tools of `DumpAst` from source, first build the jar from the [repository](https://git-st.inf.tu-dresden.de/jastadd/relast2uml):
 
 ```bash
 git clone https://git-st.inf.tu-dresden.de/jastadd/relast2uml.git
 cd relast2uml
 ./gradlew jar
-ls dumpAst/build/libs/ dumpAstWithPlantuml/build/libs/
+ls dumpAst/build/libs/
 ```
 
-Those JARs can then be copied to your project.
+This JAR can then be copied to your project.
 
 ```bash
 cp dumpAst/build/libs/dumpAst-<version>.jar ../your-project/libs/dumpAst.jar
 cd ../your-project/
 ```
 
-Finally, this JAR has to be integrated into your build process. In case [Gradle](https://gradle.org/) is used, the JAR file needs to be added as dependency using:
+Finally, this JAR has to be integrated into your build process.
+In case [Gradle](https://gradle.org/) is used, the JAR file needs to be added as dependency using:
 
 ```groovy
 dependencies {
diff --git a/pages/docs/img/dumpAst.png b/pages/docs/img/dumpAst.png
index db1fecd4eeef97f01d961f003ca2e2cd2e55d525..919d8b1df6856c215f512fb2fe0101b9b8510b90 100644
Binary files a/pages/docs/img/dumpAst.png and b/pages/docs/img/dumpAst.png differ
diff --git a/pages/docs/index.md b/pages/docs/index.md
index 43ab1c3a63dc9349e98eba10d6f0e7bc6a18c17a..80e8008f7e47b4ebc8676959db78292d306c4e76 100644
--- a/pages/docs/index.md
+++ b/pages/docs/index.md
@@ -23,9 +23,9 @@ Using the return value, the following methods are supported and can be chained t
 |---------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
 | customPreamble                  | Add the given string as preamble to the visualization.                                                                                                                                                                                                                                     |
 | disableTypes                    | Disable all objects with types matching at least one of the given regex strings. Disabled objects won't be included in any output. However, their children are still processed.                                                                                                            |
-| dumpAsPNG                       | Write out content as PNG image generated by plantuml. (**Only available in dumpAstWithPlantuml**)                                                                                                                                                                                          |
+| dumpAsPNG                       | Write out content as PNG image generated by plantuml.                                                                                                                                                                                    |
 | dumpAsSource                    | Write out content as plantuml source code                                                                                                                                                                                                                                                  |
-| dumpAsSVG                       | Write out content as SVG image generated by plantuml. (**Only available in dumpAstWithPlantuml**)                                                                                                                                                                                          |
+| dumpAsSVG                       | Write out content as SVG image generated by plantuml.                                                                                                                                                                                          |
 | dumpAsYaml                      | Write out content as YAML representation of the ASTNode                                                                                                                                                                                                                                    |
 | enableDebug                     | Add debug information in dumped content, mainly version numbers.                                                                                                                                                                                                                           |
 | excludeAttributesFor            | *experimental, documentation missing*                                                                                                                                                                                                                                                      |
diff --git a/settings.gradle b/settings.gradle
index 631fcc974ebb903bd09ad6b13cd6dd5fbd875f3d..3eb76f1e946e81f5037864327765fdf452bf8c5c 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -2,5 +2,6 @@ rootProject.name = 'relast2uml'
 
 include 'relast.preprocessor'
 include 'dumpAst'
-include 'dumpAstWithPlantuml'
 include 'testDumper'
+include 'featureTest'
+
diff --git a/testDumper/build.gradle b/testDumper/build.gradle
index 1c0603dc55c06e22ddea0a727ea178102ece369a..34cd313f4905be14e54079c763fab87a07460f95 100644
--- a/testDumper/build.gradle
+++ b/testDumper/build.gradle
@@ -1,21 +1,42 @@
+// --- Buildscripts (must be at the top) ---
 buildscript {
-    repositories.mavenLocal()
     repositories.mavenCentral()
     dependencies {
         classpath group: 'org.jastadd', name: 'jastaddgradle', version: '1.13.3'
     }
 }
 
+// --- Plugin definitions ---
 plugins {
-    id 'relast2uml.java-jastadd-conventions'
+    id 'java'
+    id 'java-library'
+    id 'idea'
+    id 'com.github.ben-manes.versions'
 }
 
 apply plugin: 'jastadd'
 
+// --- Dependencies ---
+repositories {
+    mavenCentral()
+}
+
 dependencies {
-    testImplementation project(':dumpAst')
+    implementation project(':dumpAst')
+
+    api group: 'net.sf.beaver', name: 'beaver-rt', version: '0.9.11'
+
+    testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: "${jupiter_version}"
+    testImplementation group: 'org.assertj', name: 'assertj-core', version: '3.18.1'
+    testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: "${jupiter_version}"
 }
 
+
+// --- Preprocessors ---
+File genSrc = file("src/gen/java")
+sourceSets.main.java.srcDir genSrc
+idea.module.generatedSourceDirs += genSrc
+
 File testingGrammar = file('./src/main/jastadd/testDumper.relast')
 
 task relast(type: JavaExec) {
@@ -50,6 +71,7 @@ task relast(type: JavaExec) {
             file('./src/gen/jastadd/testDumperResolverStubs.jrag'))
 }
 
+// --- JastAdd ---
 jastadd {
     configureModuleBuild()
     modules {
@@ -92,4 +114,10 @@ jastadd {
     jastaddOptions = ["--lineColumnNumbers", "--List=JastAddList", "--safeLazy", "--visitCheck=true", "--rewrite=cnta", "--cache=all"]
 }
 
+// --- Tests ---
+test.useJUnitPlatform()
+
+// --- Versioning and Publishing ---
+
+// --- Task order ---
 generateAst.dependsOn relast
diff --git a/testDumper/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestDumperMain.java b/testDumper/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestDumperMain.java
deleted file mode 100644
index d7b14047c0b144339dead510f89f8ea510e60464..0000000000000000000000000000000000000000
--- a/testDumper/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestDumperMain.java
+++ /dev/null
@@ -1,52 +0,0 @@
-package de.tudresden.inf.st.jastadd.testDumper;
-
-import de.tudresden.inf.st.jastadd.dumpAst.ast.DumpAst;
-import de.tudresden.inf.st.jastadd.dumpAst.ast.DumpNode;
-import org.jastadd.testDumper.ast.A;
-import org.jastadd.testDumper.ast.B;
-import org.jastadd.testDumper.ast.C;
-import org.jastadd.testDumper.ast.Root;
-
-import java.util.stream.Collectors;
-
-public class TestDumperMain {
-  public static void main(String[] args) {
-    Root root = new Root();
-    root.setName("Root1");
-    A a = new A();
-    a.setName("A2");
-    B b1 = new B();
-    b1.setName("B3");
-    C c = new C();
-    c.setName("C4");
-    c.setRawReference(a);
-    b1.setOneA(a);
-    B b2 = new B();
-    b2.setName("B5");
-    C myC = new C();
-    myC.setName("C6");
-    a.setMyC(myC);
-    root.setA(a);
-    root.addB(b1);
-    root.addB(b2);
-    root.setC(c);
-
-    TestUtils.ExposingDumpBuilder builder = new TestUtils.ExposingDumpBuilder(root);
-    builder.includeAttributes("simpleAttr")
-        .orderChildren()
-        .includeNonterminalAttributes("getCalculated")
-        .setNameMethod(n -> n == null ? "null" : n.getClass().getSimpleName());
-
-    System.out.println(">> PlantUml");
-    DumpAst dumpAst = builder.build();
-    System.out.println(dumpAst.toPlantUml());
-
-    DumpNode node = dumpAst.getDumpNode(0);
-    System.out.println(node.getName());
-    System.out.println(node.myChildren().stream().map(DumpNode::getName).collect(Collectors.joining(", ")));
-
-//    System.out.println(">> YAML begin");
-//    System.out.println(builder.build().toYaml(true));
-//    System.out.println(">> YAML end");
-  }
-}
diff --git a/testDumper/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestSimple.java b/testDumper/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestSimple.java
index 1ccba6cc518d0ec487b937f0efd81068013ba2af..1b9be71cc6a27b381a94127d8cab991f06a87479 100644
--- a/testDumper/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestSimple.java
+++ b/testDumper/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestSimple.java
@@ -6,13 +6,16 @@ import de.tudresden.inf.st.jastadd.dumpAst.ast.DumpNode;
 import org.jastadd.testDumper.ast.*;
 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 java.util.Optional;
 
 import static de.tudresden.inf.st.jastadd.testDumper.TestUtils.*;
 import static org.assertj.core.api.Assertions.*;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.*;
 
 public class TestSimple {
 
@@ -28,6 +31,22 @@ public class TestSimple {
     assertEquals(0, actualRoot.getNumDumpRelation());
   }
 
+  @Test
+  public void testCustomPreamble() throws IOException {
+    Root root = createRoot(null, null);
+    String preamble = "{<\n>\n}";
+    ExposingDumpBuilder builder = new ExposingDumpBuilder(root);
+    builder.excludeNullNodes().customPreamble(preamble);
+
+    Path path = Paths.get("src/gen/resources/");
+    Files.createDirectories(path);
+    builder.dumpAsYaml(path.resolve("customPreamble.yaml"), true);
+
+    DumpAst dumpAst = builder.build();
+    String puml = dumpAst.toPlantUml();
+    assertThat(puml).contains(preamble);
+  }
+
   @Test void testChildlessNonterminal() {
     Root root = createRoot(createA(A_NAME, a -> a.setD(new D())), null);
     List<DumpNode> nodes = TestUtils.dumpModel(root);
@@ -80,10 +99,11 @@ public class TestSimple {
 
   @Test
   public void testOrderedListChildren() {
-    Root root = createRoot(null, null, createB(B1_NAME), createB(B2_NAME), createB(B3_NAME));
+    Root root = createRoot(createA(A_NAME), createC(C_NAME), createB(B1_NAME), createB(B2_NAME), createB(B3_NAME));
 
     List<DumpNode> nodes = TestUtils.dumpModel(root, DumpBuilder::orderChildren);
-    assertThat(nodes).flatExtracting(NAME_EXTRACTOR).containsExactlyInAnyOrder(ROOT_NAME, B1_NAME, B2_NAME, B3_NAME);
+    assertThat(nodes).flatExtracting(NAME_EXTRACTOR).containsExactlyInAnyOrder(
+        ROOT_NAME, A_NAME, B1_NAME, B2_NAME, B3_NAME, C_NAME);
 
     DumpNode actualRoot = TestUtils.findByName(nodes, ROOT_NAME);
     // in grammar: DumpAst ::= [...] DumpNode* [...];
@@ -91,7 +111,30 @@ public class TestSimple {
     assertTrue(((DumpAst) actualRoot.getParent().getParent()).getPrintConfig().getOrderChildren());
 
     List<DumpNode> children = actualRoot.myChildren();
-    assertThat(children).extracting(NAME_EXTRACTOR).containsExactlyInAnyOrder(B1_NAME, B2_NAME, B3_NAME);
+    assertThat(children).extracting(NAME_EXTRACTOR).containsExactly(
+        A_NAME, B1_NAME, B2_NAME, B3_NAME, C_NAME);
+
+    DumpNode actualA = TestUtils.findByName(nodes, A_NAME);
+    DumpNode actualB1 = TestUtils.findByName(nodes, B1_NAME);
+    DumpNode actualB2 = TestUtils.findByName(nodes, B2_NAME);
+    DumpNode actualB3 = TestUtils.findByName(nodes, B3_NAME);
+    DumpNode actualC = TestUtils.findByName(nodes, C_NAME);
+
+    for (DumpNode d : children) {
+      System.out.println(d.getName() + "/" + d.getLabel() + " = " + d);
+    }
+
+    assertEquals(actualB1, actualA.successor(), actualA.successor().getName());
+    assertEquals(actualB2, actualB1.successor(), actualB1.successor().getName());
+    assertEquals(actualB3, actualB2.successor(), actualB2.successor().getName());
+    assertEquals(actualC, actualB3.successor(), actualB3.successor().getName());
+    assertNull(actualC.successor());
+
+    assertTrue(actualA.hasSuccessor());
+    assertTrue(actualB1.hasSuccessor());
+    assertTrue(actualB2.hasSuccessor());
+    assertTrue(actualB3.hasSuccessor());
+    assertFalse(actualC.hasSuccessor());
   }
 
   @Test
diff --git a/testDumper/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestUtils.java b/testDumper/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestUtils.java
index 10a0859b6dec99ff2b7797c0ea382c774722bdfd..b3e8fa92d7ddfcefc0331facc0d27e228f6363b8 100644
--- a/testDumper/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestUtils.java
+++ b/testDumper/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestUtils.java
@@ -163,7 +163,7 @@ public class TestUtils {
     dumpAst.toPlantUml();
     List<DumpNode> result = new ArrayList<>();
     for (DumpNode dumpNode : dumpAst.getDumpNodeList()) {
-      if (dumpNode.isAstNode() && !dumpNode.getInvisible()) {
+      if ((dumpNode.isAstNode() || dumpNode.isNull()) && !dumpNode.getInvisible()) {
         result.add(dumpNode);
       }
     }
@@ -187,7 +187,7 @@ public class TestUtils {
       if (!dumpChildNode.isList()) {
         // then it is a DumpNormalChildNode
         DumpNode target = ((DumpNormalChildNode) dumpChildNode).getDumpNode();
-        if (!target.getInvisible()) {
+        if (target != null && !target.getInvisible()) {
           result.put(dumpChildNode.getName(), target);
         }
       }
@@ -201,7 +201,7 @@ public class TestUtils {
       if (dumpChildNode.isList()) {
         // then it is a DumpListChildNode
         ((DumpListChildNode) dumpChildNode).getInnerDumpNodeList().forEach(inner -> {
-          if (!inner.getDumpNode().getInvisible()) {
+          if (inner.getDumpNode() != null && !inner.getDumpNode().getInvisible()) {
             result.computeIfAbsent(dumpChildNode.getName(), key -> new ArrayList<>()).add(inner.getDumpNode());
           }
         });