From c469a7d9f0d5c70dcbdfa8c3de95f6a7414f4f12 Mon Sep 17 00:00:00 2001
From: rschoene <rene.schoene@tu-dresden.de>
Date: Wed, 12 Oct 2022 17:01:07 +0200
Subject: [PATCH] 3.0.0

- prepare new release
- finished styling of lists
---
 dumpAst.base/src/main/jastadd/DumpAst.relast  |  18 +--
 .../src/main/jastadd/GeneratedNavigation.jrag |  13 ++
 dumpAst.base/src/main/jastadd/Navigation.jrag |   5 +-
 .../src/main/jastadd/TemplateContext.jrag     |  32 +++--
 dumpAst.base/src/main/jastadd/ToYaml.jrag     |  43 ++++---
 dumpAst.base/src/main/jastadd/Transform.jadd  | 119 +++++++++++-------
 .../src/main/jastadd/TransformPlus.jadd       |  24 +++-
 .../src/main/resources/dumpAst.mustache       |  16 +--
 .../src/main/jastadd/featureTest.relast       |   6 +
 .../jastadd/featureTest/FeatureTestMain.java  |  96 +++++++++++---
 .../inf/st/jastadd/testDumper/TestUtils.java  |   2 +-
 pages/docs/using.md                           |   6 +-
 12 files changed, 253 insertions(+), 127 deletions(-)

diff --git a/dumpAst.base/src/main/jastadd/DumpAst.relast b/dumpAst.base/src/main/jastadd/DumpAst.relast
index e6b8d57..572b8b9 100644
--- a/dumpAst.base/src/main/jastadd/DumpAst.relast
+++ b/dumpAst.base/src/main/jastadd/DumpAst.relast
@@ -5,24 +5,24 @@ DumpNode ::=  DumpChildNode* DumpToken* DumpRelation*
  <Name> <Label> <BackgroundColor> <TextColor> <Object:Object> <Invisible:boolean> <Computed:boolean> <ManualStereotypes>
  /InvisiblePath/ ;
 
-InnerDumpNode ::= <OriginalIndex:int> <Label> <LineColor> <TextColor> ;
+InnerDumpNode ::= <OriginalIndex:int> <Label> <LineColor> <TextColor> ;  //TODO remove colors (and label?)
 rel InnerDumpNode.DumpNode <-> DumpNode.ContainerOfInner? ;
-InnerRelationDumpNode ::= <OriginalIndex:int> <Label> <LineColor> <TextColor> ;
+InnerRelationDumpNode ::= <OriginalIndex:int> <Label> <LineColor> <TextColor> ;  //TODO remove colors (and label?)
 rel InnerRelationDumpNode.DumpNode <-> DumpNode.ContainerOfRelationInner* ;
 
-abstract DumpChildNode ::= <Label> <Computed:boolean> ;
-DumpNormalChildNode : DumpChildNode ::= <LineColor> <TextColor> ;
+abstract DumpChildNode ::= <Label> <Computed:boolean> <LineColor> <TextColor> ;
+DumpNormalChildNode : DumpChildNode ::= ;
 rel DumpNormalChildNode.DumpNode <-> DumpNode.ContainerOfNormalChild? ;
 DumpListChildNode : DumpChildNode ::= InnerDumpNode* <Name> ;
 
 abstract DumpToken ::= <Label> <Computed:boolean> ;
-DumpReferenceToken : DumpToken ;
-rel DumpReferenceToken.Value -> DumpNode ;
-DumpReferenceListToken : DumpToken ::= InnerRelationDumpNode* <Name> ;
+DumpReferenceToken : DumpToken ::= <LineColor> <TextColor> ;
+rel DumpReferenceToken.DumpNode -> DumpNode ;
+DumpReferenceListToken : DumpToken ::= InnerRelationDumpNode* <Name> <LineColor> <TextColor> ;
 DumpValueToken : DumpToken ::= <Value:Object> ;
 
-abstract DumpRelation ::= <Label> <Bidirectional:boolean> ;
-DumpNormalRelation : DumpRelation ::= <LineColor> <TextColor> ;
+abstract DumpRelation ::= <Label> <Bidirectional:boolean> <LineColor> <TextColor> ;
+DumpNormalRelation : DumpRelation ::= ;
 rel DumpNormalRelation.DumpNode -> DumpNode ;
 DumpListRelation : DumpRelation ::= InnerRelationDumpNode* <Name> ;
 
