diff --git a/.gitignore b/.gitignore
index 0e0b7169249e659f71fd6741bfa3f5804ac72341..a5b7d04336a4a3dd2c22ab2e8f7c2522d73f20c3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,4 @@
-*.jar
+*.jarsrc/test/java/org/jastadd/relast/tests/Resolver2.java
 .idea/
 .gradle/
 build
@@ -15,5 +15,8 @@ src/test/jastadd/multiple/Multiple.jadd
 src/test/jastadd/resolver/Resolver.ast
 src/test/jastadd/resolver/Resolver.jadd
 src/test/jastadd/resolver/ResolverRefResolver.jadd
+src/test/jastadd/resolver2/Resolver.ast
+src/test/jastadd/resolver2/Resolver.jadd
+src/test/jastadd/resolver2/ResolverRefResolver.jadd
 src/test/jastadd/listnames/ListNames.ast
 src/test/jastadd/listnames/ListNames.jadd
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index fdf2acc274d909b8a9436cf2ffbf72e1b253f379..0383ec1426d52eb248d733b7fd55a1d748f8a07b 100644
--- a/build.gradle
+++ b/build.gradle
@@ -201,6 +201,29 @@ task compileResolverTest(type: JavaExec, group: 'verification') {
     args '--o=src/test/java-gen/', '--package=resolver.ast', 'src/test/jastadd/resolver/Resolver.ast', 'src/test/jastadd/resolver/Resolver.jadd', 'src/test/jastadd/resolver/ResolverUtils.jadd', 'src/test/jastadd/resolver/ResolverRefResolver.jadd', 'src/test/jastadd/resolver/MyRefResolver.jadd', 'src/test/jastadd/Utils.jadd'
 }
 
