From 49d05e03c63247c84ae0ec1c6daf40957359ff29 Mon Sep 17 00:00:00 2001
From: Johannes Mey <johannes.mey@tu-dresden.de>
Date: Tue, 26 Feb 2019 14:48:09 +0100
Subject: [PATCH] resolver helper now works for all sorts of relations

---
 .gitignore                                    |   1 +
 src/main/jastadd/Backend.jadd                 |  63 +-
 src/test/jastadd/resolver/MyRefResolver.jadd  |  12 +
 src/test/jastadd/resolver/Resolver.relast     |  16 +
 .../jastadd/relast/tests/ResolverHelper.java  | 722 +++++++++++++++++-
 5 files changed, 803 insertions(+), 11 deletions(-)

diff --git a/.gitignore b/.gitignore
index dd28858..0e0b716 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,7 @@
 build
 src/gen-res/
 src/gen/
+out/
 *.class
 src/test/jastadd/relations/Relations.ast
 src/test/jastadd/relations/Relations.jadd
diff --git a/src/main/jastadd/Backend.jadd b/src/main/jastadd/Backend.jadd
index 7e02ef0..59d8c53 100644
--- a/src/main/jastadd/Backend.jadd
+++ b/src/main/jastadd/Backend.jadd
@@ -217,7 +217,7 @@ aspect BackendDirectedAPI {
     sb.append(".get" + nameCapitalized() + "() {\n");
     if (resolverHelper) {
       sb.append(ind(2) + "if (get" + getImplAttributeName() + "() != null && get" + getImplAttributeName() + "().unresolved()) {\n");
-      sb.append(ind(3) + "set" + getImplAttributeName() + "(resolve" + nameCapitalized() + "(get" + getImplAttributeName() + "().asUnresolved" + ofTypeDecl() + "().get__token()));\n");
+      sb.append(ind(3) + "set" + nameCapitalized() + "(resolve" + nameCapitalized() + "(get" + getImplAttributeName() + "().asUnresolved" + ofTypeDecl() + "().get__token()));\n");
       sb.append(ind(2) + "}\n");
     }
     sb.append(ind(2) + "return get" + getImplAttributeName() + "();\n");
@@ -250,6 +250,20 @@ aspect BackendDirectedAPI {
     sb.append(".get" + nameCapitalized() + "List() {\n");
     sb.append(ind(2) + ASTNode.listClass + "<" + ofTypeDecl() + "> l = get"
       + getImplAttributeName() + "();\n");
+    // resolve the entire list
+    if (resolverHelper) {
+      sb.append(ind(2) + "int removedElements = 0;\n");
+      sb.append(ind(2) + "if (l != null) {\n");
+        sb.append(ind(3) + "for (int i = 0; i < l.size() - removedElements; i++) {\n");
+          sb.append(ind(4) + "if (get" + getImplAttributeName() + "().get(i) != null && get" + getImplAttributeName() + "().get(i).unresolved()) {\n");
+            sb.append(ind(5) + ofTypeDecl() + " element = l.remove(i);\n");
+            sb.append(ind(5) + "add" + nameCapitalized() + "(resolve" + nameCapitalized() + "(element.asUnresolved" + ofTypeDecl() + "().get__token(), i));\n");
+            sb.append(ind(5) + "i--; // go back an index, because we removed an element\n");
+            sb.append(ind(5) + "removedElements++; // no need to iterate over the removed elements again\n");
+          sb.append(ind(4) + "}\n");
+        sb.append(ind(3) + "}\n");
+      sb.append(ind(2) + "}\n");
+    }
     sb.append(ind(2) + "return l != null ? Collections.unmodifiableList(l) : Collections.emptyList();\n");
     sb.append(ind(1) + "}\n");
   }
@@ -306,20 +320,42 @@ aspect BackendBidirectionalAPI {
     if (!isOpt) {
       sb.append(ind(2) + "assertNotNull(o);\n");
     }
-    sb.append(ind(2) + "if (get" + getImplAttributeName() + "() != null) {\n");
+    // unset the old opposite
+//    if (resolverHelper) {
+//      sb.append(ind(2) + "if (!o.unresolved() && get" + getImplAttributeName() + "() != null) {\n");
+//    } else {
+      sb.append(ind(2) + "if (get" + getImplAttributeName() + "() != null) {\n");
+//    }
     sb.append(ind(3) + "get" + getImplAttributeName() + "().set" + otherSide().getImplAttributeName() + "(null);\n");
     sb.append(ind(2) + "}\n");
-    sb.append(ind(2) + "if (o != null && o.get" + otherSide().getImplAttributeName() + "() != null) {\n");
+    if (resolverHelper) {
+      sb.append(ind(2) + "if (!o.unresolved() && o != null && o.get" + otherSide().getImplAttributeName() + "() != null) {\n");
+    } else {
+      sb.append(ind(2) + "if (o != null && o.get" + otherSide().getImplAttributeName() + "() != null) {\n");
+    }
     sb.append(ind(3) + "o.get" + otherSide().getImplAttributeName() + "().set" + getImplAttributeName() + "(null);\n");
     sb.append(ind(2) + "}\n");
     sb.append(ind(2) + "set" + getImplAttributeName() + "(o);\n");
-    if (isOpt) {
-      sb.append(ind(2) + "if (o != null) {\n");
-      sb.append(ind(3) + "o.set" + otherSide().getImplAttributeName() + "(this);\n");
+    if (resolverHelper) {
+      sb.append(ind(2) + "if (!o.unresolved()) {\n");
+      if (isOpt) {
+        sb.append(ind(3) + "if (o != null) {\n");
+        sb.append(ind(4) + "o.set" + otherSide().getImplAttributeName() + "(this);\n");
+        sb.append(ind(3) + "}\n");
+      } else {
+        sb.append(ind(3) + "o.set" + otherSide().getImplAttributeName() + "(this);\n");
+      }
       sb.append(ind(2) + "}\n");
     } else {
-      sb.append(ind(2) + "o.set" + otherSide().getImplAttributeName() + "(this);\n");
+      if (isOpt) {
+        sb.append(ind(2) + "if (o != null) {\n");
+        sb.append(ind(3) + "o.set" + otherSide().getImplAttributeName() + "(this);\n");
+        sb.append(ind(2) + "}\n");
+      } else {
+        sb.append(ind(2) + "o.set" + otherSide().getImplAttributeName() + "(this);\n");
+      }
     }
+
     sb.append(ind(1) + "}\n");
 
     if (isOpt) {
@@ -610,7 +646,8 @@ aspect NameResolutionHelper {
     relation().getLeft().generateContextDependentNameResolution(sb);
   }
   public void Bidirectional.generateContextDependentNameResolution(StringBuilder sb) {
-    // TODO
+    relation().getLeft().generateContextDependentNameResolution(sb);
+    relation().getRight().generateContextDependentNameResolution(sb);
   }
 
   public abstract void RelationComponent.generateContextDependentNameResolution(StringBuilder sb);
@@ -618,10 +655,16 @@ aspect NameResolutionHelper {
     generateDirectedContextDependentNameResolution(sb);
   }
   public void OptionalRelationComponent.generateContextDependentNameResolution(StringBuilder sb) {
-    // TODO
+    // optional relations are resolved in the same way as mandatory relations
+    // TODO maybe, there should be a check if the id to be solved is empty or null
+    generateDirectedContextDependentNameResolution(sb);
   }
   public void ManyRelationComponent.generateContextDependentNameResolution(StringBuilder sb) {
-    // TODO
+    sb.append(ind(1) + "// context-dependent name resolution\n");
+    sb.append(ind(1) + "syn " + ofTypeDecl() + " " + toTypeDecl() + ".resolve" + nameCapitalized() + "(String id, int position) {\n");
+      sb.append(ind(2) + "// default to context-independent name resolution\n");
+      sb.append(ind(2) + "return resolve" + ofTypeDecl() + "(id);\n");
+    sb.append(ind(1) + "}\n");
   }
 
   public void RelationComponent.generateDirectedContextDependentNameResolution(StringBuilder sb) {
diff --git a/src/test/jastadd/resolver/MyRefResolver.jadd b/src/test/jastadd/resolver/MyRefResolver.jadd
index c152e17..a6114c6 100644
--- a/src/test/jastadd/resolver/MyRefResolver.jadd
+++ b/src/test/jastadd/resolver/MyRefResolver.jadd
@@ -6,5 +6,17 @@ aspect MyRewrites {
     return root().findNamedElement(id);
   }
 
+  // context-independent name resolution
+  refine RefResolverStubs eq ASTNode.resolveA(String id) {
+    System.out.println("resolving " + id + " to " + root().findNamedElement(id));
+    return root().findA(id);
+  }
+
+  // context-independent name resolution
+  refine RefResolverStubs eq ASTNode.resolveB(String id) {
+    System.out.println("resolving " + id + " to " + root().findNamedElement(id));
+    return root().findB(id);
+  }
+
 }
 
diff --git a/src/test/jastadd/resolver/Resolver.relast b/src/test/jastadd/resolver/Resolver.relast
index 51cfc61..5cbaecf 100644
--- a/src/test/jastadd/resolver/Resolver.relast
+++ b/src/test/jastadd/resolver/Resolver.relast
@@ -5,3 +5,19 @@ B:NamedElement;
 
 rel A.Rel1  -> NamedElement;
 rel A.Rel2  -> NamedElement;
+
+rel A.Di1  -> B;
+rel A.Di2? -> B;
+rel A.Di3* -> B;
+
+rel A.Bi1 <-> B.Bi1;
+rel A.Bi2 <-> B.Bi2?;
+rel A.Bi3 <-> B.Bi3*;
+
+rel A.Bi4? <-> B.Bi4;
+rel A.Bi5? <-> B.Bi5?;
+rel A.Bi6? <-> B.Bi6*;
+
+rel A.Bi7* <-> B.Bi7;
+rel A.Bi8* <-> B.Bi8?;
+rel A.Bi9* <-> B.Bi9*;
diff --git a/src/test/java/org/jastadd/relast/tests/ResolverHelper.java b/src/test/java/org/jastadd/relast/tests/ResolverHelper.java
index 3e3eeff..783c6d5 100644
--- a/src/test/java/org/jastadd/relast/tests/ResolverHelper.java
+++ b/src/test/java/org/jastadd/relast/tests/ResolverHelper.java
@@ -7,7 +7,10 @@ import resolver.ast.B;
 import resolver.ast.NamedElement;
 import resolver.ast.Root;
 
-import static org.junit.jupiter.api.Assertions.assertSame;
+import java.util.Arrays;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
 
 @SuppressWarnings("ArraysAsListWithZeroOrOneArgument")
 class ResolverHelper {
@@ -31,6 +34,723 @@ class ResolverHelper {
     assertSame(a1.getRel1(), b2);
   }
 
+  /**
+   * rel A.Di1 -> B;
+   */
+  @Test
+  void testDi1() {
+    setup();
+    a1.setDi1(B.createRef("b2"));
+    a2.setDi1(B.createRef("b1"));
+
+    assertSame(a1.getDi1(), b2);
+    assertSame(a2.getDi1(), b1);
+
+    a2.setDi1(b2);
+
+    assertSame(a1.getDi1(), b2);
+    assertSame(a2.getDi1(), b2);
+
+    try {
+      a3.setDi1(null);
+      fail("should throw an exception");
+    } catch (Exception e) {
+      // OK
+    }
+  }
+
+
+  /**
+   * rel A.Di2? -> B;
+   */
+  @Test
+  void testDi2() {
+    setup();
+    a1.setDi2(B.createRef("b2"));
+    a2.setDi2(B.createRef("b1"));
+
+    assertSame(a1.getDi2(), b2);
+    assertSame(a2.getDi2(), b1);
+
+    a2.setDi2(b2);
+
+    assertSame(a1.getDi2(), b2);
+    assertSame(a2.getDi2(), b2);
+
+    a2.clearDi2();
+
+    assertSame(a1.getDi2(), b2);
+    assertNull(a2.getDi2());
+
+    assertTrue(a1.hasDi2());
+    assertFalse(a2.hasDi2());
+    assertFalse(a3.hasDi2());
+  }
+
+
+  /**
+   * rel A.Di3* -> B;
+   */
+  @Test
+  void testDi3() {
+    setup();
+    a1.addDi3(B.createRef("b1"));
+    a1.addDi3(B.createRef("b2"));
+    a1.addDi3(B.createRef("b3"));
+    a2.addDi3(B.createRef("b2"));
+
+    assertEquals(a1.getDi3s(), Arrays.asList(b1, b2, b3));
+    assertEquals(a1.getDi3List(), Arrays.asList(b1, b2, b3));
+    assertEquals(a2.getDi3s(), Arrays.asList(b2));
+    assertEquals(a2.getDi3List(), Arrays.asList(b2));
+    assertEquals(a3.getDi3s(), Arrays.asList());
+    assertEquals(a3.getDi3List(), Arrays.asList());
+
+    a1.addDi3(B.createRef("b1"));
+    a2.addDi3(B.createRef("b1"));
+    a2.addDi3(B.createRef("b2"));
+
+    assertEquals(a1.getDi3s(), Arrays.asList(b1, b2, b3, b1));
+    assertEquals(a1.getDi3List(), Arrays.asList(b1, b2, b3, b1));
+    assertEquals(a2.getDi3s(), Arrays.asList(b2, b1, b2));
+    assertEquals(a2.getDi3List(), Arrays.asList(b2, b1, b2));
+    assertEquals(a3.getDi3s(), Arrays.asList());
+    assertEquals(a3.getDi3List(), Arrays.asList());
+
+    a1.removeDi3(b1);
+    a2.removeDi3(b2);
+
+    assertEquals(a1.getDi3s(), Arrays.asList(b2, b3, b1));
+    assertEquals(a1.getDi3List(), Arrays.asList(b2, b3, b1));
+    assertEquals(a2.getDi3s(), Arrays.asList(b1, b2));
+    assertEquals(a2.getDi3List(), Arrays.asList(b1, b2));
+    assertEquals(a3.getDi3s(), Arrays.asList());
+    assertEquals(a3.getDi3List(), Arrays.asList());
+  }
+
+
+  /**
+   * rel A.Bi1 <-> B.Bi1;
+   */
+
+  @Test
+  void testBi11() {
+    // Init
+    setup();
+    a1.setBi1(B.createRef("b1"));
+
+    // before a1.Bi1() is resolved, the opposite direction is null
+    assertNull(b1.getBi1());
+
+    // now, the relation is resolved, and thus the opposite direction is also set
+    assertSame(b1, a1.getBi1());
+    assertSame(a1, b1.getBi1());
+
+    // create another, unrelated relation, perform the same tests
+    a2.setBi1(B.createRef("b2"));
+    assertNull(b2.getBi1());
+    assertSame(b2, a2.getBi1());
+    assertSame(a2, b2.getBi1());
+
+    // change the relation of a2 to overwrite the existing one in a1<->b1
+    a2.setBi1(B.createRef("b1"));
+
+    // as long as no resolution took place, the old reference still exists
+    assertSame(b1, a1.getBi1());
+    assertSame(a1, b1.getBi1());
+
+    // now, we resolve a2->b1
+    assertSame(b1, a2.getBi1());
+
+    // now the final situation should be a2<->b1, a1->null, b2->null
+    assertSame(a2.getBi1(), b1);
+    assertSame(b1.getBi1(), a2);
+    assertNull(a1.getBi1());
+    assertNull(b2.getBi1());
+  }
+
+  @Test
+  void testBi12() {
+    // Init
+    setup();
+    a1.setBi1(B.createRef("b2"));
+
+    // Change
+    a2.setBi1(B.createRef("b2"));
+
+    // unresolved
+    assertNull(b2.getBi1());
+
+    assertSame(b2, a1.getBi1());
+    // a1 resolved
+    assertSame(a1, b2.getBi1());
+
+    // now resolve the change:
+    assertSame(b2, a2.getBi1());
+
+    // now finally, a1->null
+    assertNull(a1.getBi1());
+    assertSame(a2.getBi1(), b2);
+    assertNull(b1.getBi1());
+    assertSame(b2.getBi1(), a2);
+  }
+
+
+  /**
+   * rel A.Bi2 <-> B.Bi2?;
+   */
+
+  @Test
+  void testBi21() {
+    // Init
+    setup();
+    a1.setBi2(B.createRef("b1"));
+
+    // before a1.Bi2() is resolved, the opposite direction is null
+    assertNull(b1.getBi2());
+
+    // now, the relation is resolved, and thus the opposite direction is also set
+    assertSame(b1, a1.getBi2());
+    assertSame(a1, b1.getBi2());
+
+    // create another, unrelated relation, perform the same tests
+    a2.setBi2(B.createRef("b2"));
+    assertNull(b2.getBi2());
+    assertSame(b2, a2.getBi2());
+    assertSame(a2, b2.getBi2());
+
+    // change the relation of a2 to overwrite the existing one in a1<->b1
+    a2.setBi2(B.createRef("b1"));
+
+    // as long as no resolution took place, the old reference still exists
+    assertSame(b1, a1.getBi2());
+    assertSame(a1, b1.getBi2());
+
+    // now, we resolve a2->b1
+    assertSame(b1, a2.getBi2());
+
+    // now the final situation should be a2<->b1, a1->null, b2->null
+    assertSame(a2.getBi2(), b1);
+    assertSame(b1.getBi2(), a2);
+    assertNull(a1.getBi2());
+    assertNull(b2.getBi2());
+  }
+
+  @Test
+  void testBi22() {
+    // Init
+    setup();
+    a1.setBi2(B.createRef("b2"));
+
+    // Change
+    a2.setBi2(B.createRef("b2"));
+
+    // unresolved
+    assertNull(b2.getBi2());
+
+    assertSame(b2, a1.getBi2());
+    // a1 resolved
+    assertSame(a1, b2.getBi2());
+
+    // now resolve the change:
+    assertSame(b2, a2.getBi2());
+
+    // now finally, a1->null
+    assertNull(a1.getBi2());
+    assertSame(a2.getBi2(), b2);
+    assertNull(b1.getBi2());
+    assertSame(b2.getBi2(), a2);
+  }
+
+
+
+  /**
+   * rel A.Bi3 <-> B.Bi3*;
+   */
+  @Test
+  void testBi3() {
+    setup();
+    a2.setBi3(B.createRef("b2"));
+
+    assertNull(a1.getBi3());
+    assertSame(a2.getBi3(), b2);
+    assertEquals(b1.getBi3s(), Arrays.asList());
+    assertEquals(b1.getBi3List(), Arrays.asList());
+    assertEquals(b2.getBi3s(), Arrays.asList(a2));
+    assertEquals(b2.getBi3List(), Arrays.asList(a2));
+    assertEquals(b3.getBi3s(), Arrays.asList());
+    assertEquals(b3.getBi3List(), Arrays.asList());
+
+    a2.setBi3(B.createRef("b3"));
+
+    assertNull(a1.getBi3());
+    assertSame(a2.getBi3(), b3);
+    assertEquals(b1.getBi3s(), Arrays.asList());
+    assertEquals(b1.getBi3List(), Arrays.asList());
+    assertEquals(b2.getBi3s(), Arrays.asList());
+    assertEquals(b2.getBi3List(), Arrays.asList());
+    assertEquals(b3.getBi3s(), Arrays.asList(a2));
+    assertEquals(b3.getBi3List(), Arrays.asList(a2));
+
+    a1.setBi3(B.createRef("b3"));
+    a3.setBi3(B.createRef("b3"));
+
+    assertSame(a1.getBi3(), b3);
+    assertSame(a2.getBi3(), b3);
+    assertSame(a3.getBi3(), b3);
+    assertEquals(b1.getBi3s(), Arrays.asList());
+    assertEquals(b1.getBi3List(), Arrays.asList());
+    assertEquals(b2.getBi3s(), Arrays.asList());
+    assertEquals(b2.getBi3List(), Arrays.asList());
+    assertEquals(b3.getBi3s(), Arrays.asList(a2, a1, a3));
+    assertEquals(b3.getBi3List(), Arrays.asList(a2, a1, a3));
+
+    a2.setBi3(B.createRef("b1"));
+
+    assertSame(a1.getBi3(), b3);
+    assertSame(a2.getBi3(), b1);
+    assertSame(a3.getBi3(), b3);
+    assertEquals(b1.getBi3s(), Arrays.asList(a2));
+    assertEquals(b1.getBi3List(), Arrays.asList(a2));
+    assertEquals(b2.getBi3s(), Arrays.asList());
+    assertEquals(b2.getBi3List(), Arrays.asList());
+    assertEquals(b3.getBi3s(), Arrays.asList(a1, a3));
+    assertEquals(b3.getBi3List(), Arrays.asList(a1, a3));
+
+    try {
+      a2.setBi3(null);
+      fail("should throw an exception");
+    } catch (Exception e) {
+      // OK
+    }
+  }
+
+
+  /**
+   * rel A.Bi4? <-> B.Bi4;
+   */
+  @Test
+  void testBi41() {
+    // Init
+    setup();
+    a1.setBi4(B.createRef("b1"));
+
+    // before a1.Bi4() is resolved, the opposite direction is null
+    assertNull(b1.getBi4());
+
+    // now, the relation is resolved, and thus the opposite direction is also set
+    assertSame(b1, a1.getBi4());
+    assertSame(a1, b1.getBi4());
+
+    // create another, unrelated relation, perform the same tests
+    a2.setBi4(B.createRef("b2"));
+    assertNull(b2.getBi4());
+    assertSame(b2, a2.getBi4());
+    assertSame(a2, b2.getBi4());
+
+    // change the relation of a2 to overwrite the existing one in a1<->b1
+    a2.setBi4(B.createRef("b1"));
+
+    // as long as no resolution took place, the old reference still exists
+    assertSame(b1, a1.getBi4());
+    assertSame(a1, b1.getBi4());
+
+    // now, we resolve a2->b1
+    assertSame(b1, a2.getBi4());
+
+    // now the final situation should be a2<->b1, a1->null, b2->null
+    assertSame(a2.getBi4(), b1);
+    assertSame(b1.getBi4(), a2);
+    assertNull(a1.getBi4());
+    assertNull(b2.getBi4());
+  }
+
+  @Test
+  void testBi42() {
+    // Init
+    setup();
+    a1.setBi4(B.createRef("b2"));
+
+    // Change
+    a2.setBi4(B.createRef("b2"));
+
+    // unresolved
+    assertNull(b2.getBi4());
+
+    assertSame(b2, a1.getBi4());
+    // a1 resolved
+    assertSame(a1, b2.getBi4());
+
+    // now resolve the change:
+    assertSame(b2, a2.getBi4());
+
+    // now finally, a1->null
+    assertNull(a1.getBi4());
+    assertSame(a2.getBi4(), b2);
+    assertNull(b1.getBi4());
+    assertSame(b2.getBi4(), a2);
+  }
+
+
+  /**
+   * rel A.Bi5? <-> B.Bi5?;
+   */
+  @Test
+  void testBi51() {
+    // Init
+    setup();
+    a1.setBi5(B.createRef("b1"));
+
+    // before a1.Bi5() is resolved, the opposite direction is null
+    assertNull(b1.getBi5());
+
+    // now, the relation is resolved, and thus the opposite direction is also set
+    assertSame(b1, a1.getBi5());
+    assertSame(a1, b1.getBi5());
+
+    // create another, unrelated relation, perform the same tests
+    a2.setBi5(B.createRef("b2"));
+    assertNull(b2.getBi5());
+    assertSame(b2, a2.getBi5());
+    assertSame(a2, b2.getBi5());
+
+    // change the relation of a2 to overwrite the existing one in a1<->b1
+    a2.setBi5(B.createRef("b1"));
+
+    // as long as no resolution took place, the old reference still exists
+    assertSame(b1, a1.getBi5());
+    assertSame(a1, b1.getBi5());
+
+    // now, we resolve a2->b1
+    assertSame(b1, a2.getBi5());
+
+    // now the final situation should be a2<->b1, a1->null, b2->null
+    assertSame(a2.getBi5(), b1);
+    assertSame(b1.getBi5(), a2);
+    assertNull(a1.getBi5());
+    assertNull(b2.getBi5());
+  }
+
+  @Test
+  void testBi52() {
+    // Init
+    setup();
+    a1.setBi5(B.createRef("b2"));
+
+    // Change
+    a2.setBi5(B.createRef("b2"));
+
+    // unresolved
+    assertNull(b2.getBi5());
+
+    assertSame(b2, a1.getBi5());
+    // a1 resolved
+    assertSame(a1, b2.getBi5());
+
+    // now resolve the change:
+    assertSame(b2, a2.getBi5());
+
+    // now finally, a1->null
+    assertNull(a1.getBi5());
+    assertSame(a2.getBi5(), b2);
+    assertNull(b1.getBi5());
+    assertSame(b2.getBi5(), a2);
+  }
+
+
+  /**
+   * rel A.Bi6? <-> B.Bi6*;
+   */
+  @Test
+  void testBi6() {
+    setup();
+    a2.setBi6(B.createRef("b2"));
+
+    assertNull(a1.getBi6());
+    assertSame(a2.getBi6(), b2);
+    assertEquals(b1.getBi6s(), Arrays.asList());
+    assertEquals(b1.getBi6List(), Arrays.asList());
+    assertEquals(b2.getBi6s(), Arrays.asList(a2));
+    assertEquals(b2.getBi6List(), Arrays.asList(a2));
+    assertEquals(b3.getBi6s(), Arrays.asList());
+    assertEquals(b3.getBi6List(), Arrays.asList());
+
+    a2.setBi6(B.createRef("b3"));
+
+    assertNull(a1.getBi6());
+    assertSame(a2.getBi6(), b3);
+    assertEquals(b1.getBi6s(), Arrays.asList());
+    assertEquals(b1.getBi6List(), Arrays.asList());
+    assertEquals(b2.getBi6s(), Arrays.asList());
+    assertEquals(b2.getBi6List(), Arrays.asList());
+    assertEquals(b3.getBi6s(), Arrays.asList(a2));
+    assertEquals(b3.getBi6List(), Arrays.asList(a2));
+
+    a1.setBi6(B.createRef("b3"));
+    a3.setBi6(B.createRef("b3"));
+
+    assertSame(a1.getBi6(), b3);
+    assertSame(a2.getBi6(), b3);
+    assertSame(a3.getBi6(), b3);
+    assertEquals(b1.getBi6s(), Arrays.asList());
+    assertEquals(b1.getBi6List(), Arrays.asList());
+    assertEquals(b2.getBi6s(), Arrays.asList());
+    assertEquals(b2.getBi6List(), Arrays.asList());
+    assertEquals(b3.getBi6s(), Arrays.asList(a2, a1, a3));
+    assertEquals(b3.getBi6List(), Arrays.asList(a2, a1, a3));
+
+    a2.setBi6(B.createRef("b1"));
+
+    assertSame(a1.getBi6(), b3);
+    assertSame(a2.getBi6(), b1);
+    assertSame(a3.getBi6(), b3);
+    assertEquals(b1.getBi6s(), Arrays.asList(a2));
+    assertEquals(b1.getBi6List(), Arrays.asList(a2));
+    assertEquals(b2.getBi6s(), Arrays.asList());
+    assertEquals(b2.getBi6List(), Arrays.asList());
+    assertEquals(b3.getBi6s(), Arrays.asList(a1, a3));
+    assertEquals(b3.getBi6List(), Arrays.asList(a1, a3));
+
+    a2.clearBi6();
+
+    assertSame(a1.getBi6(), b3);
+    assertNull(a2.getBi6());
+    assertSame(a3.getBi6(), b3);
+    assertEquals(b1.getBi6s(), Arrays.asList());
+    assertEquals(b1.getBi6List(), Arrays.asList());
+    assertEquals(b2.getBi6s(), Arrays.asList());
+    assertEquals(b2.getBi6List(), Arrays.asList());
+    assertEquals(b3.getBi6s(), Arrays.asList(a1, a3));
+    assertEquals(b3.getBi6List(), Arrays.asList(a1, a3));
+
+    assertTrue(a1.hasBi6());
+    assertFalse(a2.hasBi6());
+    assertTrue(a3.hasBi6());
+  }
+
+
+  /**
+   * rel A.Bi7* <-> B.Bi7;
+   */
+  @Test
+  void testBi7() {
+    setup();
+    a2.addBi7(B.createRef("b2"));
+
+    // a1 list is empty
+    assertEquals(a1.getBi7s(), Arrays.asList());
+    assertEquals(a1.getBi7List(), Arrays.asList());
+
+    // a2 list contains b2 (because resolution took place)
+    assertEquals(a2.getBi7s(), Arrays.asList(b2));
+    assertEquals(a2.getBi7List(), Arrays.asList(b2));
+
+    // of all the bs, only b2 contains a back ref to a2
+    assertNull(b1.getBi7());
+    assertSame(b2.getBi7(), a2);
+    assertNull(b3.getBi7());
+
+    a2.addBi7(B.createRef("b3"));
+    a1.addBi7(B.createRef("b2"));
+
+    assertEquals(a1.getBi7s(), Arrays.asList(b2));
+    assertEquals(a1.getBi7List(), Arrays.asList(b2));
+    assertEquals(a2.getBi7s(), Arrays.asList(b3));
+    assertEquals(a2.getBi7List(), Arrays.asList(b3));
+    assertNull(b1.getBi7());
+    assertSame(b2.getBi7(), a1);
+    assertSame(b3.getBi7(), a2);
+
+    a1.addBi7(B.createRef("b1"));
+
+    assertEquals(a1.getBi7s(), Arrays.asList(b2, b1));
+    assertEquals(a1.getBi7List(), Arrays.asList(b2, b1));
+    assertEquals(a2.getBi7s(), Arrays.asList(b3));
+    assertEquals(a2.getBi7List(), Arrays.asList(b3));
+    assertSame(b1.getBi7(), a1);
+    assertSame(b2.getBi7(), a1);
+    assertSame(b3.getBi7(), a2);
+
+    a1.addBi7(B.createRef("b1"));
+
+    assertEquals(a1.getBi7s(), Arrays.asList(b2, b1));
+    assertEquals(a1.getBi7List(), Arrays.asList(b2, b1));
+    assertEquals(a2.getBi7s(), Arrays.asList(b3));
+    assertEquals(a2.getBi7List(), Arrays.asList(b3));
+    assertSame(b1.getBi7(), a1);
+    assertSame(b2.getBi7(), a1);
+    assertSame(b3.getBi7(), a2);
+
+    a1.removeBi7(b1);
+
+    assertEquals(a1.getBi7s(), Arrays.asList(b2));
+    assertEquals(a1.getBi7List(), Arrays.asList(b2));
+    assertEquals(a2.getBi7s(), Arrays.asList(b3));
+    assertEquals(a2.getBi7List(), Arrays.asList(b3));
+    assertNull(b1.getBi7());
+    assertSame(b2.getBi7(), a1);
+    assertSame(b3.getBi7(), a2);
+  }
+
+
+  /**
+   * rel A.Bi8* <-> B.Bi8?;
+   */
+  @Test
+  void testBi8() {
+    setup();
+    a2.addBi8(B.createRef("b2"));
+
+    assertEquals(a1.getBi8s(), Arrays.asList());
+    assertEquals(a1.getBi8List(), Arrays.asList());
+    assertEquals(a2.getBi8s(), Arrays.asList(b2));
+    assertEquals(a2.getBi8List(), Arrays.asList(b2));
+    assertNull(b1.getBi8());
+    assertSame(b2.getBi8(), a2);
+    assertNull(b3.getBi8());
+
+    a2.addBi8(B.createRef("b3"));
+    a1.addBi8(B.createRef("b2"));
+
+    assertEquals(a1.getBi8s(), Arrays.asList(b2));
+    assertEquals(a1.getBi8List(), Arrays.asList(b2));
+    assertEquals(a2.getBi8s(), Arrays.asList(b3));
+    assertEquals(a2.getBi8List(), Arrays.asList(b3));
+    assertNull(b1.getBi8());
+    assertSame(b2.getBi8(), a1);
+    assertSame(b3.getBi8(), a2);
+
+    a1.addBi8(B.createRef("b1"));
+
+    assertEquals(a1.getBi8s(), Arrays.asList(b2, b1));
+    assertEquals(a1.getBi8List(), Arrays.asList(b2, b1));
+    assertEquals(a2.getBi8s(), Arrays.asList(b3));
+    assertEquals(a2.getBi8List(), Arrays.asList(b3));
+    assertSame(b1.getBi8(), a1);
+    assertSame(b2.getBi8(), a1);
+    assertSame(b3.getBi8(), a2);
+
+    a1.addBi8(B.createRef("b1"));
+
+    assertEquals(a1.getBi8s(), Arrays.asList(b2, b1));
+    assertEquals(a1.getBi8List(), Arrays.asList(b2, b1));
+    assertEquals(a2.getBi8s(), Arrays.asList(b3));
+    assertEquals(a2.getBi8List(), Arrays.asList(b3));
+    assertSame(b1.getBi8(), a1);
+    assertSame(b2.getBi8(), a1);
+    assertSame(b3.getBi8(), a2);
+
+    a1.removeBi8(b1);
+
+    assertEquals(a1.getBi8s(), Arrays.asList(b2));
+    assertEquals(a1.getBi8List(), Arrays.asList(b2));
+    assertEquals(a2.getBi8s(), Arrays.asList(b3));
+    assertEquals(a2.getBi8List(), Arrays.asList(b3));
+    assertNull(b1.getBi8());
+    assertSame(b2.getBi8(), a1);
+    assertSame(b3.getBi8(), a2);
+  }
+
+
+  /**
+   * rel A.Bi9* <-> B.Bi9*;
+   */
+  @Test
+  void testBi9() {
+    setup();
+    a1.addBi9(B.createRef("b1"));
+    a1.addBi9(B.createRef("b2"));
+
+    assertEquals(a1.getBi9s(), Arrays.asList(b1, b2));
+    assertEquals(a1.getBi9List(), Arrays.asList(b1, b2));
+    assertEquals(a2.getBi9s(), Arrays.asList());
+    assertEquals(a2.getBi9List(), Arrays.asList());
+    assertEquals(a3.getBi9s(), Arrays.asList());
+    assertEquals(a3.getBi9List(), Arrays.asList());
+    assertEquals(b1.getBi9s(), Arrays.asList(a1));
+    assertEquals(b1.getBi9List(), Arrays.asList(a1));
+    assertEquals(b2.getBi9s(), Arrays.asList(a1));
+    assertEquals(b2.getBi9List(), Arrays.asList(a1));
+    assertEquals(b3.getBi9s(), Arrays.asList());
+    assertEquals(b3.getBi9List(), Arrays.asList());
+
+    b3.addBi9(A.createRef("a1"));
+    b3.addBi9(A.createRef("a3"));
+    b3.addBi9(A.createRef("a1"));
+
+    // b3 is not resolved yet, so the as look like before
+    assertEquals(a1.getBi9s(), Arrays.asList(b1, b2));
+    assertEquals(a1.getBi9List(), Arrays.asList(b1, b2));
+    assertEquals(a2.getBi9s(), Arrays.asList());
+    assertEquals(a2.getBi9List(), Arrays.asList());
+    assertEquals(a3.getBi9s(), Arrays.asList());
+    assertEquals(a3.getBi9List(), Arrays.asList());
+
+    // let's resolve b3
+    assertEquals(b1.getBi9s(), Arrays.asList(a1));
+    assertEquals(b1.getBi9List(), Arrays.asList(a1));
+    assertEquals(b2.getBi9s(), Arrays.asList(a1));
+    assertEquals(b2.getBi9List(), Arrays.asList(a1));
+    assertEquals(b3.getBi9s(), Arrays.asList(a1, a3, a1));
+    assertEquals(b3.getBi9List(), Arrays.asList(a1, a3, a1));
+
+    // now the as are updated
+    assertEquals(a1.getBi9s(), Arrays.asList(b1, b2, b3, b3));
+    assertEquals(a1.getBi9List(), Arrays.asList(b1, b2, b3, b3));
+    assertEquals(a2.getBi9s(), Arrays.asList());
+    assertEquals(a2.getBi9List(), Arrays.asList());
+    assertEquals(a3.getBi9s(), Arrays.asList(b3));
+    assertEquals(a3.getBi9List(), Arrays.asList(b3));
+
+    b3.removeBi9(a1);
+
+    assertEquals(a1.getBi9s(), Arrays.asList(b1, b2, b3));
+    assertEquals(a1.getBi9List(), Arrays.asList(b1, b2, b3));
+    assertEquals(a2.getBi9s(), Arrays.asList());
+    assertEquals(a2.getBi9List(), Arrays.asList());
+    assertEquals(a3.getBi9s(), Arrays.asList(b3));
+    assertEquals(a3.getBi9List(), Arrays.asList(b3));
+    assertEquals(b1.getBi9s(), Arrays.asList(a1));
+    assertEquals(b1.getBi9List(), Arrays.asList(a1));
+    assertEquals(b2.getBi9s(), Arrays.asList(a1));
+    assertEquals(b2.getBi9List(), Arrays.asList(a1));
+    assertEquals(b3.getBi9s(), Arrays.asList(a3, a1));
+    assertEquals(b3.getBi9List(), Arrays.asList(a3, a1));
+  }
+
+
+  @Test
+  void testImmutableList() {
+    setup();
+
+    a1.addDi3(B.createRef("b1"));
+    a1.addDi3(B.createRef("b2"));
+    try {
+      a1.getDi3s().add(b3);
+      fail("should throw an exception");
+    } catch (Exception e) {
+      // OK
+    }
+
+    a1.addBi7(B.createRef("b1"));
+    a1.addBi7(B.createRef("b2"));
+    try {
+      a1.getBi7s().add(B.createRef("b3"));
+      fail("should throw an exception");
+    } catch (Exception e) {
+      // OK
+    }
+
+    a1.addBi9(B.createRef("b1"));
+    a1.addBi9(B.createRef("b2"));
+    try {
+      a1.getBi9s().add(B.createRef("b3"));
+      fail("should throw an exception");
+    } catch (Exception e) {
+      // OK
+    }
+  }
+
   @BeforeEach
   void setup() {
     r = new Root();
-- 
GitLab