diff --git a/dumpAst.base/src/main/jastadd/GeneratedNavigation.jrag b/dumpAst.base/src/main/jastadd/GeneratedNavigation.jrag
index 723bfba..c7ab004 100644
--- a/dumpAst.base/src/main/jastadd/GeneratedNavigation.jrag
+++ b/dumpAst.base/src/main/jastadd/GeneratedNavigation.jrag
@@ -108,6 +108,12 @@ aspect Navigation {
   syn boolean DumpToken.isDumpReferenceToken() = false;
   eq DumpReferenceToken.isDumpReferenceToken() = true;
 
+  /** Tests if DumpToken is a DumpReferenceListToken.
+  *  @return 'true' if this is a DumpReferenceListToken, otherwise 'false'
+  */
+  syn boolean DumpToken.isDumpReferenceListToken() = false;
+  eq DumpReferenceListToken.isDumpReferenceListToken() = true;
+
   /** Tests if DumpToken is a DumpValueToken.
   *  @return 'true' if this is a DumpValueToken, otherwise 'false'
   */
@@ -233,6 +239,13 @@ aspect Navigation {
   eq DumpToken.asDumpReferenceToken() = null;
   eq DumpReferenceToken.asDumpReferenceToken() = this;
 
+  /** casts a DumpToken into a DumpReferenceListToken if possible.
+   *  @return 'this' cast to a DumpReferenceListToken or 'null'
+   */
+  syn DumpReferenceListToken DumpToken.asDumpReferenceListToken();
+  eq DumpToken.asDumpReferenceListToken() = null;
+  eq DumpReferenceListToken.asDumpReferenceListToken() = this;
+
   /** casts a DumpToken into a DumpValueToken if possible.
    *  @return 'this' cast to a DumpValueToken or 'null'
    */
diff --git a/dumpAst.base/src/main/jastadd/Navigation.jrag b/dumpAst.base/src/main/jastadd/Navigation.jrag
index 2c420a7..2bdae26 100644
--- a/dumpAst.base/src/main/jastadd/Navigation.jrag
+++ b/dumpAst.base/src/main/jastadd/Navigation.jrag
@@ -14,6 +14,7 @@ aspect Navigation {
   inh BuildConfig InnerRelationDumpNode.buildConfig();
   inh BuildConfig DumpChildNode.buildConfig();
   inh BuildConfig DumpRelation.buildConfig();
+  inh BuildConfig DumpToken.buildConfig();
   eq DumpAst.getChild().buildConfig() = getBuildConfig();
 
   // --- printConfig ---
@@ -29,6 +30,8 @@ aspect Navigation {
   inh DumpNode InnerRelationDumpNode.containingDumpNode();
   inh DumpNode DumpChildNode.containingDumpNode();
   inh DumpNode DumpRelation.containingDumpNode();
+  inh DumpNode DumpReferenceToken.containingDumpNode();
+  inh DumpNode DumpReferenceListToken.containingDumpNode();
   eq DumpNode.getDumpChildNode().containingDumpNode() = this;
   eq DumpNode.getDumpRelation().containingDumpNode() = this;
   eq DumpNode.getDumpToken().containingDumpNode() = this;
@@ -101,7 +104,7 @@ aspect Navigation {
 
   // --- innerNotNullOrEmpty ---
   syn boolean DumpNormalRelation.innerNotNullOrEmpty() = !printConfig().getRelationWithRank() && getDumpNode() != null && getDumpNode().getObject() != null && getDumpNode().getObject() != DumpAst.EMPTY;
-  syn boolean DumpReferenceToken.innerNotNullOrEmpty() = !printConfig().getRelationWithRank() && getValue() != null && getValue().getObject() != null && getValue().getObject() != DumpAst.EMPTY;
+  syn boolean DumpReferenceToken.innerNotNullOrEmpty() = !printConfig().getRelationWithRank() && getDumpNode() != null && getDumpNode().getObject() != null && getDumpNode().getObject() != DumpAst.EMPTY;
   syn boolean InnerRelationDumpNode.innerNotNullOrEmpty() = !printConfig().getRelationWithRank() && getDumpNode() != null && getDumpNode().getObject() != null && getDumpNode().getObject() != DumpAst.EMPTY;
 
   // === Method naviagtion ===
diff --git a/dumpAst.base/src/main/jastadd/TemplateContext.jrag b/dumpAst.base/src/main/jastadd/TemplateContext.jrag
index db8fb1d..f7c0941 100644
--- a/dumpAst.base/src/main/jastadd/TemplateContext.jrag
+++ b/dumpAst.base/src/main/jastadd/TemplateContext.jrag
@@ -41,24 +41,24 @@ aspect TemplateContext {
   syn String InnerRelationDumpNode.innerNodeName() = getDumpNode() != null ? getDumpNode().name() : null;
   syn String DumpNormalChildNode.innerNodeName() = getDumpNode() != null ? getDumpNode().name() : null;
   syn String DumpNormalRelation.innerNodeName() = getDumpNode() != null ? getDumpNode().name() : null;
-  syn String DumpReferenceToken.innerNodeName() = getValue().name();
+  syn String DumpReferenceToken.innerNodeName() = getDumpNode().name();
 
   // --- label ---
-  syn String DumpChildNode.label() = getLabel() + (getComputed() ? "()" : "");
-  syn String DumpRelation.label() = getLabel();
-  syn String DumpToken.label() = getLabel() + (getComputed() ? "()" : "");
-  syn String DumpNode.label() = getLabel();
+  syn String DumpChildNode.label() = safeLabel(getLabel() + (getComputed() ? "()" : ""));
+  syn String DumpRelation.label() = safeLabel(getLabel());
+  syn String DumpToken.label() = safeLabel(getLabel() + (getComputed() ? "()" : ""));
+  syn String DumpNode.label() = safeLabel(getLabel());
   inh String InnerDumpNode.label();
   inh String InnerRelationDumpNode.label();
   eq DumpListChildNode.getInnerDumpNode(int index).label() {
     InnerDumpNode inner = getInnerDumpNode(index);
-    return
-        (inner.getDumpNode().isEmpty() ? "" : "[" + chooseIndex(inner.getOriginalIndex(), index) + "]");
+    return safeLabel(
+        (inner.getDumpNode().isEmpty() ? "" : "[" + chooseIndex(inner.getOriginalIndex(), index) + "]"));
   }
   eq DumpListRelation.getInnerRelationDumpNode(int index).label() {
     InnerRelationDumpNode inner = getInnerRelationDumpNode(index);
-    return (inner.isComputed() ? " " : inner.getLabel()) +
-        (inner.getDumpNode().isEmpty() ? "" : "[" + chooseIndex(inner.getOriginalIndex(), index) + "]");
+    return safeLabel(inner.getLabel() +
+        (inner.getDumpNode().isEmpty() ? "" : "[" + chooseIndex(inner.getOriginalIndex(), index) + "]"));
   }
   eq DumpReferenceListToken.getInnerRelationDumpNode(int index).label() = "[" + index + "]";
   eq InvisiblePath.getInnerRelationDumpNode(int index).label() = null;
@@ -66,6 +66,10 @@ aspect TemplateContext {
     return originalIndex != 0 ? originalIndex : inheritedIndex;
   }
 
+  static String ASTNode.safeLabel(String s) {
+    return s.isEmpty() ? " " : s;
+  }
+
   // --- bothVisible ---
   boolean ASTNode.bothVisible(DumpNode one, DumpNode two) {
     return one != null && two != null && !one.getInvisible() && !two.getInvisible();
@@ -102,18 +106,12 @@ aspect TemplateContext {
   // --- labelAndTextColor ---
   syn String DumpNode.labelAndTextColor() {
     if (getTextColor().isEmpty()) {
-      return getLabel();
+      return label();
     } else {
-      return "<color:" + getTextColor() + ">" + getLabel() + "</color>";
+      return "<color:" + getTextColor() + ">" + label() + "</color>";
     }
   }
 
-  // --- hasLineColor ---
-  syn boolean InnerDumpNode.needRelationStyling() = !getLineColor().isEmpty() || !getTextColor().isEmpty();
-  syn boolean InnerRelationDumpNode.needRelationStyling() = !getLineColor().isEmpty() || !getTextColor().isEmpty();
-  syn boolean DumpNormalChildNode.needRelationStyling() = !getLineColor().isEmpty() || !getTextColor().isEmpty();
-  syn boolean DumpNormalRelation.needRelationStyling() = !getLineColor().isEmpty() || !getTextColor().isEmpty();
-
   // --- stereotypeList ---
   syn String DumpNode.stereotypeList() {
     StringBuilder sb = new StringBuilder(getManualStereotypes());
diff --git a/dumpAst.base/src/main/jastadd/ToYaml.jrag b/dumpAst.base/src/main/jastadd/ToYaml.jrag
index 7f1cf01..82fd9ab 100644
--- a/dumpAst.base/src/main/jastadd/ToYaml.jrag
+++ b/dumpAst.base/src/main/jastadd/ToYaml.jrag
@@ -32,9 +32,9 @@ aspect ToYaml {
     addYamledList(result, "Headers", getHeaderList(), fromRelation);
 
     // tokens
+    result.put("OrderChildren", getOrderChildren());
     result.put("Scale", getScale());
     result.put("Version", getVersion());
-    result.put("OrderChildren", getOrderChildren());
 
     // attributes
     result.put("debug", debug());
@@ -61,10 +61,9 @@ aspect ToYaml {
     // tokens
     result.put("Name", getName());
     if (!fromRelation) {
-      result.put("Computed", getComputed());
       result.put("BackgroundColor", getBackgroundColor());
-      result.put("TextColor", getTextColor());
       result.put("Invisible", getInvisible());
+      result.put("TextColor", getTextColor());
     }
 
     // attributes
@@ -91,11 +90,13 @@ aspect ToYaml {
     MappingElement result = new MappingElement();
 
     // tokens
-    result.put("Computed", getComputed());
+    result.put("LineColor", getLineColor());
+    result.put("TextColor", getTextColor());
 
     // attributes
-    result.put("label", label());
     result.put("isList", isList());
+    result.put("label", label());
+    result.put("needRelationStyling", needRelationStyling());
     result.put("outerNodeName", outerNodeName());
 
     return result;
@@ -104,14 +105,9 @@ aspect ToYaml {
   syn MappingElement DumpNormalChildNode.toYaml(boolean fromRelation) {
     MappingElement result = super.toYaml(fromRelation);
 
-    // tokens
-    result.put("LineColor", getLineColor());
-    result.put("TextColor", getTextColor());
-
     // attributes
     result.put("bothVisible", bothVisible());
     result.put("innerNodeName", innerNodeName());
-    result.put("needRelationStyling", needRelationStyling());
     return result;
   }
 
@@ -129,13 +125,10 @@ aspect ToYaml {
   syn MappingElement DumpToken.toYaml(boolean fromRelation) {
     MappingElement result = new MappingElement();
 
-    // tokens
-    result.put("Computed", getComputed());
-
     // attributes
+    result.put("isDumpValueToken", isDumpValueToken());
     result.put("isList", isList());
     result.put("label", label());
-    result.put("isDumpValueToken", isDumpValueToken());
     return result;
   }
 
@@ -150,9 +143,14 @@ aspect ToYaml {
   syn MappingElement DumpReferenceToken.toYaml(boolean fromRelation) {
     MappingElement result = super.toYaml(fromRelation);
 
+    // tokens
+    result.put("LineColor", getLineColor());
+    result.put("TextColor", getTextColor());
+
     // attributes
     result.put("innerNodeName", innerNodeName());
     result.put("innerNotNullOrEmpty", innerNotNullOrEmpty());
+    result.put("needRelationStyling", needRelationStyling());
     result.put("outerNodeName", outerNodeName());
     return result;
   }
@@ -162,11 +160,14 @@ aspect ToYaml {
 
     // tokens
     result.put("Name", getName());
+    result.put("LineColor", getLineColor());
+    result.put("TextColor", getTextColor());
 
     // children
     addYamledList(result, "InnerRelationDumpNode", getInnerRelationDumpNodeList(), fromRelation);
 
     // attributes
+    result.put("needRelationStyling", needRelationStyling());
     result.put("outerNodeName", outerNodeName());
     return result;
   }
@@ -176,10 +177,13 @@ aspect ToYaml {
 
     // tokens
     result.put("Bidirectional", getBidirectional());
+    result.put("LineColor", getLineColor());
+    result.put("TextColor", getTextColor());
 
     // attributes
     result.put("isList", isList());
     result.put("label", label());
+    result.put("needRelationStyling", needRelationStyling());
     result.put("outerNodeName", outerNodeName());
     return result;
   }
@@ -187,15 +191,10 @@ aspect ToYaml {
   syn MappingElement DumpNormalRelation.toYaml(boolean fromRelation) {
     MappingElement result = super.toYaml(fromRelation);
 
-    // tokens
-    result.put("LineColor", getLineColor());
-    result.put("TextColor", getTextColor());
-
     // attributes
     result.put("bothVisible", bothVisible());
     result.put("innerNodeName", innerNodeName());
     result.put("innerNotNullOrEmpty", innerNotNullOrEmpty());
-    result.put("needRelationStyling", needRelationStyling());
     return result;
   }
 
@@ -220,9 +219,9 @@ aspect ToYaml {
     // attributes
     result.put("bothVisible", bothVisible());
     result.put("innerNodeName", innerNodeName());
-    result.put("outerNodeName", outerNodeName());
     result.put("label", label());
     result.put("needRelationStyling", needRelationStyling());
+    result.put("outerNodeName", outerNodeName());
     return result;
   }
 
@@ -237,9 +236,9 @@ aspect ToYaml {
     result.put("bothVisible", bothVisible());
     result.put("innerNodeName", innerNodeName());
     result.put("innerNotNullOrEmpty", innerNotNullOrEmpty());
-    result.put("outerNodeName", outerNodeName());
     result.put("label", label());
     result.put("needRelationStyling", needRelationStyling());
+    result.put("outerNodeName", outerNodeName());
     return result;
   }
 
@@ -279,7 +278,7 @@ aspect ToYaml {
     if (value == null || value.equals("null")) {
       return StringElement.of("null");
     }
-    if (value.isEmpty()) {
+    if (value.isBlank()) {
       return StringElement.of(value);
     }
     return containsAny(value, ",#[{\"\n") ?
diff --git a/dumpAst.base/src/main/jastadd/Transform.jadd b/dumpAst.base/src/main/jastadd/Transform.jadd
index 9606b1b..eecc082 100644
--- a/dumpAst.base/src/main/jastadd/Transform.jadd
+++ b/dumpAst.base/src/main/jastadd/Transform.jadd
@@ -192,6 +192,7 @@ aspect Transform {
     listChild.setLabel(childName);
     listChild.setName(tti.nextName());
     node.addDumpChildNode(listChild);
+    RelationStyle style = applyStyle(listChild);
 
     int index = -1;
     for (Object target : targetList) {
@@ -203,14 +204,14 @@ aspect Transform {
       if (target != null && targetNode != null) {
         InnerDumpNode inner = new InnerDumpNode().setDumpNode(targetNode).setOriginalIndex(index);
         listChild.addInnerDumpNode(inner);
-        applyStyle(inner);
+        inner.applyStyle(style);
       }
     }
     if (listChild.getNumInnerDumpNode() == 0) {
       InnerDumpNode inner = new InnerDumpNode().setDumpNode(transform(tti, EMPTY,
           options.asNormal(false).allowNullObjectsOnce()));
       listChild.addInnerDumpNode(inner);
-      applyStyle(inner);
+      inner.applyStyle(style);
     }
     return true;
   }
@@ -254,19 +255,15 @@ aspect Transform {
     listChild.setComputed(computed);
     listChild.setLabel(childName);
     listChild.setName(tti.nextName());
+    node.addDumpChildNode(listChild);
+    RelationStyle style = applyStyle(listChild);
 
-    boolean notAddedYet = true;
     for (Object target : targetList) {
       DumpNode targetNode = transform(tti, target, options.asNormal(false).computed(computed));
       if (target != null && targetNode != null) {
         InnerDumpNode inner = new InnerDumpNode().setDumpNode(targetNode);
         listChild.addInnerDumpNode(inner);
-        if (notAddedYet) {
-          // list child has to be added before application of style, thus inline adding with condition
-          node.addDumpChildNode(listChild);
-          notAddedYet = false;
-        }
-        applyStyle(inner);
+        inner.applyStyle(style);
       }
     }
     return true;
@@ -301,6 +298,7 @@ aspect Transform {
     listRelation.setLabel(otherMethod.getName());
     listRelation.setName(tti.nextName());
     node.addDumpRelation(listRelation);
+    RelationStyle style = applyStyle(listRelation);
 
     int index = -1;
     for (Object target : targetList) {
@@ -312,14 +310,14 @@ aspect Transform {
       if (target != null && targetNode != null) {
         InnerRelationDumpNode inner = new InnerRelationDumpNode().setDumpNode(targetNode).setOriginalIndex(index);
         listRelation.addInnerRelationDumpNode(inner);
-        applyStyle(inner);
+        inner.applyStyle(style);
       }
     }
     if (listRelation.getNumInnerRelationDumpNode() == 0) {
       InnerRelationDumpNode inner = new InnerRelationDumpNode().setDumpNode(transform(tti, EMPTY,
           options.asNormal(false).allowNullObjectsOnce()));
       listRelation.addInnerRelationDumpNode(inner);
-      applyStyle(inner);
+      inner.applyStyle(style);
     }
     return true;
   }
@@ -336,14 +334,19 @@ aspect Transform {
       return false;
     }
     Object target = otherMethod.getMethod().invoke(obj);
+    // local function to add common member and add the token-node to the given node
+    java.util.function.Consumer<DumpToken> setMemberAndAdd = token -> {
+      token.setLabel(otherMethod.getName());
+      token.setComputed(otherMethod.asTokenMethod().isAttributeMethod());
+      node.addDumpToken(token);
+    };
     if (target != null) {
-      DumpToken token = null;
       boolean atLeastOneASTNode = false;
       if (Iterable.class.isAssignableFrom(target.getClass())) {
         java.util.List<DumpNode> nodes = new java.util.ArrayList<>();
-        Iterable iterable = (Iterable) target;
+        Iterable<?> iterable = (Iterable<?>) target;
         for (Object element : iterable) {
-          // TODO check if isAstNode for first non-null. if yes, use DumpReferenceListToken, other DumpValueToken
+          // check if isAstNode for first non-null. if yes, use DumpReferenceListToken, otherwise DumpValueToken
           DumpNode nodeForElement = transform(tti, element, options.asRelation());
           nodes.add(nodeForElement);
           if (nodeForElement != null && nodeForElement.isAstNode()) {
@@ -353,30 +356,29 @@ aspect Transform {
         if (atLeastOneASTNode) {
           DumpReferenceListToken listToken = new DumpReferenceListToken();
           listToken.setName(tti.nextName());
+          setMemberAndAdd.accept(listToken);
+          RelationStyle style = applyStyle(listToken);
           nodes.forEach(element -> {
-            listToken.addInnerRelationDumpNode(new InnerRelationDumpNode().setDumpNode(element));
+            InnerRelationDumpNode inner = new InnerRelationDumpNode().setDumpNode(element);
+            listToken.addInnerRelationDumpNode(inner);
+            inner.applyStyle(style);
           });
-          token = listToken;
         }
       }
       if (!atLeastOneASTNode) {
         DumpNode targetNode = transform(tti, target, options.asRelation());
         if (targetNode != null && targetNode.isAstNode()) {
-          token = new DumpReferenceToken().setValue(targetNode);
+          DumpReferenceToken token = new DumpReferenceToken().setDumpNode(targetNode);
+          setMemberAndAdd.accept(token);
+          applyStyle(token.asDumpReferenceToken());
         } else {
           if (getBuildConfig().getIncludeEmptyString() || !target.toString().isEmpty()) {
             DumpValueToken valueToken = new DumpValueToken();
             valueToken.setValue(target);
-            token = valueToken;
+            setMemberAndAdd.accept(valueToken);
           }
         }
       }
-      if (token != null) {
-        token.setLabel(otherMethod.getName());
-        token.setComputed(otherMethod.asTokenMethod().isAttributeMethod());
-        node.addDumpToken(token);
-        // TODO apply style
-      }
     }
     return true;
   }
@@ -411,14 +413,9 @@ aspect Transform {
   }
 
   // === RelationStyle ===
-  interface RelationStylable<SELF> {
+  interface RelationStyleDefinable {
     // more methods in TransformPlus.jadd
 
-    // tokens
-    SELF setLabel(String name);
-    SELF setTextColor(String textColor);
-    SELF setLineColor(String lineColor);
-
     // attributes
     BuildConfig buildConfig();
     boolean isComputed();
@@ -427,28 +424,56 @@ aspect Transform {
     DumpNode containingDumpNode();
     DumpNode getDumpNode();
   }
-  DumpNormalChildNode implements RelationStylable<DumpNormalChildNode>;
-  syn String DumpNormalChildNode.initialLabel() = getLabel();
-  syn boolean DumpNormalChildNode.isComputed() = getComputed();
-  syn boolean DumpNormalChildNode.isContainment() = true;
+  interface RelationStylable<SELF> {
+    // more methods in TransformPlus.jadd
 
-  InnerDumpNode implements RelationStylable<InnerDumpNode>;
-  syn String InnerDumpNode.initialLabel() = containingDumpListChildNode().getLabel();
-  syn boolean InnerDumpNode.isComputed() = containingDumpListChildNode().getComputed();
-  syn boolean InnerDumpNode.isContainment() = true;
+    // tokens
+    String getTextColor();
+    String getLineColor();
 
-  DumpNormalRelation implements RelationStylable<DumpNormalRelation>;
-  syn String DumpNormalRelation.initialLabel() = getLabel();
-  syn boolean DumpNormalRelation.isComputed() = false;
-  syn boolean DumpNormalRelation.isContainment() = false;
+    SELF setLabel(String name);
+    SELF setTextColor(String textColor);
+    SELF setLineColor(String lineColor);
+  }
+
+  DumpChildNode implements RelationStyleDefinable, RelationStylable<DumpChildNode>;
+  syn String DumpChildNode.initialLabel() = getLabel();
+  syn boolean DumpChildNode.isComputed() = getComputed();
+  syn boolean DumpChildNode.isContainment() = true;
+
+  syn DumpNode DumpListChildNode.getDumpNode() = null;
+
+  DumpRelation implements RelationStyleDefinable, RelationStylable<DumpRelation>;
+  syn String DumpRelation.initialLabel() = getLabel();
+  syn boolean DumpRelation.isComputed() = false;
+  syn boolean DumpRelation.isContainment() = false;
 
+  syn DumpNode DumpListRelation.getDumpNode() = null;
+
+  InnerDumpNode implements RelationStylable<InnerDumpNode>;
   InnerRelationDumpNode implements RelationStylable<InnerRelationDumpNode>;
-  syn String InnerRelationDumpNode.initialLabel() = containingDumpListRelation().getLabel();
-  syn boolean InnerRelationDumpNode.isComputed() = containingDumpListRelation() == null;
-  syn boolean InnerRelationDumpNode.isContainment() = false;
 
-  private void DumpAst.applyStyle(RelationStylable<?> stylable) {
-    stylable.applyStyle(getPrintConfig().getRelationStyleDefinition());
+  DumpReferenceListToken implements RelationStyleDefinable, RelationStylable<DumpReferenceListToken>;
+  syn String DumpReferenceListToken.initialLabel() = getLabel();
+  syn boolean DumpReferenceListToken.isComputed() = getComputed();
+  syn boolean DumpReferenceListToken.isContainment() = false;
+  syn DumpNode DumpReferenceListToken.getDumpNode() = null;
+
+  DumpReferenceToken implements RelationStyleDefinable, RelationStylable<DumpReferenceToken>;
+  syn String DumpReferenceToken.initialLabel() = getLabel();
+  syn boolean DumpReferenceToken.isComputed() = getComputed();
+  syn boolean DumpReferenceToken.isContainment() = false;
+
+  private void DumpAst.applyStyle(RelationStylable<?> stylable, RelationStyle style) {
+    stylable.applyStyle(style);
+  }
+  private void DumpAst.applyStyle(DumpToken token, RelationStyle style) {
+    if (token.isDumpReferenceListToken()) {
+      token.asDumpReferenceListToken().applyStyle(style);
+    } else if (token.isDumpReferenceToken()) {
+      token.asDumpReferenceToken().applyStyle(style);
+    }
+    // otherwise ignore
   }
 
   // --- isTypeEnabled ---
diff --git a/dumpAst.base/src/main/jastadd/TransformPlus.jadd b/dumpAst.base/src/main/jastadd/TransformPlus.jadd
index 2296ad7..ff3b8e5 100644
--- a/dumpAst.base/src/main/jastadd/TransformPlus.jadd
+++ b/dumpAst.base/src/main/jastadd/TransformPlus.jadd
@@ -1,19 +1,35 @@
 // more stuff from Transform that is not correctly handled by the IntelliJ plugin
 aspect Transform {
-  interface RelationStylable<SELF> {
+  interface RelationStyleDefinable {
     default RelationStyle createDefaultStyle() {
       return new RelationStyle()
         .setLabel(initialLabel())
-        .setTextColor(isComputed() ? buildConfig().getStyleInformation().getComputedColor() : "")
+        .setLineColor(isComputed() ? buildConfig().getStyleInformation().getComputedColor() : "")
         .setTextColor(isComputed() ? buildConfig().getStyleInformation().getComputedColor() : "");
     }
-    default void applyStyle(RelationStyleDefinition relationStyleDefinition) {
+    default RelationStyle getStyle(RelationStyleDefinition relationStyleDefinition) {
       RelationStyle style = createDefaultStyle();
-      relationStyleDefinition.style(containingDumpNode().getObject(), getDumpNode().getObject(),
+      relationStyleDefinition.style(containingDumpNode().getObject(),
+          getDumpNode() != null ? getDumpNode().getObject() : null,
           isComputed(), isContainment(), style);
+      return style;
+    }
+  }
+
+  interface RelationStylable<SELF> {
+    default boolean needRelationStyling() {
+      return !getLineColor().isEmpty() || !getTextColor().isEmpty();
+    }
+    default void applyStyle(RelationStyle style) {
       setLabel(style.getLabel());
       setTextColor(style.getTextColor());
       setLineColor(style.getLineColor());
     }
   }
+
+  private <T extends RelationStyleDefinable & RelationStylable<?>> RelationStyle DumpAst.applyStyle(T styleDefinable) {
+    RelationStyle result = styleDefinable.getStyle(getPrintConfig().getRelationStyleDefinition());
+    styleDefinable.applyStyle(result);
+    return result;
+  }
 }
diff --git a/dumpAst.base/src/main/resources/dumpAst.mustache b/dumpAst.base/src/main/resources/dumpAst.mustache
index 4cc8283..5785dd7 100644
--- a/dumpAst.base/src/main/resources/dumpAst.mustache
+++ b/dumpAst.base/src/main/resources/dumpAst.mustache
@@ -42,16 +42,16 @@ object "{{{labelAndTextColor}}}" as {{{Name}}} {{{stereotypeList}}} {{#Backgroun
     {{^Invisible}}
       {{#isList}}
 circle " " as {{{Name}}}
-{{{outerNodeName}}} .-[norank{{#Computed}},#{{{computedColor}}}{{/Computed}}]-> {{{Name}}}{{#label}} : {{{label}}}{{/label}}
+{{{outerNodeName}}} .-[norank]-> {{{Name}}} {{#needRelationStyling}}{{>RelationStyle}}{{/needRelationStyling}} : "{{{label}}}"
         {{#InnerRelationDumpNode}}
           {{#bothVisible}}
-{{{Name}}} .[#black{{#Computed}},#{{{computedColor}}}{{/Computed}}{{#innerNotNullOrEmpty}},norank{{/innerNotNullOrEmpty}}].> {{{innerNodeName}}} {{#needRelationStyling}}{{>RelationStyle}}{{/needRelationStyling}}{{#label}} : {{{label}}}{{/label}}
+{{{Name}}} .{{#innerNotNullOrEmpty}}[norank]{{/innerNotNullOrEmpty}}.> {{{innerNodeName}}} {{#needRelationStyling}}{{>RelationStyle}}{{/needRelationStyling}} : "{{{label}}}"
           {{/bothVisible}}
         {{/InnerRelationDumpNode}}
       {{/isList}}
       {{^isList}}
         {{^isDumpValueToken}}
-{{{outerNodeName}}} .[#black{{#Computed}},#{{{computedColor}}}{{/Computed}}{{#innerNotNullOrEmpty}},norank{{/innerNotNullOrEmpty}}].> {{{innerNodeName}}}{{#label}} : {{{label}}}{{/label}}
+{{{outerNodeName}}} .{{#innerNotNullOrEmpty}}[norank]{{/innerNotNullOrEmpty}}.> {{{innerNodeName}}} {{#needRelationStyling}}{{>RelationStyle}}{{/needRelationStyling}} : "{{{label}}}"
         {{/isDumpValueToken}}
       {{/isList}}
     {{/Invisible}}
@@ -59,16 +59,16 @@ circle " " as {{{Name}}}
   {{#DumpChildNodes}}
     {{#isList}}
 circle " " as {{{Name}}}
-{{{outerNodeName}}} *-{{#Computed}}[#{{{computedColor}}}]{{/Computed}}- {{{Name}}} {{#label}} : {{{label}}}{{/label}}
+{{{outerNodeName}}} *-- {{{Name}}} {{#needRelationStyling}}{{>RelationStyle}}{{/needRelationStyling}} : "{{{label}}}"
       {{#InnerDumpNodes}}
         {{#bothVisible}}
-{{{Name}}} -{{#Computed}}[#{{{computedColor}}}]{{/Computed}}- {{{innerNodeName}}} {{#needRelationStyling}}{{>RelationStyle}}{{/needRelationStyling}}{{#label}} : {{{label}}}{{/label}}
+{{{Name}}} -- {{{innerNodeName}}} {{#needRelationStyling}}{{>RelationStyle}}{{/needRelationStyling}} : "{{{label}}}"
         {{/bothVisible}}
       {{/InnerDumpNodes}}
     {{/isList}}
     {{^isList}}
       {{#bothVisible}}
-{{{outerNodeName}}} *-{{#Computed}}[#{{{computedColor}}}]{{/Computed}}- {{{innerNodeName}}} {{#needRelationStyling}}{{>RelationStyle}}{{/needRelationStyling}}{{#label}} : {{{label}}}{{/label}}
+{{{outerNodeName}}} *-- {{{innerNodeName}}} {{#needRelationStyling}}{{>RelationStyle}}{{/needRelationStyling}} : "{{{label}}}"
       {{/bothVisible}}
     {{/isList}}
   {{/DumpChildNodes}}
@@ -76,13 +76,13 @@ circle " " as {{{Name}}}
     {{#isList}}
       {{#InnerRelationDumpNode}}
         {{#bothVisible}}
-{{{outerNodeName}}} {{#Bidirectional}}<{{/Bidirectional}}-{{#innerNotNullOrEmpty}}[norank]{{/innerNotNullOrEmpty}}-> {{{innerNodeName}}} {{#needRelationStyling}}{{>RelationStyle}}{{/needRelationStyling}}{{#label}} : {{{label}}}{{/label}}
+{{{outerNodeName}}} {{#Bidirectional}}<{{/Bidirectional}}-{{#innerNotNullOrEmpty}}[norank]{{/innerNotNullOrEmpty}}-> {{{innerNodeName}}} {{#needRelationStyling}}{{>RelationStyle}}{{/needRelationStyling}} : "{{{label}}}"
         {{/bothVisible}}
       {{/InnerRelationDumpNode}}
     {{/isList}}
     {{^isList}}
       {{#bothVisible}}
-{{{outerNodeName}}} {{#Bidirectional}}<{{/Bidirectional}}-{{#innerNotNullOrEmpty}}[norank]{{/innerNotNullOrEmpty}}-> {{{innerNodeName}}} {{#needRelationStyling}}{{>RelationStyle}}{{/needRelationStyling}}{{#label}} : {{{label}}}{{/label}}
+{{{outerNodeName}}} {{#Bidirectional}}<{{/Bidirectional}}-{{#innerNotNullOrEmpty}}[norank]{{/innerNotNullOrEmpty}}-> {{{innerNodeName}}} {{#needRelationStyling}}{{>RelationStyle}}{{/needRelationStyling}} : "{{{label}}}"
       {{/bothVisible}}
     {{/isList}}
   {{/DumpRelations}}
diff --git a/dumpAst.prototyping/src/main/jastadd/featureTest.relast b/dumpAst.prototyping/src/main/jastadd/featureTest.relast
index 7e108fe..003417c 100644
--- a/dumpAst.prototyping/src/main/jastadd/featureTest.relast
+++ b/dumpAst.prototyping/src/main/jastadd/featureTest.relast
@@ -24,3 +24,9 @@ T3 : AbstractT ;
 rel AbstractT.oneA -> A ;
 rel AbstractT.maybeA? -> A ;
 rel AbstractT.manyA* -> A ;
+
+OtherRoot : Nameable ::= E* F*;
+E : Nameable ;
+F : Nameable ;
+
+rel E.myF* <-> F.myE* ;
diff --git a/dumpAst.prototyping/src/main/java/de/tudresden/inf/st/jastadd/featureTest/FeatureTestMain.java b/dumpAst.prototyping/src/main/java/de/tudresden/inf/st/jastadd/featureTest/FeatureTestMain.java
index 3a6c733..bebca7d 100644
--- a/dumpAst.prototyping/src/main/java/de/tudresden/inf/st/jastadd/featureTest/FeatureTestMain.java
+++ b/dumpAst.prototyping/src/main/java/de/tudresden/inf/st/jastadd/featureTest/FeatureTestMain.java
@@ -9,6 +9,7 @@ import org.jastadd.featureTest.ast.*;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.Arrays;
 
 /**
  * Main class of feature test.
@@ -18,6 +19,38 @@ import java.nio.file.Paths;
 public class FeatureTestMain {
 
   public static void main(String[] args) throws IOException, TransformationException {
+    new FeatureTestMain().run(args);
+  }
+
+  private void run(String[] args) throws TransformationException, IOException {
+    String mode = args.length == 0 ? "default" : args[0];
+    System.out.println(Arrays.toString(args) + " -> " + mode);
+
+    final DumpBuilder builder;
+
+    switch (mode) {
+      case "default":
+      case "withRoot":
+        builder = withRoot();
+        break;
+      case "withOtherRoot":
+        builder = withOtherRoot();
+        break;
+      default:
+        throw new UnsupportedOperationException("Unknown mode: " + mode);
+    }
+
+    Path pathToYaml = Paths.get("featureTest.yml");
+    Path pathToPng = Paths.get("featureTest.png");
+    Path pathToSvg = Paths.get("featureTest.svg");
+
+    builder.dumpAsYaml(pathToYaml, true);
+    builder.dumpAsPNG(pathToPng);
+    builder.dumpAsSVG(pathToSvg);
+    builder.dumpAsSource(Paths.get("featureTest.puml"));
+  }
+
+  private DumpBuilder withRoot() {
     Root root = new Root();
     root.setName("Root1");
     A a = new A().setName("A2");
@@ -38,10 +71,7 @@ public class FeatureTestMain {
     root.addB(b2);
 //    root.setC(c);
 
-    Path pathToYaml = Paths.get("featureTest.yml");
-    Path pathToPng = Paths.get("featureTest.png");
-    Path pathToSvg = Paths.get("featureTest.svg");
-    DumpBuilder builder = Dumper
+    return Dumper
 //        .read(null)
         .read(root)
 //        .enableRelationWithRank()
@@ -55,7 +85,8 @@ public class FeatureTestMain {
           if (parentNode instanceof Root && childNode instanceof B) {
             return !"B3".equals(((B) childNode).getName());
           }
-          return !contextName.equals("MyC");
+//          return !contextName.equals("MyC");
+          return true;
         })
         .includeRelation((sourceNode, targetNode, roleName) ->
             !(sourceNode instanceof B) || !((B) sourceNode).getName().equals("B6.1.1"))
@@ -74,15 +105,27 @@ public class FeatureTestMain {
         })
         .skinParam(SkinParamBooleanSetting.Shadowing, false)
         .relationStyle((source, target, isComputed, isContainment, style) -> {
-          System.out.println(style.getLabel() + ", computed: " + isComputed + ", containment: " + isContainment);
+          System.out.println(style.getLabel() + ", computed: " + isComputed + ", containment: " + isContainment + ", textC: " + style.getTextColor() + ", lineC: " + style.getLineColor());
           if (isContainment && target != null && style.getLabel().equals(target.getClass().getSimpleName())) {
             style.setLabel("");
           }
-          if (style.getLabel().equals("ManyA")) {
-            style.setLabel("ManyA of " + ((Nameable) source).getName());
-          } else if (style.getLabel().equals("OneA") || style.getLabel().equals("MaybeC?")) {
-            style.setTextColor("red");
-            style.setLineColor("green");
+          switch (style.getLabel()) {
+            case "ManyA":
+              style.setLabel("ManyA of " + ((Nameable) source).getName());
+              break;
+            case "OneA":
+            case "MaybeC?":
+              style.setTextColor("red");
+              style.setLineColor("green");
+              break;
+            case "B":
+              style.setLineColor("orange");
+              style.setTextColor("orange");
+              break;
+            case "collectBs":
+              style.setLineColor("purple");
+              style.setTextColor("purple");
+              break;
           }
         })
         .<ASTNode<?>>nodeStyle((node, style) -> {
@@ -94,10 +137,33 @@ public class FeatureTestMain {
           }
           style.setLabel(node.getClass().getSimpleName() + ASTNode.counter++);
         });
+  }
 
-    builder.dumpAsYaml(pathToYaml, true);
-    builder.dumpAsPNG(pathToPng);
-    builder.dumpAsSVG(pathToSvg);
-    builder.dumpAsSource(Paths.get("featureTest.puml"));
+  private DumpBuilder withOtherRoot() {
+    OtherRoot root = new OtherRoot();
+    E e1 = new E().setName("E1");
+    E e2 = new E().setName("E2");
+    E e3 = new E().setName("E3");
+    E e4 = new E().setName("E4");
+    F f1 = new F().setName("F1");
+    F f2 = new F().setName("F2");
+    F f3 = new F().setName("F3");
+    F f4 = new F().setName("F4");
+    for (E e : new E[]{e1, e2, e3, e4}) {
+      root.addE(e);
+    }
+    for (F f : new F[]{f1, f2, f3, f4}) {
+      root.addF(f);
+    }
+    e1.addMyF(f1);
+    e1.addMyF(f2);
+    e2.addMyF(f2);
+    e3.addMyF(f2);
+
+    return Dumper.read(root)
+        .includeToken((node, tokenName, value) -> false)
+        .nodeStyle((node, style) -> {
+          style.setLabel(((Nameable) node).getName());
+        });
   }
 }
diff --git a/dumpAst.tests/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestUtils.java b/dumpAst.tests/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestUtils.java
index 5246c9f..c1228e0 100644
--- a/dumpAst.tests/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestUtils.java
+++ b/dumpAst.tests/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestUtils.java
@@ -212,7 +212,7 @@ public class TestUtils {
     for (DumpToken dumpToken : node.getDumpTokenList()) {
       if (!dumpToken.isDumpValueToken() && !dumpToken.isList()) {
         // then it is a DumpReferenceToken
-        DumpNode target = ((DumpReferenceToken) dumpToken).getValue();
+        DumpNode target = ((DumpReferenceToken) dumpToken).getDumpNode();
         if (!target.getInvisible()) {
           result.put(dumpToken.getLabel(), target);
         }
diff --git a/pages/docs/using.md b/pages/docs/using.md
index 26cd6c0..f01ec77 100644
--- a/pages/docs/using.md
+++ b/pages/docs/using.md
@@ -31,7 +31,7 @@ public class Main {
           style.useSimpleName();  // (4)
           style.setBackgroundColor(node.size() > 30 ? "blue" : "white");  // (5)
         })
-        .relationStyle((source, target, style) -> {
+        .relationStyle((source, target, isComputed, isContainment, style) -> {
           if (style.getLabel().equals("EndEffector")) {
             style.setLabel("");  // (6)
           }
@@ -57,9 +57,9 @@ The steps in detail:
 - (2) The first call is always `Dumper.read()` to which the (part of the) AST is provided as an argument. It returns an object of type [DumpBuilder](../ragdoc/#/type/DumpBuilder). With this object, the output can be changed.
 - (3) [Optional] You can only include nodes of certain types by using `includeChild` and passing a lambda function, which is called for every node and should return `true` if the `childNode` shall be included in the output. It defaults to returning `true` for every node. Here, all children of a `Robot` node (except if the name of the robot is `"Robot2"`) and all children of the context named `"EndEffector"` are excluded.
 - (4) and (5) [Optional] To style a node, the method `nodeStyle` is used. It accepts a lambda function, in which the style of every node can be customized by calling methods on the given `Style` object, e.g. setting the name (5) or background color (6). The default name is to use the class name along with the hashcode of the object (`node == null ? "null" : node.getClass().getSimpleName() + "@" + Integer.toHexString(node.hashCode())`).
-- (6) [Optional] To style a relation, the method `relationStyle` is used. It is used similar to `nodeStyle`, but is called with source and target node of a relation (containment and non-containment). Note, that (a) the default label can be retrieved with `style.getLabel()` and (b) indices are always appended to the set label.
+- (6) [Optional] To style a relation, the method `relationStyle` is used. It is used similar to `nodeStyle`, but is called with source and target node of a relation (containment and non-containment). Note, that (a) the default label can be retrieved with `style.getLabel()`, (b) `target` can be `null` for lists, (c) secondary connections from a list node to the target nodes use the style of the relation and use their index as label.
 - (7) [Optional] There are a few ways to style the output in general through [SkinParam](https://plantuml.com/de/skinparam). Not all of those parameters are supported; see `SkinParamBooleanSetting` and `SkinParamStringSetting`.
 - (8), (9) and (10) To create an output image, use `dumpAsPng` for PNG (`dumpAsSVG` for SVG). Those methods do not return the builder object, as they produce an output. They potentially throw two kinds of exception: `IOException` (when the file could not be written) and `TransformationException` (when the AST could not be processed). As shown here, the builder object must be stored in a variable to create multiple outputs. Alternatively to a file, the result can be written to a given `OutputStream` (10).
 
 To summarise the required steps: Specify the AST to read in, configure the output, and write ("dump") the output.
-For more configuration options, please consult the [API Docs of DumpBuilder](../ragdoc/#/type/DumpBuilder).
+For more configuration options, please consult the [API Docs of DumpBuilder](../ragdoc/#/type/DumpBuilder) or check out [FeatureTestMain](https://git-st.inf.tu-dresden.de/jastadd/dumpAst/-/blob/main/dumpAst.prototyping/src/main/java/de/tudresden/inf/st/jastadd/featureTest/FeatureTestMain.java).
-- 
GitLab