+
+task preprocessResolver2Test(type: JavaExec, group: 'verification') {
+
+    doFirst {
+        delete 'src/test/jastadd/resolver2/Resolver.ast', 'src/test/jastadd/resolver2/Resolver.jadd', 'src/test/jastadd/resolver2/ResolverRefResolver.jadd'
+    }
+
+    classpath = sourceSets.main.runtimeClasspath
+    main = 'org.jastadd.relast.compiler.Compiler'
+    args 'src/test/jastadd/resolver2/Resolver.relast', '--file', '--grammarName=src/test/jastadd/resolver2/Resolver', '--resolverHelper'
+}
+
+task compileResolver2Test(type: JavaExec, group: 'verification') {
+
+    doFirst {
+        delete 'src/test/java-gen/resolver2'
+    }
+
+    classpath = sourceSets.main.runtimeClasspath
+    main = 'org.jastadd.JastAdd'
+    args '--o=src/test/java-gen/', '--package=resolver2.ast', 'src/test/jastadd/resolver2/Resolver.ast', 'src/test/jastadd/resolver2/Resolver.jadd', 'src/test/jastadd/resolver2/ResolverUtils.jadd', 'src/test/jastadd/resolver2/ResolverRefResolver.jadd', 'src/test/jastadd/resolver2/MyRefResolver.jadd', 'src/test/jastadd/Utils.jadd'
+}
+
 task preprocessListNamesTest(type: JavaExec, group: 'verification') {
 
     doFirst {
@@ -243,5 +266,8 @@ compileMultipleTest.dependsOn preprocessMultipleTest
 test.dependsOn compileResolverTest
 compileResolverTest.dependsOn preprocessResolverTest
 
+test.dependsOn compileResolver2Test
+compileResolver2Test.dependsOn preprocessResolver2Test
+
 test.dependsOn compileListNamesTest
 compileListNamesTest.dependsOn preprocessListNamesTest
\ No newline at end of file
diff --git a/src/test/jastadd/resolver2/MyRefResolver.jadd b/src/test/jastadd/resolver2/MyRefResolver.jadd
new file mode 100644
index 0000000000000000000000000000000000000000..fa585c919296a0ea027cd06caf679cb9a5b62a00
--- /dev/null
+++ b/src/test/jastadd/resolver2/MyRefResolver.jadd
@@ -0,0 +1,22 @@
+aspect MyRewrites {
+
+  // context-independent name resolution
+  refine RefResolverStubs eq ASTNode.globallyResolveNamedElementByToken(String id) {
+    System.out.println("resolving " + id + " to " + root().findNamedElement(id));
+    return root().findNamedElement(id);
+  }
+
+  // context-independent name resolution
+  refine RefResolverStubs eq ASTNode.globallyResolveAByToken(String id) {
+    System.out.println("resolving " + id + " to " + root().findNamedElement(id));
+    return root().findA(id);
+  }
+
+  // context-independent name resolution
+  refine RefResolverStubs eq ASTNode.globallyResolveBByToken(String id) {
+    System.out.println("resolving " + id + " to " + root().findNamedElement(id));
+    return root().findB(id);
+  }
+
+}
+
diff --git a/src/test/jastadd/resolver2/Resolver.relast b/src/test/jastadd/resolver2/Resolver.relast
new file mode 100644
index 0000000000000000000000000000000000000000..93a52019c56ee00c4a7af797a3d806f9d80da4ee
--- /dev/null
+++ b/src/test/jastadd/resolver2/Resolver.relast
@@ -0,0 +1,23 @@
+Root ::= A* B*;
+abstract NamedElement ::= <Name:String>;
+A:NamedElement;
+B:NamedElement;
+
+rel A.Rel1  -> NamedElement;
+rel A.Rel2  -> NamedElement;
+
+rel A.Di1  -> B;
+rel A.Di2? -> B;
+rel A.Di3* -> B;
+
+rel A.Bi1l <-> B.Bi1;
+rel A.Bi2l <-> B.Bi2?;
+rel A.Bi3l <-> B.Bi3*;
+
+rel A.Bi4l? <-> B.Bi4;
+rel A.Bi5l? <-> B.Bi5?;
+rel A.Bi6l? <-> B.Bi6*;
+
+rel A.Bi7l* <-> B.Bi7;
+rel A.Bi8l* <-> B.Bi8?;
+rel A.Bi9l* <-> B.Bi9*;
diff --git a/src/test/jastadd/resolver2/ResolverUtils.jadd b/src/test/jastadd/resolver2/ResolverUtils.jadd
new file mode 100644
index 0000000000000000000000000000000000000000..a8d547321c3231220cd02c3f94494c85afd0786f
--- /dev/null
+++ b/src/test/jastadd/resolver2/ResolverUtils.jadd
@@ -0,0 +1,38 @@
+aspect Utils {
+
+  inh Root ASTNode.root();
+  eq Root.getA(int i).root() = this;
+  eq Root.getB(int i).root() = this;
+
+  syn NamedElement Root.findNamedElement(String name) {
+    for (A a : getAList()) {
+      if (a.getName().equals(name)) {
+        return a;
+      }
+    }
+    for (B b : getBList()) {
+      if (b.getName().equals(name)) {
+        return b;
+      }
+    }
+    return null;
+  }
+
+  syn A Root.findA(String name) {
+    for (A a : getAList()) {
+      if (a.getName().equals(name)) {
+        return a;
+      }
+    }
+    return null;
+  }
+
+  syn B Root.findB(String name) {
+    for (B b : getBList()) {
+      if (b.getName().equals(name)) {
+        return b;
+      }
+    }
+    return null;
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/org/jastadd/relast/tests/Resolver2.java b/src/test/java/org/jastadd/relast/tests/Resolver2.java
new file mode 100644
index 0000000000000000000000000000000000000000..c653c3c04627124fac2a9563e84cb6977103477c
--- /dev/null
+++ b/src/test/java/org/jastadd/relast/tests/Resolver2.java
@@ -0,0 +1,771 @@
+package org.jastadd.relast.tests;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import resolver2.ast.A;
+import resolver2.ast.B;
+import resolver2.ast.NamedElement;
+import resolver2.ast.Root;
+
+import java.util.Arrays;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+@SuppressWarnings("ArraysAsListWithZeroOrOneArgument")
+class Resolver2 {
+  private Root r;
+  private A a1;
+  private A a2;
+  private A a3;
+  private B b1;
+  private B b2;
+  private B b3;
+
+
+  /**
+   * rel A.Di1 -> B;
+   */
+  @Test
+  void testNameRes1() {
+    setup();
+    a1.setRel1(NamedElement.createRef("b2"));
+    System.out.println("Rel 1 of a1 has type " + a1.getRel1().getClass().getSimpleName());
+    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.setBi1l(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.getBi1l());
+    assertSame(a1, b1.getBi1());
+
+    // create another, unrelated relation, perform the same tests
+    a2.setBi1l(B.createRef("b2"));
+    assertNull(b2.getBi1());
+    assertSame(b2, a2.getBi1l());
+    assertSame(a2, b2.getBi1());
+
+    // change the relation of a2 to overwrite the existing one in a1<->b1
+    a2.setBi1l(B.createRef("b1"));
+
+    // as long as no resolution took place, the old reference still exists
+    assertSame(b1, a1.getBi1l());
+    assertSame(a1, b1.getBi1());
+
+    // now, we resolve a2->b1
+    assertSame(b1, a2.getBi1l());
+
+    // now the final situation should be a2<->b1, a1->null, b2->null
+    assertSame(a2.getBi1l(), b1);
+    assertSame(b1.getBi1(), a2);
+    assertNull(a1.getBi1l());
+    assertNull(b2.getBi1());
+  }
+
+  @Test
+  void testBi12() {
+    // Init
+    setup();
+    a1.setBi1l(B.createRef("b2"));
+
+    // Change
+    a2.setBi1l(B.createRef("b2"));
+
+    // unresolved
+    assertNull(b2.getBi1());
+
+    assertSame(b2, a1.getBi1l());
+    // a1 resolved
+    assertSame(a1, b2.getBi1());
+
+    // now resolve the change:
+    assertSame(b2, a2.getBi1l());
+
+    // now finally, a1->null
+    assertNull(a1.getBi1l());
+    assertSame(a2.getBi1l(), b2);
+    assertNull(b1.getBi1());
+    assertSame(b2.getBi1(), a2);
+  }
+
+
+  /**
+   * rel A.Bi2 <-> B.Bi2?;
+   */
+
+  @Test
+  void testBi21() {
+    // Init
+    setup();
+    a1.setBi2l(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.getBi2l());
+    assertSame(a1, b1.getBi2());
+
+    // create another, unrelated relation, perform the same tests
+    a2.setBi2l(B.createRef("b2"));
+    assertNull(b2.getBi2());
+    assertSame(b2, a2.getBi2l());
+    assertSame(a2, b2.getBi2());
+
+    // change the relation of a2 to overwrite the existing one in a1<->b1
+    a2.setBi2l(B.createRef("b1"));
+
+    // as long as no resolution took place, the old reference still exists
+    assertSame(b1, a1.getBi2l());
+    assertSame(a1, b1.getBi2());
+
+    // now, we resolve a2->b1
+    assertSame(b1, a2.getBi2l());
+
+    // now the final situation should be a2<->b1, a1->null, b2->null
+    assertSame(a2.getBi2l(), b1);
+    assertSame(b1.getBi2(), a2);
+    assertNull(a1.getBi2l());
+    assertNull(b2.getBi2());
+  }
+
+  @Test
+  void testBi22() {
+    // Init
+    setup();
+    a1.setBi2l(B.createRef("b2"));
+
+    // Change
+    a2.setBi2l(B.createRef("b2"));
+
+    // unresolved
+    assertNull(b2.getBi2());
+
+    assertSame(b2, a1.getBi2l());
+    // a1 resolved
+    assertSame(a1, b2.getBi2());
+
+    // now resolve the change:
+    assertSame(b2, a2.getBi2l());
+
+    // now finally, a1->null
+    assertNull(a1.getBi2l());
+    assertSame(a2.getBi2l(), b2);
+    assertNull(b1.getBi2());
+    assertSame(b2.getBi2(), a2);
+  }
+
+
+
+  /**
+   * rel A.Bi3 <-> B.Bi3*;
+   */
+  @Test
+  void testBi3() {
+    setup();
+    a2.setBi3l(B.createRef("b2"));
+
+    assertNull(a1.getBi3l());
+    assertSame(a2.getBi3l(), 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.setBi3l(B.createRef("b3"));
+
+    assertNull(a1.getBi3l());
+    assertSame(a2.getBi3l(), 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.setBi3l(B.createRef("b3"));
+    a3.setBi3l(B.createRef("b3"));
+
+    assertSame(a1.getBi3l(), b3);
+    assertSame(a2.getBi3l(), b3);
+    assertSame(a3.getBi3l(), 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.setBi3l(B.createRef("b1"));
+
+    assertSame(a1.getBi3l(), b3);
+    assertSame(a2.getBi3l(), b1);
+    assertSame(a3.getBi3l(), 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.setBi3l(null);
+      fail("should throw an exception");
+    } catch (Exception e) {
+      // OK
+    }
+  }
+
+
+  /**
+   * rel A.Bi4? <-> B.Bi4;
+   */
+  @Test
+  void testBi41() {
+    // Init
+    setup();
+    a1.setBi4l(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.getBi4l());
+    assertSame(a1, b1.getBi4());
+
+    // create another, unrelated relation, perform the same tests
+    a2.setBi4l(B.createRef("b2"));
+    assertNull(b2.getBi4());
+    assertSame(b2, a2.getBi4l());
+    assertSame(a2, b2.getBi4());
+
+    // change the relation of a2 to overwrite the existing one in a1<->b1
+    a2.setBi4l(B.createRef("b1"));
+
+    // as long as no resolution took place, the old reference still exists
+    assertSame(b1, a1.getBi4l());
+    assertSame(a1, b1.getBi4());
+
+    // now, we resolve a2->b1
+    assertSame(b1, a2.getBi4l());
+
+    // now the final situation should be a2<->b1, a1->null, b2->null
+    assertSame(a2.getBi4l(), b1);
+    assertSame(b1.getBi4(), a2);
+    assertNull(a1.getBi4l());
+    assertNull(b2.getBi4());
+  }
+
+  @Test
+  void testBi42() {
+    // Init
+    setup();
+    a1.setBi4l(B.createRef("b2"));
+
+    // Change
+    a2.setBi4l(B.createRef("b2"));
+
+    // unresolved
+    assertNull(b2.getBi4());
+
+    assertSame(b2, a1.getBi4l());
+    // a1 resolved
+    assertSame(a1, b2.getBi4());
+
+    // now resolve the change:
+    assertSame(b2, a2.getBi4l());
+
+    // now finally, a1->null
+    assertNull(a1.getBi4l());
+    assertSame(a2.getBi4l(), b2);
+    assertNull(b1.getBi4());
+    assertSame(b2.getBi4(), a2);
+  }
+
+
+  /**
+   * rel A.Bi5? <-> B.Bi5?;
+   */
+  @Test
+  void testBi51() {
+    // Init
+    setup();
+    a1.setBi5l(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.getBi5l());
+    assertSame(a1, b1.getBi5());
+
+    // create another, unrelated relation, perform the same tests
+    a2.setBi5l(B.createRef("b2"));
+    assertNull(b2.getBi5());
+    assertSame(b2, a2.getBi5l());
+    assertSame(a2, b2.getBi5());
+
+    // change the relation of a2 to overwrite the existing one in a1<->b1
+    a2.setBi5l(B.createRef("b1"));
+
+    // as long as no resolution took place, the old reference still exists
+    assertSame(b1, a1.getBi5l());
+    assertSame(a1, b1.getBi5());
+
+    // now, we resolve a2->b1
+    assertSame(b1, a2.getBi5l());
+
+    // now the final situation should be a2<->b1, a1->null, b2->null
+    assertSame(a2.getBi5l(), b1);
+    assertSame(b1.getBi5(), a2);
+    assertNull(a1.getBi5l());
+    assertNull(b2.getBi5());
+  }
+
+  @Test
+  void testBi52() {
+    // Init
+    setup();
+    a1.setBi5l(B.createRef("b2"));
+
+    // Change
+    a2.setBi5l(B.createRef("b2"));
+
+    // unresolved
+    assertNull(b2.getBi5());
+
+    assertSame(b2, a1.getBi5l());
+    // a1 resolved
+    assertSame(a1, b2.getBi5());
+
+    // now resolve the change:
+    assertSame(b2, a2.getBi5l());
+
+    // now finally, a1->null
+    assertNull(a1.getBi5l());
+    assertSame(a2.getBi5l(), b2);
+    assertNull(b1.getBi5());
+    assertSame(b2.getBi5(), a2);
+  }
+
+
+  /**
+   * rel A.Bi6? <-> B.Bi6*;
+   */
+  @Test
+  void testBi6() {
+    setup();
+    a2.setBi6l(B.createRef("b2"));
+
+    assertNull(a1.getBi6l());
+    assertSame(a2.getBi6l(), 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.setBi6l(B.createRef("b3"));
+
+    assertNull(a1.getBi6l());
+    assertSame(a2.getBi6l(), 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.setBi6l(B.createRef("b3"));
+    a3.setBi6l(B.createRef("b3"));
+
+    assertSame(a1.getBi6l(), b3);
+    assertSame(a2.getBi6l(), b3);
+    assertSame(a3.getBi6l(), 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.setBi6l(B.createRef("b1"));
+
+    assertSame(a1.getBi6l(), b3);
+    assertSame(a2.getBi6l(), b1);
+    assertSame(a3.getBi6l(), 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.clearBi6l();
+
+    assertSame(a1.getBi6l(), b3);
+    assertNull(a2.getBi6l());
+    assertSame(a3.getBi6l(), 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.hasBi6l());
+    assertFalse(a2.hasBi6l());
+    assertTrue(a3.hasBi6l());
+  }
+
+
+  /**
+   * rel A.Bi7* <-> B.Bi7;
+   */
+  @Test
+  void testBi7() {
+    setup();
+    a2.addBi7l(B.createRef("b2"));
+
+    // a1 list is empty
+    assertEquals(a1.getBi7ls(), Arrays.asList());
+    assertEquals(a1.getBi7lList(), Arrays.asList());
+
+    // a2 list contains b2 (because resolution took place)
+    assertEquals(a2.getBi7ls(), Arrays.asList(b2));
+    assertEquals(a2.getBi7lList(), 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.addBi7l(B.createRef("b3"));
+    a1.addBi7l(B.createRef("b2"));
+
+    assertEquals(a1.getBi7ls(), Arrays.asList(b2));
+    assertEquals(a1.getBi7lList(), Arrays.asList(b2));
+    assertEquals(a2.getBi7ls(), Arrays.asList(b3));
+    assertEquals(a2.getBi7lList(), Arrays.asList(b3));
+    assertNull(b1.getBi7());
+    assertSame(b2.getBi7(), a1);
+    assertSame(b3.getBi7(), a2);
+
+    a1.addBi7l(B.createRef("b1"));
+
+    assertEquals(a1.getBi7ls(), Arrays.asList(b2, b1));
+    assertEquals(a1.getBi7lList(), Arrays.asList(b2, b1));
+    assertEquals(a2.getBi7ls(), Arrays.asList(b3));
+    assertEquals(a2.getBi7lList(), Arrays.asList(b3));
+    assertSame(b1.getBi7(), a1);
+    assertSame(b2.getBi7(), a1);
+    assertSame(b3.getBi7(), a2);
+
+    a1.addBi7l(B.createRef("b1"));
+
+    assertEquals(a1.getBi7ls(), Arrays.asList(b2, b1));
+    assertEquals(a1.getBi7lList(), Arrays.asList(b2, b1));
+    assertEquals(a2.getBi7ls(), Arrays.asList(b3));
+    assertEquals(a2.getBi7lList(), Arrays.asList(b3));
+    assertSame(b1.getBi7(), a1);
+    assertSame(b2.getBi7(), a1);
+    assertSame(b3.getBi7(), a2);
+
+    a1.removeBi7l(b1);
+
+    assertEquals(a1.getBi7ls(), Arrays.asList(b2));
+    assertEquals(a1.getBi7lList(), Arrays.asList(b2));
+    assertEquals(a2.getBi7ls(), Arrays.asList(b3));
+    assertEquals(a2.getBi7lList(), Arrays.asList(b3));
+    assertNull(b1.getBi7());
+    assertSame(b2.getBi7(), a1);
+    assertSame(b3.getBi7(), a2);
+  }
+
+
+  /**
+   * rel A.Bi8* <-> B.Bi8?;
+   */
+  @Test
+  void testBi8() {
+    setup();
+    a2.addBi8l(B.createRef("b2"));
+
+    assertEquals(a1.getBi8ls(), Arrays.asList());
+    assertEquals(a1.getBi8lList(), Arrays.asList());
+    assertEquals(a2.getBi8ls(), Arrays.asList(b2));
+    assertEquals(a2.getBi8lList(), Arrays.asList(b2));
+    assertNull(b1.getBi8());
+    assertSame(b2.getBi8(), a2);
+    assertNull(b3.getBi8());
+
+    a2.addBi8l(B.createRef("b3"));
+    a1.addBi8l(B.createRef("b2"));
+
+    assertEquals(a1.getBi8ls(), Arrays.asList(b2));
+    assertEquals(a1.getBi8lList(), Arrays.asList(b2));
+    assertEquals(a2.getBi8ls(), Arrays.asList(b3));
+    assertEquals(a2.getBi8lList(), Arrays.asList(b3));
+    assertNull(b1.getBi8());
+    assertSame(b2.getBi8(), a1);
+    assertSame(b3.getBi8(), a2);
+
+    a1.addBi8l(B.createRef("b1"));
+
+    assertEquals(a1.getBi8ls(), Arrays.asList(b2, b1));
+    assertEquals(a1.getBi8lList(), Arrays.asList(b2, b1));
+    assertEquals(a2.getBi8ls(), Arrays.asList(b3));
+    assertEquals(a2.getBi8lList(), Arrays.asList(b3));
+    assertSame(b1.getBi8(), a1);
+    assertSame(b2.getBi8(), a1);
+    assertSame(b3.getBi8(), a2);
+
+    a1.addBi8l(B.createRef("b1"));
+
+    assertEquals(a1.getBi8ls(), Arrays.asList(b2, b1));
+    assertEquals(a1.getBi8lList(), Arrays.asList(b2, b1));
+    assertEquals(a2.getBi8ls(), Arrays.asList(b3));
+    assertEquals(a2.getBi8lList(), Arrays.asList(b3));
+    assertSame(b1.getBi8(), a1);
+    assertSame(b2.getBi8(), a1);
+    assertSame(b3.getBi8(), a2);
+
+    a1.removeBi8l(b1);
+
+    assertEquals(a1.getBi8ls(), Arrays.asList(b2));
+    assertEquals(a1.getBi8lList(), Arrays.asList(b2));
+    assertEquals(a2.getBi8ls(), Arrays.asList(b3));
+    assertEquals(a2.getBi8lList(), Arrays.asList(b3));
+    assertNull(b1.getBi8());
+    assertSame(b2.getBi8(), a1);
+    assertSame(b3.getBi8(), a2);
+  }
+
+
+  /**
+   * rel A.Bi9* <-> B.Bi9*;
+   */
+  @Test
+  void testBi9() {
+    setup();
+    a1.addBi9l(B.createRef("b1"));
+    a1.addBi9l(B.createRef("b2"));
+
+    assertEquals(a1.getBi9ls(), Arrays.asList(b1, b2));
+    assertEquals(a1.getBi9lList(), Arrays.asList(b1, b2));
+    assertEquals(a2.getBi9ls(), Arrays.asList());
+    assertEquals(a2.getBi9lList(), Arrays.asList());
+    assertEquals(a3.getBi9ls(), Arrays.asList());
+    assertEquals(a3.getBi9lList(), 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.getBi9ls(), Arrays.asList(b1, b2));
+    assertEquals(a1.getBi9lList(), Arrays.asList(b1, b2));
+    assertEquals(a2.getBi9ls(), Arrays.asList());
+    assertEquals(a2.getBi9lList(), Arrays.asList());
+    assertEquals(a3.getBi9ls(), Arrays.asList());
+    assertEquals(a3.getBi9lList(), 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.getBi9ls(), Arrays.asList(b1, b2, b3, b3));
+    assertEquals(a1.getBi9lList(), Arrays.asList(b1, b2, b3, b3));
+    assertEquals(a2.getBi9ls(), Arrays.asList());
+    assertEquals(a2.getBi9lList(), Arrays.asList());
+    assertEquals(a3.getBi9ls(), Arrays.asList(b3));
+    assertEquals(a3.getBi9lList(), Arrays.asList(b3));
+
+    b3.removeBi9(a1);
+
+    assertEquals(a1.getBi9ls(), Arrays.asList(b1, b2, b3));
+    assertEquals(a1.getBi9lList(), Arrays.asList(b1, b2, b3));
+    assertEquals(a2.getBi9ls(), Arrays.asList());
+    assertEquals(a2.getBi9lList(), Arrays.asList());
+    assertEquals(a3.getBi9ls(), Arrays.asList(b3));
+    assertEquals(a3.getBi9lList(), 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.addBi7l(B.createRef("b1"));
+    a1.addBi7l(B.createRef("b2"));
+    try {
+      a1.getBi7ls().add(B.createRef("b3"));
+      fail("should throw an exception");
+    } catch (Exception e) {
+      // OK
+    }
+
+    a1.addBi9l(B.createRef("b1"));
+    a1.addBi9l(B.createRef("b2"));
+    try {
+      a1.getBi9ls().add(B.createRef("b3"));
+      fail("should throw an exception");
+    } catch (Exception e) {
+      // OK
+    }
+  }
+
+  @BeforeEach
+  void setup() {
+    r = new Root();
+    a1 = new A("a1");
+    a2 = new A("a2");
+    a3 = new A("a3");
+    b1 = new B("b1");
+    b2 = new B("b2");
+    b3 = new B("b3");
+
+    r.addA(a1);
+    r.addA(a2);
+    r.addA(a3);
+    r.addB(b1);
+    r.addB(b2);
+    r.addB(b3);
+  }
+
+}