diff --git a/build.gradle b/build.gradle
index aa950e4f313455285a629e034648c856b4c90d93..068c1f63ec3bc3e4f09a87e14198f696307c1bf9 100644
--- a/build.gradle
+++ b/build.gradle
@@ -30,6 +30,7 @@ dependencies {
     testCompile 'org.assertj:assertj-core:3.12.1'
     compile 'com.fasterxml.jackson.core:jackson-core:2.9.8'
     compile 'com.fasterxml.jackson.core:jackson-databind:2.9.8'
+    compile 'org.jastadd:jastadd:2.3.4'
     runtime 'org.jastadd:jastadd:2.3.4'
     compile group: 'net.sf.beaver', name: 'beaver-rt', version: '0.9.11'
     compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.10.0'
@@ -159,7 +160,7 @@ task firstRelationsRun(type: RelastTest) {
     grammarName = 'src/test/jastadd/relations/Relations'
     useJastAddNames = true
     packageName = 'relations.ast'
-    moreInputFiles 'src/test/jastadd/Utils.jadd'
+    moreInputFiles 'src/test/jastadd/Utils.jadd', 'src/test/jastadd/relations/Relations.jrag'
 }
 
 task secondRelationsRun(type: RelastTest, dependsOn: firstRelationsRun) {
@@ -185,7 +186,7 @@ task compileDefaultNamesTest(type: RelastTest) {
     relastFiles 'src/test/jastadd/relations/Relations.relast'
     grammarName = 'src/test/jastadd/relations/Relations3'
     packageName = 'defaultnames.ast'
-    moreInputFiles 'src/test/jastadd/Utils.jadd'
+    moreInputFiles 'src/test/jastadd/Utils.jadd', 'src/test/jastadd/relations/Relations.jrag'
 }
 
 task compileDefaultNamesResolverTest(type: RelastTest) {
@@ -253,6 +254,7 @@ task compileListNamesTest(type: RelastTest) {
     grammarName = 'src/test/jastadd/listnames/ListNames'
     useJastAddNames = true
     jastAddList = 'ListyMcListface'
+    listClass = 'java.util.LinkedList'
     packageName = 'listnames.ast'
     moreInputFiles 'src/test/jastadd/Utils.jadd'
 }
diff --git a/src/main/jastadd/Analysis.jrag b/src/main/jastadd/Analysis.jrag
index 139bd082748e4407dbdcbf851f54a8147fba7949..21456331346ac897ee8f597c52e93c1ac8edb498 100644
--- a/src/main/jastadd/Analysis.jrag
+++ b/src/main/jastadd/Analysis.jrag
@@ -36,13 +36,13 @@ aspect ComponentAnalysis {
   eq Program.getChild().enclosingTypeDecl() = null;
 
   //--- otherSide ---
-  inh RelationComponent RelationComponent.otherSide();
-  eq Relation.getLeft().otherSide() = getRight();
-  eq Relation.getRight().otherSide() = getLeft();
-  eq Program.getChild().otherSide() = null;
+  inh RelationComponent RelationComponent.opposite();
+  eq Relation.getLeft().opposite() = getRight();
+  eq Relation.getRight().opposite() = getLeft();
+  eq Program.getChild().opposite() = null;
 
   //--- ofTypeDecl ---
-  syn TypeDecl RelationComponent.ofTypeDecl() = otherSide().getTypeUse().decl();
+  syn TypeDecl RelationComponent.ofTypeDecl() = opposite().getTypeUse().decl();
 
   //--- isAlreadyDeclared ---
   /**
@@ -141,7 +141,7 @@ aspect ComponentAnalysis {
 
   //--- relationComponentsTransitive ---
   syn Collection<RelationComponent> TypeDecl.relationComponentsTransitive() {
-    ArrayList<RelationComponent> list = new ArrayList<>();
+    Collection<RelationComponent> list = new ArrayList<>();
     if (hasSuper() && getSuper().decl() != null) {
       list.addAll(getSuper().decl().relationComponentsTransitive());
     }
@@ -170,10 +170,14 @@ aspect ComponentAnalysis {
   //--- isList ---
   syn boolean Component.isList() = false;
   eq ListComponent.isList() = true;
+  eq NTAListComponent.isList() = true;
+  eq ManyRelationComponent.isList() = true;
 
   //--- isOpt ---
   syn boolean Component.isOpt() = false;
   eq OptComponent.isOpt() = true;
+  eq NTAOptComponent.isOpt() = true;
+  eq OptionalRelationComponent.isOpt() = true;
 
   //--- isNullable ---
   syn boolean Component.isNullable() = false;
@@ -188,7 +192,7 @@ aspect InstanceSupplier {
 
   //--- subTypeDecls ---
   syn Collection<TypeDecl> TypeDecl.subTypeDecls() {
-    java.util.List<TypeDecl> subDecls = new ArrayList();
+    Collection<TypeDecl> subDecls = new ArrayList();
     for (TypeDecl decl : program().getTypeDeclList()) {
       if (decl.hasSuper() && decl.getSuper().getID().equals(getID())) {
         subDecls.add(decl);
@@ -248,10 +252,6 @@ aspect Constructors {
 
 aspect Utils {
 
-  //--- isMany ---
-  syn boolean RelationComponent.isMany() = false;
-  eq ManyRelationComponent.isMany() = true;
-
   //--- toString ---
   public String SimpleTypeUse.toString() {
     return getID();
diff --git a/src/main/jastadd/DumpTree.jrag b/src/main/jastadd/DumpTree.jrag
index 9193b9334fb63901d8dc985b5e5ba091e6a1e710..2d2f3403c7c03738cd6ff6e4351d22dadc440988 100644
--- a/src/main/jastadd/DumpTree.jrag
+++ b/src/main/jastadd/DumpTree.jrag
@@ -1,25 +1,18 @@
-import java.io.ByteArrayOutputStream;
-import java.io.PrintStream;
-import java.lang.reflect.InvocationTargetException;
-
-// We have been careful to not import many types here because the imports are added for
-// _ALL_ AST nodes and can cause collisions in the abstract grammar namespace.
-
 aspect DumpTree {
   private static final String ASTNode.DUMP_TREE_INDENT = "  ";
 
   public String ASTNode.dumpTree() {
-    ByteArrayOutputStream bytes = new ByteArrayOutputStream();
-    dumpTree(new PrintStream(bytes));
+    java.io.ByteArrayOutputStream bytes = new java.io.ByteArrayOutputStream();
+    dumpTree(new java.io.PrintStream(bytes));
     return bytes.toString();
   }
 
-  public void ASTNode.dumpTree(PrintStream out) {
+  public void ASTNode.dumpTree(java.io.PrintStream out) {
     dumpTree(out, "");
     out.flush();
   }
 
-  public void ASTNode.dumpTree(PrintStream out, String indent) {
+  public void ASTNode.dumpTree(java.io.PrintStream out, String indent) {
     out.print(indent + getClass().getSimpleName());
     out.print(getTokens());
     String extra = extraDumpInfo();
@@ -40,26 +33,21 @@ aspect DumpTree {
   public String ASTNode.extraDumpInfo() { return ""; }
 
   public String ASTNode.getTokens() {
-    java.util.TreeSet<java.lang.reflect.Method> methods = new java.util.TreeSet<>(
-        new java.util.Comparator<java.lang.reflect.Method>() {
-          public int compare(java.lang.reflect.Method m1, java.lang.reflect.Method m2) {
-            return m1.getName().compareTo(m2.getName());
-          }
-        });
+    java.util.TreeSet<java.lang.reflect.Method> methods = new java.util.TreeSet<>(Comparator.comparing(java.lang.reflect.Method::getName));
 
     methods.addAll(java.util.Arrays.asList(getClass().getMethods()));
 
-    String result = "";
+    StringBuilder result = new StringBuilder();
     for (java.lang.reflect.Method method : methods) {
       ASTNodeAnnotation.Token token = method.getAnnotation(ASTNodeAnnotation.Token.class);
       if (token != null) {
         try {
-          result += String.format(" %s=\"%s\"", token.name(), method.invoke(this));
-        } catch (IllegalAccessException ignored) {
-        } catch (InvocationTargetException ignored) {
+          result.append(String.format(" %s=\"%s\"", token.name(), method.invoke(this)));
+        } catch (IllegalAccessException  ignored) {
+        } catch (java.lang.reflect.InvocationTargetException ignored) {
         }
       }
     }
-    return result;
+    return result.toString();
   }
-}
\ No newline at end of file
+}
diff --git a/src/main/jastadd/backend/API.jadd b/src/main/jastadd/backend/API.jadd
index 481bc2dbbea2bb55c7d0fd6f0bf350e739fba416..97be8bc90327168fcc71c54b2d99220a280754b5 100644
--- a/src/main/jastadd/backend/API.jadd
+++ b/src/main/jastadd/backend/API.jadd
@@ -1,16 +1,105 @@
 aspect BackendAPI {
   public void Relation.generateAPI(StringBuilder sb) {
-    sb.append(ind(1) + "// " + prettyPrint() + "\n");
-    getDirection().generateAPI(sb);
+    sb.append(ind(1) + "// api for " + prettyPrint() + "\n");
+    if (getLeft().isNavigable()) {
+      sb.append(ind(1) + "// left direction for " + prettyPrint() + "\n");
+      getLeft().generateAPI(sb);
+    }
+    if (getRight().isNavigable()) {
+      sb.append(ind(1) + "// right direction for " + prettyPrint() + "\n");
+      getRight().generateAPI(sb);
+    }
     sb.append("\n");
   }
-  public abstract void Direction.generateAPI(StringBuilder sb);
 
+  public void RelationComponent.generateAPI(StringBuilder sb) {
+    if (opposite().isNavigable()) {
+      if (multiplicityOne() || multiplicityOpt()) {
+        generateGetOne(sb);
+        if (opposite().multiplicityOne() || opposite().multiplicityOpt()) {
+          generateBiOneOne(sb);
+        } else if (opposite().multiplicityMany()) {
+          generateBiOneMany(sb);
+        }
+        if (isOpt()) {
+          generateExtraOptAPI(sb);
+        }
+      } else if (multiplicityMany()) {
+        if (opposite().multiplicityOne() || opposite().multiplicityOpt()) {
+          generateBiManyOne(sb);
+        } else if (opposite().multiplicityMany()) {
+          generateBiManyMany(sb);
+        }
+      }
+    } else {
+      if (multiplicityOne() || multiplicityOpt()) {
+        generateGetOne(sb);
+        generateDirectedZeroOneAPI(sb);
+        if (isOpt()) {
+          generateExtraOptAPI(sb);
+        }
+      } else if (multiplicityMany()) {
+        generateDirectedManyAPI(sb);
+      }
+    }
+  }
+
+  public void RelationComponent.generateGetOne(StringBuilder sb) {
+    sb.append(ind(1) + "public " + ofTypeDecl() + " " + getTypeUse().decl() + ".");
+    if (useJastAddNames) {
+      sb.append("get" + nameCapitalized());
+    } else {
+      sb.append(name());
+    }
+    sb.append("() {\n");
+    if (resolverHelper | serializer) {
+      sb.append(ind(2) + "if (" + getImplAttributeField() + " != null && " + getImplAttributeField() + "." + isUnresolvedMethod + "()) {\n");
+        sb.append(ind(3) + "if (" + getImplAttributeField() + "." + asUnresolvedMethod + "().get" + unresolvedPrefix + "ResolveOpposite()) {\n");
+          sb.append(ind(4) + "set" + nameCapitalized() + "(resolve" + nameCapitalized() + resolvePostfix + "(" + getImplAttributeField() + "." + asUnresolvedMethod + "().get" + unresolvedPrefix + "Token()));\n");
+        sb.append(ind(3) + "} else {\n");
+          sb.append(ind(4) + "set" + getImplAttributeName() + "(resolve" + nameCapitalized() + resolvePostfix + "(" + getImplAttributeField() + "." + asUnresolvedMethod + "().get" + unresolvedPrefix + "Token()));\n");
+        sb.append(ind(3) + "}\n");
+      sb.append(ind(2) + "}\n");
+    }
+    sb.append(ind(2) + "return get" + getImplAttributeName() + "();\n");
+    sb.append(ind(1) + "}\n");
+  }
+
+  public void RelationComponent.generateExtraOptAPI(StringBuilder sb) {
+    // has
+    sb.append(ind(1) + "public boolean " + getTypeUse().decl());
+    sb.append(".has" + nameCapitalized() + "() {\n");
+    sb.append(ind(2) + "return ");
+    if (useJastAddNames) {
+      sb.append("get" + nameCapitalized());
+    } else {
+      sb.append(name());
+    }
+    sb.append("() != null;\n");
+    sb.append(ind(1) + "}\n");
+
+    // clear
+    sb.append(ind(1) + "public void " + getTypeUse().decl());
+    sb.append(".clear" + nameCapitalized() + "() {\n");
+    sb.append(ind(2) + "set" + nameCapitalized() + "(null);\n");
+    sb.append(ind(1) + "}\n");
+  }
 
   inh Relation Direction.relation();
+  inh Relation RelationComponent.relation();
   eq Relation.getChild().relation() = this;
   eq Program.getChild().relation() = null;
 
+  inh boolean RelationComponent.isNavigable();
+  eq Relation.getLeft().isNavigable() = getDirection().isNavigableLeftToRight();
+  eq Relation.getRight().isNavigable() = getDirection().isNavigableRightToLeft();
+  eq Program.getChild().isNavigable() = false;
+
+  syn boolean Direction.isNavigableRightToLeft() = true;
+  eq RightDirection.isNavigableRightToLeft() = false;
+  syn boolean Direction.isNavigableLeftToRight() = true;
+  eq LeftDirection.isNavigableLeftToRight() = false;
+
   public String RelationComponent.nameCapitalized() {
     return name().substring(0,1).toUpperCase() + name().substring(1);
   }
diff --git a/src/main/jastadd/backend/AbstractGrammar.jadd b/src/main/jastadd/backend/AbstractGrammar.jadd
index a9728d231ef25162218719dc08a47853003abbdb..b067019d21a52f64e9000c1decd9cbf6bcb080aa 100644
--- a/src/main/jastadd/backend/AbstractGrammar.jadd
+++ b/src/main/jastadd/backend/AbstractGrammar.jadd
@@ -1,7 +1,9 @@
 aspect BackendAbstractGrammar {
 
-  public static String ASTNode.listClass = "ArrayList";
+  public static String ASTNode.listInterface = "java.util.List";
+  public static String ASTNode.listClass = "java.util.ArrayList";
   public static String ASTNode.jastAddListType = "List";
+  public static String ASTNode.jastAddOptType = "Opt";
 
   public static boolean ASTNode.resolverHelper = false;
   public static boolean ASTNode.serializer = false;
@@ -21,47 +23,6 @@ aspect BackendAbstractGrammar {
     }
   }
 
-  public void TypeDecl.generateUnresolvedClass(StringBuilder sb) {
-    if (getAbstract()) {
-      sb.append(ind(1) + "abstract ");
-    } else {
-      sb.append(ind(1));
-    }
-    sb.append("class " + "Unresolved$" + getID() + " extends " + getID() + "  implements Unresolved$Node {\n");
-
-    sb.append(ind(2) + "private String unresolved$Token;\n");
-    sb.append(ind(2) + "public String getUnresolved$Token() {\n");
-    sb.append(ind(3) + "return unresolved$Token;\n");
-    sb.append(ind(2) + "}\n");
-    sb.append(ind(2) + "void setUnresolved$Token(String token) {\n");
-    sb.append(ind(3) + "this.unresolved$Token = token;\n");
-    sb.append(ind(2) + "}\n");
-
-    sb.append(ind(2) + "private boolean unresolved$ResolveOpposite;\n");
-    sb.append(ind(2) + "public boolean getUnresolved$ResolveOpposite() {\n");
-    sb.append(ind(3) + "return unresolved$ResolveOpposite;\n");
-    sb.append(ind(2) + "}\n");
-    sb.append(ind(2) + "void setUnresolved$ResolveOpposite(boolean resolveOpposite) {\n");
-    sb.append(ind(3) + "this.unresolved$ResolveOpposite = resolveOpposite;\n");
-    sb.append(ind(2) + "}\n");
-
-    sb.append(ind(1) + "}\n");
-
-    sb.append(ind(1) + "Unresolved$Node " + getID() + ".as$Unresolved() {\n");
-    sb.append(ind(2) + "return null;\n");
-    sb.append(ind(1) + "}\n");
-    sb.append(ind(1) + "Unresolved$Node Unresolved$" + getID() + ".as$Unresolved() {\n");
-    sb.append(ind(2) + "return this;\n");
-    sb.append(ind(1) + "}\n");
-
-    sb.append(ind(1) + "boolean " + getID() + ".is$Unresolved() {\n");
-    sb.append(ind(2) + "return false;\n");
-    sb.append(ind(1) + "}\n");
-    sb.append(ind(1) + "boolean Unresolved$" + getID() + ".is$Unresolved() {\n");
-    sb.append(ind(2) + "return true;\n");
-    sb.append(ind(1) + "}\n");
-  }
-
   public void TypeDecl.generateAbstractGrammar(StringBuilder sb) {
     if (getAbstract()) {
       sb.append("abstract ");
@@ -119,7 +80,7 @@ aspect BackendAbstractGrammar {
     return "<" + getImplAttributeName() + ":" + ofTypeDecl() + ">";
   }
   public String ManyRelationComponent.generateAbstractGrammar() {
-    return "<" + getImplAttributeName() + ":" + ASTNode.listClass + "<" + ofTypeDecl() + ">>";
+    return "<" + getImplAttributeName() + ":" + ASTNode.listInterface + "<" + ofTypeDecl() + ">>";
   }
 
   public String RelationComponent.getImplAttributeName() {
@@ -128,11 +89,11 @@ aspect BackendAbstractGrammar {
 
   public String RelationComponent.getImplAttributeField() {
     //  tt.bind("TypeInSignature", ASTNode.convTypeNameToSignature(type()));
-    return "token" + ofTypeDecl() + "__impl_" + getID();
+    return "token" + org.jastadd.ast.AST.ASTNode.convTypeNameToSignature(ofTypeDecl().toString()) + "__impl_" + getID();
   }
 
   public String ManyRelationComponent.getImplAttributeField() {
     //  tt.bind("TypeInSignature", ASTNode.convTypeNameToSignature(type()));
-    return "token" + listClass + "_" + ofTypeDecl() + "___impl_" + getID();
+    return "token" + org.jastadd.ast.AST.ASTNode.convTypeNameToSignature(ASTNode.listInterface) + "_" + ofTypeDecl() + "___impl_" + getID();
   }
 }
diff --git a/src/main/jastadd/backend/Backend.jadd b/src/main/jastadd/backend/Backend.jadd
index c751488eb18df1a1e59b4a93edebf341bcdea0c0..cc4477d91bac5e0fe442be6130c418c7803abdd7 100644
--- a/src/main/jastadd/backend/Backend.jadd
+++ b/src/main/jastadd/backend/Backend.jadd
@@ -6,10 +6,6 @@ aspect BackendAspect {
   }
 
   public void Program.generateAspect(StringBuilder sb) {
-    sb.append("import java.util.ArrayList;\n");
-    sb.append("import java.util.Collections;\n");
-    sb.append("import java.time.Instant;\n");
-    sb.append("import java.time.Period;\n");
     sb.append("aspect RelAstAPI {\n");
 
     for (TypeDecl td: getTypeDecls()) {
@@ -53,7 +49,7 @@ aspect BackendAspect {
     return ASTNode.jastAddListType + "<" + getTypeUse() + "> " + getID();
   }
   public String OptComponent.constructorParameter() {
-    return "Opt<" + getTypeUse() + "> " + getID();
+    return ASTNode.jastAddOptType + "<" + getTypeUse() + "> " + getID();
   }
   public String Component.constructorSetMethod() {
     return "set" + getID();
diff --git a/src/main/jastadd/backend/BidirectionalAPI.jadd b/src/main/jastadd/backend/BidirectionalAPI.jadd
index 75f28ab40df71d2690208408e6cb314d9107d886..3ee8f6d4fa5588f36949d770aa7a27560811689f 100644
--- a/src/main/jastadd/backend/BidirectionalAPI.jadd
+++ b/src/main/jastadd/backend/BidirectionalAPI.jadd
@@ -1,349 +1,261 @@
 aspect BackendBidirectionalAPI {
-  public void Bidirectional.generateAPI(StringBuilder sb) {
-    RelationComponent l = relation().getLeft();
-    RelationComponent r = relation().getRight();
 
-    if (l.multiplicityOne()) {
-      if (r.multiplicityOne()) {
-        l.generateBiOneOne(sb, false);
-        r.generateBiOneOne(sb, false);
-      } else if (r.multiplicityOpt()) {
-        l.generateBiOneOne(sb, false);
-        r.generateBiOneOne(sb, true);
-      } else if (r.multiplicityMany()) {
-        l.generateBiOneMany(sb, false);
-        r.generateBiManyOne(sb, l);
-      }
-    } else if (l.multiplicityOpt()) {
-      if (r.multiplicityOne()) {
-        l.generateBiOneOne(sb, true);
-        r.generateBiOneOne(sb, false);
-      } else if (r.multiplicityOpt()) {
-        l.generateBiOneOne(sb, true);
-        r.generateBiOneOne(sb, true);
-      } else if (r.multiplicityMany()) {
-        l.generateBiOneMany(sb, true);
-        r.generateBiManyOne(sb, l);
-      }
-    } else if (l.multiplicityMany()) {
-      if (r.multiplicityOne()) {
-        l.generateBiManyOne(sb, r);
-        r.generateBiOneMany(sb, false);
-      } else if (r.multiplicityOpt()) {
-        l.generateBiManyOne(sb, r);
-        r.generateBiOneMany(sb, true);
-      } else if (r.multiplicityMany()) {
-        l.generateBiManyMany(sb, r);
-        r.generateBiManyMany(sb, l);
-      }
-    }
-  }
-
-  public void RelationComponent.generateBiOneOne(StringBuilder sb, boolean isOpt) {
-    // Get
-    generateGetOne(sb);
+  public void RelationComponent.generateBiOneOne(StringBuilder sb) {
+    boolean resolve = resolverHelper || serializer;
 
     // Set
-    sb.append(ind(1) + "public " + getTypeUse().decl() + " " + getTypeUse().decl());
-    sb.append(".set" + nameCapitalized() + "(" + ofTypeDecl() + " o) {\n");
-    if (!isOpt) {
+    String setMethodDecl = getTypeUse().decl() + " " + getTypeUse().decl() + ".set" + nameCapitalized() + "(" + ofTypeDecl() + " o)";
+    sb.append(ind(1) + "public " + setMethodDecl + " {\n");
+    if (!isOpt()) {
       sb.append(ind(2) + "assertNotNull(o);\n");
     }
     // unset the old opposite
-    sb.append(ind(2) + "if (" + getImplAttributeField() + " != null) {\n");
-    sb.append(ind(3) + "" + getImplAttributeField() + ".set" + otherSide().getImplAttributeName() + "(null);\n");
-    sb.append(ind(2) + "}\n");
-    if (resolverHelper | serializer) {
-      sb.append(ind(2) + "if (o != null && !o.is$Unresolved() && o." + otherSide().getImplAttributeField() + " != null) {\n");
-    } else {
-      sb.append(ind(2) + "if (o != null && o." + otherSide().getImplAttributeField() + " != null) {\n");
-    }
-    sb.append(ind(3) + "o." + otherSide().getImplAttributeField() + ".set" + getImplAttributeName() + "(null);\n");
+    sb.append(ind(2) + "if (" + getImplAttributeField() + " != null)\n");
+    sb.append(ind(3) + getImplAttributeField() + ".set" + opposite().getImplAttributeName() + "(null);\n");
+
+    sb.append(ind(2) + "if (o != null && " + (resolve ? "!o." + isUnresolvedMethod + "() && o." : "o.") + opposite().getImplAttributeField() + " != null) {\n");
+    sb.append(ind(3) + "o." + opposite().getImplAttributeField() + ".set" + getImplAttributeName() + "(null);\n");
     sb.append(ind(2) + "}\n");
     sb.append(ind(2) + "set" + getImplAttributeName() + "(o);\n");
-    if (resolverHelper | serializer) {
-      sb.append(ind(2) + "if (o == null || !o.is$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 {
-      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");
-      }
+    if (resolve) {
+      sb.append(ind(2) + "if (o == null || !o." + isUnresolvedMethod + "())\n");
     }
+    if (isOpt()) {
+      sb.append(ind(resolve ? 3 : 2) + "if (o != null)\n");
+    }
+    sb.append(ind((isOpt() ? 1 : 0) + (resolve ? 3 : 2)) + "o.set" + opposite().getImplAttributeName() + "(this);\n");
 
     sb.append(ind(2) + "return this;\n");
     sb.append(ind(1) + "}\n");
-
-    if (isOpt) {
-      generateExtraOptAPI(sb);
-    }
   }
 
-  public void RelationComponent.generateBiManyMany(StringBuilder sb, RelationComponent opposite) {
+  public void RelationComponent.generateBiManyMany(StringBuilder sb) {
     // Get
-    sb.append(ind(1) + "public java.util.List<" + ofTypeDecl() + "> " + getTypeUse().decl() + ".");
     if (useJastAddNames) {
+      String getMethodDecl1 = "java.util.List<" + ofTypeDecl() + "> " + getTypeUse().decl() + ".get" + nameCapitalized() + "s()";
+      String getMethodDecl2 = "java.util.List<" + ofTypeDecl() + "> " + getTypeUse().decl() + ".get" + nameCapitalized() + "List()";
+
       // getXs
-      sb.append("get" + nameCapitalized() + "s() {\n");
+      sb.append("public " + getMethodDecl1 + " {\n");
       sb.append(ind(2) + "return get" + nameCapitalized() + "List();\n");
       sb.append(ind(1) + "}\n");
 
       // getXList
-      sb.append(ind(1) + "public java.util.List<" + ofTypeDecl() + "> " + getTypeUse().decl());
-      sb.append(".get" + nameCapitalized() + "List() {\n");
+      sb.append(ind(1) + "public " + getMethodDecl2 + " {\n");
     } else {
-      sb.append(name() + "() {\n");
+      String getMethodDecl = "java.util.List<" + ofTypeDecl() + "> " + getTypeUse().decl() + "." + name() + "()";
+      sb.append("public " + getMethodDecl + " {\n");
     }
-    sb.append(ind(2) + ASTNode.listClass + "<" + ofTypeDecl() + "> l = get" + getImplAttributeName() + "();\n");
+    sb.append(ind(2) + ASTNode.listInterface + "<" + ofTypeDecl() + "> l = get" + getImplAttributeName() + "();\n");
     // resolve the entire list
     if (resolverHelper | serializer) {
       sb.append(ind(2) + "if (l != null) {\n");
-        sb.append(ind(3) + "boolean changed = false;\n");
-        sb.append(ind(3) + "for (int i = 0; i < l.size(); i++) {\n");
-          sb.append(ind(4) + ofTypeDecl() + " element = l.get(i);\n");
-          sb.append(ind(4) + "if (element.is$Unresolved()) {\n");
-            sb.append(ind(5) + "changed = true;\n");
-            sb.append(ind(5) + ofTypeDecl() + " resolvedElement = resolve" + nameCapitalized() + "ByToken(element.as$Unresolved().getUnresolved$Token(), i);\n");
-            sb.append(ind(5) + "if (resolvedElement != null && element.as$Unresolved().getUnresolved$ResolveOpposite()) {\n");
-              sb.append(ind(6) + ASTNode.listClass + "<" + getTypeUse().decl() + "> otherList = resolvedElement." + opposite.getImplAttributeField() + ";\n");
-              sb.append(ind(6) + "if (otherList == null) {\n");
-                sb.append(ind(7) + "otherList = new " + listClass + "<>();\n");
-              sb.append(ind(6) + "}\n");
-              sb.append(ind(6) + "otherList.add(this);\n");
-              sb.append(ind(6) + "resolvedElement.set" + opposite.getImplAttributeName() + "(otherList);\n");
-            sb.append(ind(5) + "}\n");
-            sb.append(ind(5) + "l.set(i, resolvedElement);\n");
-          sb.append(ind(4) + "}\n");
-        sb.append(ind(3) + "}\n");
-        sb.append(ind(3) + "if (changed) {\n");
-          sb.append(ind(4) + "set" + getImplAttributeName() + "(l);\n");
-        sb.append(ind(3) + "}\n");
+      sb.append(ind(3) + "boolean changed = false;\n");
+      sb.append(ind(3) + "for (int i = 0; i < l.size(); i++) {\n");
+      sb.append(ind(4) + ofTypeDecl() + " element = l.get(i);\n");
+      sb.append(ind(4) + "if (element." + isUnresolvedMethod + "()) {\n");
+      sb.append(ind(5) + "changed = true;\n");
+      sb.append(ind(5) + ofTypeDecl() + " resolvedElement = resolve" + nameCapitalized() + "" + resolvePostfix + "(element." + asUnresolvedMethod + "().get" + unresolvedTokenMethod + "(), i);\n");
+      sb.append(ind(5) + "if (resolvedElement != null && element." + asUnresolvedMethod + "().get" + unresolvedResolveOppositeMethod + "()) {\n");
+      sb.append(ind(6) + ASTNode.listInterface + "<" + getTypeUse().decl() + "> otherList = resolvedElement." + opposite().getImplAttributeField() + ";\n");
+      sb.append(ind(6) + "if (otherList == null) {\n");
+      sb.append(ind(7) + "otherList = new " + listClass + "<>();\n");
+      sb.append(ind(6) + "}\n");
+      sb.append(ind(6) + "otherList.add(this);\n");
+      sb.append(ind(6) + "resolvedElement.set" + opposite().getImplAttributeName() + "(otherList);\n");
+      sb.append(ind(5) + "}\n");
+      sb.append(ind(5) + "l.set(i, resolvedElement);\n");
+      sb.append(ind(4) + "}\n");
+      sb.append(ind(3) + "}\n");
+      sb.append(ind(3) + "if (changed) {\n");
+      sb.append(ind(4) + "set" + getImplAttributeName() + "(l);\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(2) + "return l != null ? java.util.Collections.unmodifiableList(l) : java.util.Collections.emptyList();\n");
     sb.append(ind(1) + "}\n");
 
     // Add
-    sb.append(ind(1) + "public void " + getTypeUse().decl() + ".add");
-    if (!useJastAddNames) {
-      sb.append("To");
-    }
-    sb.append(nameCapitalized() + "(" + ofTypeDecl() + " o) {\n");
+    String addMethodDecl1 = "void " + getTypeUse().decl() + ".add" + (useJastAddNames ? "" : "To") + nameCapitalized() + "(" + ofTypeDecl() + " o)";
+    sb.append(ind(1) + "public " + addMethodDecl1 + " {\n");
     sb.append(ind(2) + "assertNotNull(o);\n");
-    sb.append(ind(2) + ASTNode.listClass + "<" + ofTypeDecl() + "> list = " + getImplAttributeField() + ";\n");
+    sb.append(ind(2) + ASTNode.listInterface + "<" + ofTypeDecl() + "> list = " + getImplAttributeField() + ";\n");
     sb.append(ind(2) + "if (list == null) {\n");
     sb.append(ind(3) + "list = new " + ASTNode.listClass + "<>();\n");
     sb.append(ind(2) + "}\n");
-    sb.append(ind(2) + ASTNode.listClass + "<" + otherSide().ofTypeDecl() + "> list2 = o." + otherSide().getImplAttributeField() + ";\n");
+    sb.append(ind(2) + ASTNode.listInterface + "<" + opposite().ofTypeDecl() + "> list2 = o." + opposite().getImplAttributeField() + ";\n");
     sb.append(ind(2) + "if (list2 == null) {\n");
     sb.append(ind(3) + "list2 = new "+ ASTNode.listClass + "<>();\n");
     sb.append(ind(2) + "}\n");
     sb.append(ind(2) + "list.add(o);\n");
     sb.append(ind(2) + "list2.add(this);\n");
     sb.append(ind(2) + "set" + getImplAttributeName() + "(list);\n");
-    sb.append(ind(2) + "o.set" + otherSide().getImplAttributeName() + "(list2);\n");
+    sb.append(ind(2) + "o.set" + opposite().getImplAttributeName() + "(list2);\n");
     sb.append(ind(1) + "}\n");
 
     // Insert / add at specific position
-    sb.append(ind(1) + "public void " + getTypeUse().decl() + ".add");
-    if (!useJastAddNames) {
-      sb.append("To");
-    }
-    sb.append(nameCapitalized() + "(int index, " + ofTypeDecl() + " o) {\n");
+    String addMethodDecl2 = "void " + getTypeUse().decl() + ".add" + (useJastAddNames ? "" : "To") + nameCapitalized() + "(int index, " + ofTypeDecl() + " o)";
+    sb.append(ind(1) + "public " + addMethodDecl2 + " {\n");
     sb.append(ind(2) + "assertNotNull(o);\n");
-    sb.append(ind(2) + ASTNode.listClass + "<" + ofTypeDecl() + "> list = " + getImplAttributeField() + ";\n");
+    sb.append(ind(2) + ASTNode.listInterface + "<" + ofTypeDecl() + "> list = " + getImplAttributeField() + ";\n");
     sb.append(ind(2) + "if (list == null) {\n");
     sb.append(ind(3) + "list = new " + ASTNode.listClass + "<>();\n");
     sb.append(ind(2) + "}\n");
-    sb.append(ind(2) + ASTNode.listClass + "<" + otherSide().ofTypeDecl() + "> list2 = o."
-    + otherSide().getImplAttributeField() + ";\n");
+    sb.append(ind(2) + ASTNode.listInterface + "<" + opposite().ofTypeDecl() + "> list2 = o." + opposite().getImplAttributeField() + ";\n");
     sb.append(ind(2) + "if (list2 == null) {\n");
     sb.append(ind(3) + "list2 = new "+ ASTNode.listClass + "<>();\n");
     sb.append(ind(2) + "}\n");
     sb.append(ind(2) + "list.add(index, o);\n");
     sb.append(ind(2) + "list2.add(this);\n");
     sb.append(ind(2) + "set" + getImplAttributeName() + "(list);\n");
-    sb.append(ind(2) + "o.set" + otherSide().getImplAttributeName() + "(list2);\n");
+    sb.append(ind(2) + "o.set" + opposite().getImplAttributeName() + "(list2);\n");
     sb.append(ind(1) + "}\n");
 
     // Remove
-    sb.append(ind(1) + "public void " + getTypeUse().decl() + ".remove");
-    if (!useJastAddNames) {
-      sb.append("From");
-    }
-    sb.append(nameCapitalized() + "(" + ofTypeDecl() + " o) {\n");
+    String removeMethodDecl = "void " + getTypeUse().decl() + ".remove" + (useJastAddNames ? "" : "From") + nameCapitalized() + "(" + ofTypeDecl() + " o)";
+    sb.append(ind(1) + "public " + removeMethodDecl + " {\n");
     sb.append(ind(2) + "assertNotNull(o);\n");
-    sb.append(ind(2) + ASTNode.listClass + "<" + ofTypeDecl() + "> list = " + getImplAttributeField() + ";\n");
+    sb.append(ind(2) + ASTNode.listInterface + "<" + ofTypeDecl() + "> list = " + getImplAttributeField() + ";\n");
     sb.append(ind(2) + "if (list != null && list.remove(o)) {\n");
-    sb.append(ind(3) + ASTNode.listClass + "<" + otherSide().ofTypeDecl() + "> list2 = o."
-      + otherSide().getImplAttributeField() + ";\n");
+    sb.append(ind(3) + ASTNode.listInterface + "<" + opposite().ofTypeDecl() + "> list2 = o." + opposite().getImplAttributeField() + ";\n");
     sb.append(ind(3) + "if (list2 != null) list2.remove(this);\n");
     sb.append(ind(3) + "set" + getImplAttributeName() + "(list);\n");
-    sb.append(ind(3) + "o.set" + otherSide().getImplAttributeName() + "(list2);\n");
+    sb.append(ind(3) + "o.set" + opposite().getImplAttributeName() + "(list2);\n");
     sb.append(ind(2) + "}\n");
     sb.append(ind(1) + "}\n");
   }
 
 
-  public void RelationComponent.generateBiManyOne(StringBuilder sb, RelationComponent opposite) {
+  public void RelationComponent.generateBiManyOne(StringBuilder sb) {
     // Get
-    sb.append(ind(1) + "public java.util.List<" + ofTypeDecl() + "> " + getTypeUse().decl() + ".");
     if (useJastAddNames) {
+      String getMethodDecl1 = "java.util.List<" + ofTypeDecl() + "> " + getTypeUse().decl() + ".get" + nameCapitalized() + "s()";
+      String getMethodDecl2 = "java.util.List<" + ofTypeDecl() + "> " + getTypeUse().decl() + ".get" + nameCapitalized() + "List()";
+
       // getXs
-      sb.append("get" + nameCapitalized() + "s() {\n");
+      sb.append("public " + getMethodDecl1 + " {\n");
       sb.append(ind(2) + "return get" + nameCapitalized() + "List();\n");
       sb.append(ind(1) + "}\n");
 
       // getXList
-      sb.append(ind(1) + "public java.util.List<" + ofTypeDecl() + "> " + getTypeUse().decl());
-      sb.append(".get" + nameCapitalized() + "List() {\n");
+      sb.append(ind(1) + "public " + getMethodDecl2 + " {\n");
     } else {
-      sb.append(name() + "() {\n");
+      String getMethodDecl = "java.util.List<" + ofTypeDecl() + "> " + getTypeUse().decl() + "." + name() + "()";
+      sb.append("public " + getMethodDecl + " {\n");
     }
-    sb.append(ind(2) + ASTNode.listClass + "<" + ofTypeDecl() + "> l = get" + getImplAttributeName() + "();\n");
+    sb.append(ind(2) + ASTNode.listInterface + "<" + ofTypeDecl() + "> l = get" + getImplAttributeName() + "();\n");
     // resolve the entire list
     if (resolverHelper | serializer) {
       sb.append(ind(2) + "if (l != null) {\n");
-        sb.append(ind(3) + "boolean changed = false;\n");
-        sb.append(ind(3) + "for (int i = 0; i < l.size(); i++) {\n");
-          sb.append(ind(4) + ofTypeDecl() + " element = l.get(i);\n");
-          sb.append(ind(4) + "if (element.is$Unresolved()) {\n");
-            sb.append(ind(5) + "changed = true;\n");
-            sb.append(ind(5) + ofTypeDecl() + " resolvedElement = resolve" + nameCapitalized() + "ByToken(element.as$Unresolved().getUnresolved$Token(), i);\n");
-            sb.append(ind(5) + "if (element.as$Unresolved().getUnresolved$ResolveOpposite()) {\n");
-              sb.append(ind(6) + getTypeUse().decl() + " oldTarget = resolvedElement." + opposite.getImplAttributeField() + ";\n");
-              sb.append(ind(6) + "if (oldTarget != null && oldTarget != this) {\n");
-                sb.append(ind(7) + "oldTarget." + getImplAttributeField() + ".remove(resolvedElement);\n");
-              sb.append(ind(6) + "}\n");
-              sb.append(ind(6) + "if (oldTarget == this) {\n");
-                sb.append(ind(7) + "l.remove(i);\n");
-                sb.append(ind(7) + "i--;\n");
-              sb.append(ind(6) + "} else {\n");
-                sb.append(ind(7) + "resolvedElement.set" + opposite.getImplAttributeName() + "(this);\n");
-                sb.append(ind(7) + "l.set(i, resolvedElement);\n");
-              sb.append(ind(6) + "}\n");
-            sb.append(ind(5) + "} else {\n");
-              sb.append(ind(6) + "l.set(i, resolvedElement);\n");
-            sb.append(ind(5) + "}\n");
-          sb.append(ind(4) + "}\n");
-        sb.append(ind(3) + "}\n");
-        sb.append(ind(3) + "if (changed) {\n");
-          sb.append(ind(4) + "set" + getImplAttributeName() + "(l);\n");
-        sb.append(ind(3) + "}\n");
+      sb.append(ind(3) + "boolean changed = false;\n");
+      sb.append(ind(3) + "for (int i = 0; i < l.size(); i++) {\n");
+      sb.append(ind(4) + ofTypeDecl() + " element = l.get(i);\n");
+      sb.append(ind(4) + "if (element." + isUnresolvedMethod + "()) {\n");
+      sb.append(ind(5) + "changed = true;\n");
+      sb.append(ind(5) + ofTypeDecl() + " resolvedElement = resolve" + nameCapitalized() + resolvePostfix + "(element." + asUnresolvedMethod + "().get" + unresolvedTokenMethod + "(), i);\n");
+      sb.append(ind(5) + "if (element." + asUnresolvedMethod + "().get" + unresolvedResolveOppositeMethod + "()) {\n");
+      sb.append(ind(6) + getTypeUse().decl() + " oldTarget = resolvedElement." + opposite().getImplAttributeField() + ";\n");
+      sb.append(ind(6) + "if (oldTarget != null && oldTarget != this) {\n");
+      sb.append(ind(7) + "oldTarget." + getImplAttributeField() + ".remove(resolvedElement);\n");
+      sb.append(ind(6) + "}\n");
+      sb.append(ind(6) + "if (oldTarget == this) {\n");
+      sb.append(ind(7) + "l.remove(i);\n");
+      sb.append(ind(7) + "i--;\n");
+      sb.append(ind(6) + "} else {\n");
+      sb.append(ind(7) + "resolvedElement.set" + opposite().getImplAttributeName() + "(this);\n");
+      sb.append(ind(7) + "l.set(i, resolvedElement);\n");
+      sb.append(ind(6) + "}\n");
+      sb.append(ind(5) + "} else {\n");
+      sb.append(ind(6) + "l.set(i, resolvedElement);\n");
+      sb.append(ind(5) + "}\n");
+      sb.append(ind(4) + "}\n");
+      sb.append(ind(3) + "}\n");
+      sb.append(ind(3) + "if (changed) {\n");
+      sb.append(ind(4) + "set" + getImplAttributeName() + "(l);\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(2) + "return l != null ? java.util.Collections.unmodifiableList(l) : java.util.Collections.emptyList();\n");
     sb.append(ind(1) + "}\n");
 
     // Add
-    sb.append(ind(1) + "public void " + getTypeUse().decl() + ".add");
-    if (!useJastAddNames) {
-      sb.append("To");
-    }
-    sb.append(nameCapitalized() + "(" + ofTypeDecl() + " o) {\n");
+    String addMethodDecl1 = "void " + getTypeUse().decl() + ".add" + (useJastAddNames ? "" : "To") + nameCapitalized() + "(" + ofTypeDecl() + " o)";
+    sb.append(ind(1) + "public " + addMethodDecl1 + " {\n");
     sb.append(ind(2) + "assertNotNull(o);\n");
-    sb.append(ind(2) + "if (o != null && o." + otherSide().getImplAttributeField() + " != null) {\n");
-    sb.append(ind(3) + ASTNode.listClass + "<" + ofTypeDecl() + "> list2 = o."
-      + otherSide().getImplAttributeField() + "." + getImplAttributeField() + ";\n");
+    sb.append(ind(2) + "if (o != null && o." + opposite().getImplAttributeField() + " != null) {\n");
+    sb.append(ind(3) + ASTNode.listInterface + "<" + ofTypeDecl() + "> list2 = o." + opposite().getImplAttributeField() + "." + getImplAttributeField() + ";\n");
     sb.append(ind(3) + "if (list2.remove(o))\n");
-    sb.append(ind(4) + "o." + otherSide().getImplAttributeField()
-      + ".set" + getImplAttributeName() + "(list2);\n");
+    sb.append(ind(4) + "o." + opposite().getImplAttributeField()  + ".set" + getImplAttributeName() + "(list2);\n");
     sb.append(ind(2) + "}\n");
-    sb.append(ind(2) + ASTNode.listClass + "<" + ofTypeDecl() + "> list = " + getImplAttributeField() + ";\n");
+    sb.append(ind(2) + ASTNode.listInterface + "<" + ofTypeDecl() + "> list = " + getImplAttributeField() + ";\n");
     sb.append(ind(2) + "if (list == null) {\n");
     sb.append(ind(3) + "list = new " + ASTNode.listClass + "<>();\n");
     sb.append(ind(2) + "}\n");
     sb.append(ind(2) + "list.add(o);\n");
     sb.append(ind(2) + "set" + getImplAttributeName() + "(list);\n");
-    sb.append(ind(2) + "o.set" + otherSide().getImplAttributeName() + "(this);\n");
+    sb.append(ind(2) + "o.set" + opposite().getImplAttributeName() + "(this);\n");
     sb.append(ind(1) + "}\n");
 
     // Insert / add at specific position
-    sb.append(ind(1) + "public void " + getTypeUse().decl() + ".add");
-    if (!useJastAddNames) {
-      sb.append("To");
-    }
-    sb.append(nameCapitalized() + "(int index, " + ofTypeDecl() + " o) {\n");
-      sb.append(ind(2) + "assertNotNull(o);\n");
-      sb.append(ind(2) + "if (o != null && o." + otherSide().getImplAttributeField() + " != null) {\n");
-        sb.append(ind(3) + ASTNode.listClass + "<" + ofTypeDecl() + "> list2 = o." + otherSide().getImplAttributeField() + "." + getImplAttributeField() + ";\n");
-        sb.append(ind(3) + "if (list2.remove(o))\n");
-          sb.append(ind(4) + "o." + otherSide().getImplAttributeField() + ".set" + getImplAttributeName() + "(list2);\n");
-      sb.append(ind(2) + "}\n");
-      sb.append(ind(2) + ASTNode.listClass + "<" + ofTypeDecl() + "> list = " + getImplAttributeField() + ";\n");
-      sb.append(ind(2) + "if (list == null) {\n");
-        sb.append(ind(3) + "list = new " + ASTNode.listClass + "<>();\n");
-      sb.append(ind(2) + "}\n");
-      sb.append(ind(2) + "list.add(index, o);\n");
-      sb.append(ind(2) + "set" + getImplAttributeName() + "(list);\n");
-      sb.append(ind(2) + "o.set" + otherSide().getImplAttributeName() + "(this);\n");
+    String addMethodDecl2 = "void " + getTypeUse().decl() + ".add" + (useJastAddNames ? "" : "To") + nameCapitalized() + "(int index, " + ofTypeDecl() + " o)";
+    sb.append(ind(1) + "public " + addMethodDecl2 + " {\n");
+    sb.append(ind(2) + "assertNotNull(o);\n");
+    sb.append(ind(2) + "if (o != null && o." + opposite().getImplAttributeField() + " != null) {\n");
+    sb.append(ind(3) + ASTNode.listInterface + "<" + ofTypeDecl() + "> list2 = o." + opposite().getImplAttributeField() + "." + getImplAttributeField() + ";\n");
+    sb.append(ind(3) + "if (list2.remove(o))\n");
+    sb.append(ind(4) + "o." + opposite().getImplAttributeField() + ".set" + getImplAttributeName() + "(list2);\n");
+    sb.append(ind(2) + "}\n");
+    sb.append(ind(2) + ASTNode.listInterface + "<" + ofTypeDecl() + "> list = " + getImplAttributeField() + ";\n");
+    sb.append(ind(2) + "if (list == null) {\n");
+    sb.append(ind(3) + "list = new " + ASTNode.listClass + "<>();\n");
+    sb.append(ind(2) + "}\n");
+    sb.append(ind(2) + "list.add(index, o);\n");
+    sb.append(ind(2) + "set" + getImplAttributeName() + "(list);\n");
+    sb.append(ind(2) + "o.set" + opposite().getImplAttributeName() + "(this);\n");
     sb.append(ind(1) + "}\n");
 
     // Remove
-    sb.append(ind(1) + "public void " + getTypeUse().decl() + ".remove");
-    if (!useJastAddNames) {
-      sb.append("From");
-    }
-    sb.append(nameCapitalized() + "(" + ofTypeDecl() + " o) {\n");
+    String removeMethodDecl = "void " + getTypeUse().decl() + ".remove" + (useJastAddNames ? "" : "From") + nameCapitalized() + "(" + ofTypeDecl() + " o)";
+    sb.append(ind(1) + "public " + removeMethodDecl + " {\n");
     sb.append(ind(2) + "assertNotNull(o);\n");
-    sb.append(ind(2) + ASTNode.listClass + "<" + ofTypeDecl() + "> list = " + getImplAttributeField() + ";\n");
+    sb.append(ind(2) + ASTNode.listInterface + "<" + ofTypeDecl() + "> list = " + getImplAttributeField() + ";\n");
     sb.append(ind(2) + "if (list != null && list.remove(o)) {\n");
     sb.append(ind(3) + "set" + getImplAttributeName() + "(list);\n");
-    sb.append(ind(3) + "if (o." + otherSide().getImplAttributeField() + " == this) {\n");
-    sb.append(ind(4) + "o.set" + otherSide().getImplAttributeName() + "(null);\n");
+    sb.append(ind(3) + "if (o." + opposite().getImplAttributeField() + " == this) {\n");
+    sb.append(ind(4) + "o.set" + opposite().getImplAttributeName() + "(null);\n");
     sb.append(ind(3) + "}\n");
     sb.append(ind(2) + "}\n");
     sb.append(ind(1) + "}\n");
   }
 
-  public void RelationComponent.generateBiOneMany(StringBuilder sb, boolean isOpt) {
-    // Get
-    generateGetOne(sb);
-
+  public void RelationComponent.generateBiOneMany(StringBuilder sb) {
     // Set
-    sb.append(ind(1) + "public " + getTypeUse().decl() + " " + getTypeUse().decl() + ".set" + nameCapitalized()
-      + "(" + ofTypeDecl() + " o) {\n");
-    if (!isOpt) {
+    String setMethodDecl = getTypeUse().decl() + " " + getTypeUse().decl() + ".set" + nameCapitalized() + "(" + ofTypeDecl() + " o)";
+    sb.append(ind(1) + "public " + setMethodDecl + " {\n");
+    if (!isOpt()) {
       sb.append(ind(2) + "assertNotNull(o);\n");
     }
     sb.append(ind(2) + "if (" + getImplAttributeField() + " != null) {\n");
-    sb.append(ind(3) + ASTNode.listClass + "<" + getTypeUse().decl() + "> list2 = " + getImplAttributeField()
-      + "." + otherSide().getImplAttributeField() + ";\n");
+    sb.append(ind(3) + ASTNode.listInterface + "<" + getTypeUse().decl() + "> list2 = " + getImplAttributeField() + "." + opposite().getImplAttributeField() + ";\n");
     sb.append(ind(3) + "list2.remove(this);\n");
-    sb.append(ind(3) + getImplAttributeField() + "." + "set"
-      + otherSide().getImplAttributeName() + "(list2);\n");
+    sb.append(ind(3) + getImplAttributeField() + "." + "set" + opposite().getImplAttributeName() + "(list2);\n");
     sb.append(ind(2) + "}\n");
     sb.append(ind(2) + "set" + getImplAttributeName() + "(o);\n");
 
-    int ind = isOpt ? 3 : 2;
-    if (isOpt) {
+    int ind = isOpt() ? 3 : 2;
+    if (isOpt()) {
       sb.append(ind(2) + "if (o != null) {\n");
     }
-    sb.append(ind(ind) + ASTNode.listClass + "<" + getTypeUse().decl() + "> list = o."
-      + otherSide().getImplAttributeField() + ";\n");
+    sb.append(ind(ind) + ASTNode.listInterface + "<" + getTypeUse().decl() + "> list = o." + opposite().getImplAttributeField() + ";\n");
     sb.append(ind(ind) + "if (list == null) {\n");
     sb.append(ind(ind+1) + "list = new " + ASTNode.listClass + "<>();\n");
     sb.append(ind(ind) + "}\n");
     sb.append(ind(ind) + "list.add(this);\n");
-    sb.append(ind(ind) + "o.set" + otherSide().getImplAttributeName() + "(list);\n");
-    if (isOpt) {
+    sb.append(ind(ind) + "o.set" + opposite().getImplAttributeName() + "(list);\n");
+    if (isOpt()) {
       sb.append(ind(2) + "}\n");
     }
     sb.append(ind(2) + "return this;\n");
     sb.append(ind(1) + "}\n");
-
-    if (isOpt) {
-      generateExtraOptAPI(sb);
-    }
   }
 }
diff --git a/src/main/jastadd/backend/DirectedAPI.jadd b/src/main/jastadd/backend/DirectedAPI.jadd
index 000c47614ec48abbf110f18090a1d08ec55eb758..3cbc9f0985fec33ccd79b36eb6831af1a6b4fce7 100644
--- a/src/main/jastadd/backend/DirectedAPI.jadd
+++ b/src/main/jastadd/backend/DirectedAPI.jadd
@@ -1,28 +1,11 @@
 aspect BackendDirectedAPI {
-  public void RightDirection.generateAPI(StringBuilder sb) {
-    relation().getLeft().generateDirectedAPI(sb);
-  }
-  public void LeftDirection.generateAPI(StringBuilder sb) {
-    relation().getRight().generateDirectedAPI(sb);
-  }
 
-  public abstract void RelationComponent.generateDirectedAPI(StringBuilder sb);
-  public void OneRelationComponent.generateDirectedAPI(StringBuilder sb) {
-    generateDirectedZeroOneAPI(sb, false);
-  }
-  public void OptionalRelationComponent.generateDirectedAPI(StringBuilder sb) {
-    generateDirectedZeroOneAPI(sb, true);
-
-    generateExtraOptAPI(sb);
-  }
-  public void RelationComponent.generateDirectedZeroOneAPI(StringBuilder sb, boolean optional) {
-    // Get
-    generateGetOne(sb);
+  public void RelationComponent.generateDirectedZeroOneAPI(StringBuilder sb) {
+    String setMethodDecl = getTypeUse().decl() + " " + getTypeUse().decl() + ".set" + nameCapitalized() + "(" + ofTypeDecl() + " o)";
 
     // Set
-    sb.append(ind(1) + "public " + getTypeUse().decl() + " " + getTypeUse().decl());
-    sb.append(".set" + nameCapitalized() + "(" + ofTypeDecl() + " o) {\n");
-    if (!optional) {
+    sb.append(ind(1) + "public " + setMethodDecl + " {\n");
+    if (!isOpt()) {
       sb.append(ind(2) + "assertNotNull(o);\n");
     }
     sb.append(ind(2) + "set" + getImplAttributeName() + "(o);\n");
@@ -30,50 +13,49 @@ aspect BackendDirectedAPI {
     sb.append(ind(1) + "}\n");
   }
 
-  public void ManyRelationComponent.generateDirectedAPI(StringBuilder sb) {
+  public void RelationComponent.generateDirectedManyAPI(StringBuilder sb) {
+
     // Get
-    sb.append(ind(1) + "public java.util.List<" + ofTypeDecl() + "> " + getTypeUse().decl() + ".");
     if (useJastAddNames) {
+      String getMethodDecl1 = "java.util.List<" + ofTypeDecl() + "> " + getTypeUse().decl() + ".get" + nameCapitalized() + "s()";
+      String getMethodDecl2 = "java.util.List<" + ofTypeDecl() + "> " + getTypeUse().decl() + ".get" + nameCapitalized() + "List()";
       // getXs
-      sb.append("get" + nameCapitalized() + "s() {\n");
+      sb.append(ind(1) + "public " + getMethodDecl1 + " {\n");
       sb.append(ind(2) + "return get" + nameCapitalized() + "List();\n");
       sb.append(ind(1) + "}\n");
 
       // getXList
-      sb.append(ind(1) + "public java.util.List<" + ofTypeDecl() + "> " + getTypeUse().decl());
-      sb.append(".get" + nameCapitalized() + "List() {\n");
+      sb.append(ind(1) + "public " + getMethodDecl2 + " {\n");
     } else {
-      sb.append(name() + "() {\n");
+      String getMethodDecl = "java.util.List<" + ofTypeDecl() + "> " + getTypeUse().decl() + "." + name() + "()";
+      sb.append("public " + getMethodDecl + " {\n");
     }
-    sb.append(ind(2) + ASTNode.listClass + "<" + ofTypeDecl() + "> l = get" + getImplAttributeName() + "();\n");
+    sb.append(ind(2) + ASTNode.listInterface + "<" + ofTypeDecl() + "> l = get" + getImplAttributeName() + "();\n");
     // resolve the entire list
     if (resolverHelper | serializer) {
       sb.append(ind(2) + "if (l != null) {\n");
-        sb.append(ind(3) + "boolean changed = false;\n");
-        sb.append(ind(3) + "for (int i = 0; i < l.size(); i++) {\n");
-          sb.append(ind(4) + ofTypeDecl() + " element = l.get(i);\n");
-          sb.append(ind(4) + "if (element.is$Unresolved()) {\n");
-            sb.append(ind(5) + "changed = true;\n");
-            sb.append(ind(5) + ofTypeDecl() + " resolvedElement = resolve" + nameCapitalized() + "ByToken(element.as$Unresolved().getUnresolved$Token(), i);\n");
-            sb.append(ind(5) + "l.set(i, resolvedElement);\n");
-          sb.append(ind(4) + "}\n");
-        sb.append(ind(3) + "}\n");
-        sb.append(ind(3) + "if (changed) {\n");
-          sb.append(ind(4) + "set" + getImplAttributeName() + "(l);\n");
-        sb.append(ind(3) + "}\n");
+      sb.append(ind(3) + "boolean changed = false;\n");
+      sb.append(ind(3) + "for (int i = 0; i < l.size(); i++) {\n");
+      sb.append(ind(4) + ofTypeDecl() + " element = l.get(i);\n");
+      sb.append(ind(4) + "if (element." + isUnresolvedMethod + "()) {\n");
+      sb.append(ind(5) + "changed = true;\n");
+      sb.append(ind(5) + ofTypeDecl() + " resolvedElement = " + resolvePrefix + "" + nameCapitalized() + "" + resolvePostfix + "(element." + asUnresolvedMethod + "().get" + unresolvedTokenMethod + "(), i);\n");
+      sb.append(ind(5) + "l.set(i, resolvedElement);\n");
+      sb.append(ind(4) + "}\n");
+      sb.append(ind(3) + "}\n");
+      sb.append(ind(3) + "if (changed) {\n");
+      sb.append(ind(4) + "set" + getImplAttributeName() + "(l);\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(2) + "return l != null ? java.util.Collections.unmodifiableList(l) : java.util.Collections.emptyList();\n");
     sb.append(ind(1) + "}\n");
 
     // Add
-    sb.append(ind(1) + "public void " + getTypeUse().decl() + ".add");
-    if (!useJastAddNames) {
-      sb.append("To");
-    }
-    sb.append(nameCapitalized() + "(" + ofTypeDecl() + " o) {\n");
+    String addMethodDecl1 = "void " + getTypeUse().decl() + ".add" + (useJastAddNames ? "" : "To") + nameCapitalized() + "(" + ofTypeDecl() + " o)";
+    sb.append(ind(1) + "public " + addMethodDecl1 + " {\n");
     sb.append(ind(2) + "assertNotNull(o);\n");
-    sb.append(ind(2) + ASTNode.listClass + "<" + ofTypeDecl() + "> list = " + getImplAttributeField() + ";\n");
+    sb.append(ind(2) + ASTNode.listInterface + "<" + ofTypeDecl() + "> list = " + getImplAttributeField() + ";\n");
     sb.append(ind(2) + "if (list == null) {\n");
     sb.append(ind(3) + "list = new " + ASTNode.listClass + "<>();\n");
     sb.append(ind(2) + "}\n");
@@ -82,13 +64,10 @@ aspect BackendDirectedAPI {
     sb.append(ind(1) + "}\n");
 
     // Insert / add at specific position
-    sb.append(ind(1) + "public void " + getTypeUse().decl() + ".add");
-    if (!useJastAddNames) {
-      sb.append("To");
-    }
-    sb.append(nameCapitalized() + "(int index, " + ofTypeDecl() + " o) {\n");
+    String addMethodDecl2 = "void " + getTypeUse().decl() + ".add" + (useJastAddNames ? "" : "To") + nameCapitalized() + "(int index, " + ofTypeDecl() + " o)";
+    sb.append(ind(1) + "public " + addMethodDecl2 + " {\n");
     sb.append(ind(2) + "assertNotNull(o);\n");
-    sb.append(ind(2) + ASTNode.listClass + "<" + ofTypeDecl() + "> list = " + getImplAttributeField() + ";\n");
+    sb.append(ind(2) + ASTNode.listInterface + "<" + ofTypeDecl() + "> list = " + getImplAttributeField() + ";\n");
     sb.append(ind(2) + "if (list == null) {\n");
     sb.append(ind(3) + "list = new " + ASTNode.listClass + "<>();\n");
     sb.append(ind(2) + "}\n");
@@ -97,57 +76,13 @@ aspect BackendDirectedAPI {
     sb.append(ind(1) + "}\n");
 
     // Remove
-    sb.append(ind(1) + "public void " + getTypeUse().decl() + ".remove");
-    if (!useJastAddNames) {
-      sb.append("From");
-    }
-    sb.append(nameCapitalized() + "(" + ofTypeDecl() + " o) {\n");
+    String removeMethodDecl = "void " + getTypeUse().decl() + ".remove" + (useJastAddNames ? "" : "From") + nameCapitalized() + "(" + ofTypeDecl() + " o)";
+    sb.append(ind(1) + "public " + removeMethodDecl + " {\n");
     sb.append(ind(2) + "assertNotNull(o);\n");
-    sb.append(ind(2) + ASTNode.listClass + "<" + ofTypeDecl() + "> list = " + getImplAttributeField() + ";\n");
+    sb.append(ind(2) + ASTNode.listInterface + "<" + ofTypeDecl() + "> list = " + getImplAttributeField() + ";\n");
     sb.append(ind(2) + "if (list != null && list.remove(o)) {\n");
     sb.append(ind(3) + "set" + getImplAttributeName() + "(list);\n");
     sb.append(ind(2) + "}\n");
     sb.append(ind(1) + "}\n");
   }
-
-  public void RelationComponent.generateGetOne(StringBuilder sb) {
-    sb.append(ind(1) + "public " + ofTypeDecl() + " " + getTypeUse().decl() + ".");
-    if (useJastAddNames) {
-      sb.append("get" + nameCapitalized());
-    } else {
-      sb.append(name());
-    }
-    sb.append("() {\n");
-    if (resolverHelper | serializer) {
-      sb.append(ind(2) + "if (" + getImplAttributeField() + " != null && " + getImplAttributeField() + ".is$Unresolved()) {\n");
-        sb.append(ind(3) + "if (" + getImplAttributeField() + ".as$Unresolved().getUnresolved$ResolveOpposite()) {\n");
-          sb.append(ind(4) + "set" + nameCapitalized() + "(resolve" + nameCapitalized() + "ByToken(" + getImplAttributeField() + ".as$Unresolved().getUnresolved$Token()));\n");
-        sb.append(ind(3) + "} else {\n");
-          sb.append(ind(4) + "set" + getImplAttributeName() + "(resolve" + nameCapitalized() + "ByToken(" + getImplAttributeField() + ".as$Unresolved().getUnresolved$Token()));\n");
-        sb.append(ind(3) + "}\n");
-      sb.append(ind(2) + "}\n");
-    }
-    sb.append(ind(2) + "return get" + getImplAttributeName() + "();\n");
-    sb.append(ind(1) + "}\n");
-  }
-
-  public void RelationComponent.generateExtraOptAPI(StringBuilder sb) {
-    // has
-    sb.append(ind(1) + "public boolean " + getTypeUse().decl());
-    sb.append(".has" + nameCapitalized() + "() {\n");
-    sb.append(ind(2) + "return ");
-    if (useJastAddNames) {
-      sb.append("get" + nameCapitalized());
-    } else {
-      sb.append(name());
-    }
-    sb.append("() != null;\n");
-    sb.append(ind(1) + "}\n");
-
-    // clear
-    sb.append(ind(1) + "public void " + getTypeUse().decl());
-    sb.append(".clear" + nameCapitalized() + "() {\n");
-    sb.append(ind(2) + "set" + nameCapitalized() + "(null);\n");
-    sb.append(ind(1) + "}\n");
-  }
 }
diff --git a/src/main/jastadd/backend/LowerBoundCheck.jadd b/src/main/jastadd/backend/LowerBoundCheck.jadd
index e7690cacca84527435dc09e7e6a1ff6991089795..12c57b676dccb4b706c2e0b694407f3844a26479 100644
--- a/src/main/jastadd/backend/LowerBoundCheck.jadd
+++ b/src/main/jastadd/backend/LowerBoundCheck.jadd
@@ -6,7 +6,7 @@ aspect LowerBoundCheck {
 
     sb.append(ind(1) + "public java.util.List<Pair<ASTNode, String>> "
       + "ASTNode.getLowerBoundsViolations() {\n");
-    sb.append(ind(2) + "ArrayList<Pair<ASTNode, String>> list = new ArrayList<>();\n");
+    sb.append(ind(2) + "" + ASTNode.listInterface + "<Pair<ASTNode, String>> list = new " + ASTNode.listClass + "<>();\n");
     sb.append(ind(2) + "computeLowerBoundsViolations(list);\n");
     sb.append(ind(2) + "return list;\n");
     sb.append(ind(1) + "}\n");
diff --git a/src/main/jastadd/backend/NameResolution.jadd b/src/main/jastadd/backend/NameResolution.jadd
index 1d373e9e4b4c224bf5c65227182e0c2493197737..9b47dc1cee3a14f4ad114c8028b0f446392ad064 100644
--- a/src/main/jastadd/backend/NameResolution.jadd
+++ b/src/main/jastadd/backend/NameResolution.jadd
@@ -1,5 +1,61 @@
 aspect NameResolutionHelper {
 
+  protected static final String ASTNode.unresolvedPrefix = "Unresolved$";
+  protected static final String ASTNode.isUnresolvedMethod = "is$Unresolved";
+  protected static final String ASTNode.asUnresolvedMethod = "as$Unresolved";
+  protected static final String ASTNode.unresolvedTokenMethod = unresolvedPrefix + "Token";
+  protected static final String ASTNode.unresolvedResolveOppositeMethod = unresolvedPrefix + "ResolveOpposite";
+  protected static final String ASTNode.createReferenceMethod = "createReference";
+  protected static final String ASTNode.createRefMethod = "createRef";
+  protected static final String ASTNode.createRefDirectionMethod = "createRefDirection";
+  protected static final String ASTNode.resolvePrefix = "resolve";
+  protected static final String ASTNode.globallyResolvePrefix = "globallyResolve";
+  protected static final String ASTNode.resolvePostfix = "ByToken";
+  protected static final String ASTNode.resolveAllMethod = "resolveAll";
+  protected static final String ASTNode.treeResolveAllMethod = "treeResolveAll";
+  protected static final String ASTNode.unresolvedNodeInterface = unresolvedPrefix + "Node$Interface";
+
+  public void TypeDecl.generateUnresolvedClass(StringBuilder sb) {
+    if (getAbstract()) {
+      sb.append(ind(1) + "abstract ");
+    } else {
+      sb.append(ind(1));
+    }
+    sb.append("class " + unresolvedPrefix + getID() + " extends " + getID() + "  implements " + unresolvedNodeInterface + " {\n");
+
+    sb.append(ind(2) + "private String unresolved$Token;\n");
+    sb.append(ind(2) + "public String get" + unresolvedTokenMethod + "() {\n");
+    sb.append(ind(3) + "return unresolved$Token;\n");
+    sb.append(ind(2) + "}\n");
+    sb.append(ind(2) + "void set" + unresolvedTokenMethod + "(String token) {\n");
+    sb.append(ind(3) + "this.unresolved$Token = token;\n");
+    sb.append(ind(2) + "}\n");
+
+    sb.append(ind(2) + "private boolean unresolved$ResolveOpposite;\n");
+    sb.append(ind(2) + "public boolean get" + unresolvedResolveOppositeMethod + "() {\n");
+    sb.append(ind(3) + "return unresolved$ResolveOpposite;\n");
+    sb.append(ind(2) + "}\n");
+    sb.append(ind(2) + "void set" + unresolvedResolveOppositeMethod + "(boolean resolveOpposite) {\n");
+    sb.append(ind(3) + "this.unresolved$ResolveOpposite = resolveOpposite;\n");
+    sb.append(ind(2) + "}\n");
+
+    sb.append(ind(1) + "}\n");
+
+    sb.append(ind(1) + unresolvedNodeInterface + " " + getID() + "." + asUnresolvedMethod + "() {\n");
+    sb.append(ind(2) + "return null;\n");
+    sb.append(ind(1) + "}\n");
+    sb.append(ind(1) + unresolvedNodeInterface + " " + unresolvedPrefix + getID() + "." + asUnresolvedMethod + "() {\n");
+    sb.append(ind(2) + "return this;\n");
+    sb.append(ind(1) + "}\n");
+
+    sb.append(ind(1) + "boolean " + getID() + "." + isUnresolvedMethod + "() {\n");
+    sb.append(ind(2) + "return false;\n");
+    sb.append(ind(1) + "}\n");
+    sb.append(ind(1) + "boolean " + unresolvedPrefix + getID() + "." + isUnresolvedMethod + "() {\n");
+    sb.append(ind(2) + "return true;\n");
+    sb.append(ind(1) + "}\n");
+  }
+
   public String Program.generateRewriteToSuperTypeStub() {
     StringBuilder sb = new StringBuilder();
     generateRewriteToSuperTypeStub(sb);
@@ -48,8 +104,8 @@ aspect NameResolutionHelper {
 
   public void Program.generateGenericRefCreation(StringBuilder sb) {
     sb.append(ind(1) + "// generic reference creation\n");
-    sb.append(ind(1) + "syn String ASTNode.createReference();\n");
-    sb.append(ind(1) + "eq ASTNode.createReference() {\n");
+    sb.append(ind(1) + "syn String ASTNode." + createReferenceMethod + "();\n");
+    sb.append(ind(1) + "eq ASTNode." + createReferenceMethod + "() {\n");
     sb.append(ind(2) + "throw new RuntimeException(\"Generic reference creation not implemented.\");\n");
     sb.append(ind(1) + "}\n");
   }
@@ -74,17 +130,17 @@ aspect NameResolutionHelper {
 
   public void RelationComponent.generateContextDependentRefCreation(StringBuilder sb) {
     sb.append(ind(1) + "// context-dependent reference creation\n");
-    sb.append(ind(1) + "syn String " + getTypeUse().decl() + ".createRefTo" + nameCapitalized() + "(" + ofTypeDecl() + " target) {\n");
+    sb.append(ind(1) + "syn String " + getTypeUse().decl() + "." + createRefMethod + "To" + nameCapitalized() + "(" + ofTypeDecl() + " target) {\n");
     sb.append(ind(2) + "// default to context-independent reference creation\n");
-    sb.append(ind(2) + "return target.createReference();\n");
+    sb.append(ind(2) + "return target." + createReferenceMethod + "();\n");
     sb.append(ind(1) + "}\n");
   }
 
   public void TypeDecl.generateContextIndependentRefCreation(StringBuilder sb) {
     sb.append(ind(1) + "// context-independent reference creation\n");
-    sb.append(ind(1) + "eq " + getID() + ".createReference() {\n");
+    sb.append(ind(1) + "eq " + getID() + "." + createReferenceMethod + "() {\n");
     sb.append(ind(2) + "// default to generic reference creation\n");
-    sb.append(ind(2) + "return super.createReference();\n");
+    sb.append(ind(2) + "return super." + createReferenceMethod + "();\n");
     sb.append(ind(1) + "}\n");
   }
 
@@ -110,9 +166,9 @@ aspect NameResolutionHelper {
 
     sb.append("aspect RefResolverHelpers {\n\n");
 
-    sb.append(ind(1) + "interface Unresolved$Node {\n");
-    sb.append(ind(2) + "String getUnresolved$Token();\n");
-    sb.append(ind(2) + "boolean getUnresolved$ResolveOpposite();\n");
+    sb.append(ind(1) + "interface " + unresolvedNodeInterface + " {\n");
+    sb.append(ind(2) + "String get" + unresolvedTokenMethod + "();\n");
+    sb.append(ind(2) + "boolean get" + unresolvedResolveOppositeMethod + "();\n");
     sb.append(ind(1) + "}\n\n");
 
     for (TypeDecl td: getTypeDecls()) {
@@ -131,30 +187,30 @@ aspect NameResolutionHelper {
       throw new RuntimeException("unable to find instantiable subtype for " + getID());
     }
 
-    sb.append(ind(1) + "public static " + getID() + " " + getID() + ".createRef(String ref) {\n");
-      sb.append(ind(2) + "Unresolved$" + instantiableSubType.getID() + " unresolvedNode = new Unresolved$" + instantiableSubType.getID() + "();\n");
-      sb.append(ind(2) + "unresolvedNode.setUnresolved$Token(ref);\n");
-      sb.append(ind(2) + "unresolvedNode.setUnresolved$ResolveOpposite(true);\n");
+    sb.append(ind(1) + "public static " + getID() + " " + getID() + "." + createRefMethod + "(String ref) {\n");
+      sb.append(ind(2) + unresolvedPrefix + instantiableSubType.getID() + " unresolvedNode = new " + unresolvedPrefix + instantiableSubType.getID() + "();\n");
+      sb.append(ind(2) + "unresolvedNode.set" + unresolvedPrefix + "Token(ref);\n");
+      sb.append(ind(2) + "unresolvedNode.set" + unresolvedPrefix + "ResolveOpposite(true);\n");
       sb.append(ind(2) + "return unresolvedNode;\n");
     sb.append(ind(1) + "}\n");
 
-    sb.append(ind(1) + "public static " + getID() + " " + getID() + ".createRefDirection(String ref) {\n");
-      sb.append(ind(2) + "Unresolved$" + instantiableSubType.getID() + " unresolvedNode = new Unresolved$" + instantiableSubType.getID() + "();\n");
-      sb.append(ind(2) + "unresolvedNode.setUnresolved$Token(ref);\n");
-      sb.append(ind(2) + "unresolvedNode.setUnresolved$ResolveOpposite(false);\n");
+    sb.append(ind(1) + "public static " + getID() + " " + getID() + "." + createRefDirectionMethod + "(String ref) {\n");
+      sb.append(ind(2) + unresolvedPrefix + instantiableSubType.getID() + " unresolvedNode = new " + unresolvedPrefix + instantiableSubType.getID() + "();\n");
+      sb.append(ind(2) + "unresolvedNode.set" + unresolvedPrefix + "Token(ref);\n");
+      sb.append(ind(2) + "unresolvedNode.set" + unresolvedPrefix + "ResolveOpposite(false);\n");
       sb.append(ind(2) + "return unresolvedNode;\n");
     sb.append(ind(1) + "}\n");
   }
 
   public void TypeDecl.generateContextIndependentNameResolution(StringBuilder sb) {
     sb.append(ind(1) + "// context-independent name resolution\n");
-    sb.append(ind(1) + "uncache ASTNode.globallyResolve" + getID() + "ByToken(String id);\n");
-    sb.append(ind(1) + "syn " + getID() + " ASTNode.globallyResolve" + getID() + "ByToken(String id) {\n");
+    sb.append(ind(1) + "uncache ASTNode." + globallyResolvePrefix + getID() + resolvePostfix + "(String id);\n");
+    sb.append(ind(1) + "syn " + getID() + " ASTNode." + globallyResolvePrefix + getID() + resolvePostfix + "(String id) {\n");
     if (serializer && !manualReferences) {
       if (jsonPointer) {
         sb.append(ind(2) + "return (" + getID() + ") resolveJsonPointer(id);\n");
       } else {
-        sb.append(ind(2) + "return (" + getID() + ") globallyResolveASTNodeByUID(id);\n");
+        sb.append(ind(2) + "return (" + getID() + ") " + globallyResolvePrefix + "ASTNodeByUID(id);\n");
       }
     } else {
       sb.append(ind(2) + "// perform context independent name resolution here using the id\n");
@@ -193,56 +249,56 @@ aspect NameResolutionHelper {
   public void ManyRelationComponent.generateContextDependentNameResolution(StringBuilder sb) {
 
     if (serializer && !resolverHelper) {
-      sb.append(ind(1) + ofTypeDecl() + " " + getTypeUse().decl() + ".resolve" + nameCapitalized() + "ByToken(String id, int position) {\n");
-        sb.append(ind(2) + "return (" + ofTypeDecl() + ") globallyResolveASTNodeByUID(id);\n");
+      sb.append(ind(1) + ofTypeDecl() + " " + getTypeUse().decl() + ".resolve" + nameCapitalized() + resolvePostfix + "(String id, int position) {\n");
+        sb.append(ind(2) + "return (" + ofTypeDecl() + ") " + globallyResolvePrefix + "ASTNodeByUID(id);\n");
       sb.append(ind(1) + "}\n");
     } else {
       sb.append(ind(1) + "// context-dependent name resolution\n");
-      sb.append(ind(1) + "uncache " + getTypeUse().decl() + ".resolve" + nameCapitalized() + "ByToken(String id, int position);\n");
-      sb.append(ind(1) + "syn " + ofTypeDecl() + " " + getTypeUse().decl() + ".resolve" + nameCapitalized() + "ByToken(String id, int position) {\n");
+      sb.append(ind(1) + "uncache " + getTypeUse().decl() + ".resolve" + nameCapitalized() + resolvePostfix + "(String id, int position);\n");
+      sb.append(ind(1) + "syn " + ofTypeDecl() + " " + getTypeUse().decl() + ".resolve" + nameCapitalized() + resolvePostfix + "(String id, int position) {\n");
         sb.append(ind(2) + "// default to context-independent name resolution\n");
-        sb.append(ind(2) + "return globallyResolve" + ofTypeDecl() + "ByToken(id);\n");
+        sb.append(ind(2) + "return " + globallyResolvePrefix + ofTypeDecl() + resolvePostfix + "(id);\n");
       sb.append(ind(1) + "}\n");
     }
   }
 
   public void RelationComponent.generateDirectedContextDependentNameResolution(StringBuilder sb) {
     if (serializer && !resolverHelper) {
-      sb.append(ind(1) + ofTypeDecl() + " " + getTypeUse().decl() + ".resolve" + nameCapitalized() + "ByToken(String id) {\n");
-        sb.append(ind(2) + "return (" + ofTypeDecl() + ") globallyResolveASTNodeByUID(id);\n");
+      sb.append(ind(1) + ofTypeDecl() + " " + getTypeUse().decl() + ".resolve" + nameCapitalized() + resolvePostfix + "(String id) {\n");
+        sb.append(ind(2) + "return (" + ofTypeDecl() + ") " + globallyResolvePrefix + "ASTNodeByUID(id);\n");
       sb.append(ind(1) + "}\n");
     } else {
       sb.append(ind(1) + "// context-dependent name resolution\n");
-      sb.append(ind(1) + "uncache " + getTypeUse().decl() + ".resolve" + nameCapitalized() + "ByToken(String id);\n");
-      sb.append(ind(1) + "syn " + ofTypeDecl() + " " + getTypeUse().decl() + ".resolve" + nameCapitalized() + "ByToken(String id) {\n");
+      sb.append(ind(1) + "uncache " + getTypeUse().decl() + ".resolve" + nameCapitalized() + resolvePostfix + "(String id);\n");
+      sb.append(ind(1) + "syn " + ofTypeDecl() + " " + getTypeUse().decl() + ".resolve" + nameCapitalized() + resolvePostfix + "(String id) {\n");
         sb.append(ind(2) + "// default to context-independent name resolution\n");
-        sb.append(ind(2) + "return globallyResolve" + ofTypeDecl() + "ByToken(id);\n");
+        sb.append(ind(2) + "return " + globallyResolvePrefix + ofTypeDecl() + resolvePostfix + "(id);\n");
       sb.append(ind(1) + "}\n");
     }
   }
 
   public void Program.resolveAll(StringBuilder sb) {
     sb.append(ind(1) + "// enforce resolving of all non-containment relations of the current non-terminal\n");
-    sb.append(ind(1) + "public void ASTNode.resolveAll() {\n");
+    sb.append(ind(1) + "public void ASTNode." + resolveAllMethod + "() {\n");
     sb.append(ind(1) + "}\n\n");
 
     sb.append(ind(1) + "// enforce resolving in the entire subtree\n");
-    sb.append(ind(1) + "public void ASTNode.treeResolveAll() {\n");
+    sb.append(ind(1) + "public void ASTNode." + treeResolveAllMethod + "() {\n");
       sb.append(ind(2) + "if (children != null) {\n");
         sb.append(ind(3) + "for (int i = 0; i < numChildren; ++i) {\n");
           sb.append(ind(4) + "ASTNode child = children[i];\n");
           sb.append(ind(4) + "if (child != null) {\n");
-            sb.append(ind(5) + "child.treeResolveAll();\n");
+            sb.append(ind(5) + "child." + treeResolveAllMethod + "();\n");
           sb.append(ind(4) + "}\n");
         sb.append(ind(3) + "}\n");
       sb.append(ind(2) + "}\n");
-      sb.append(ind(2) + "resolveAll();\n");
+      sb.append(ind(2) + resolveAllMethod + "();\n");
     sb.append(ind(1) + "}\n");
   }
 
   public void TypeDecl.resolveAll(StringBuilder sb) {
     sb.append(ind(1) + "// enforce resolving of all non-containment relations of the current non-terminal\n");
-    sb.append(ind(1) + "public void " + getID() + ".resolveAll() {\n");
+    sb.append(ind(1) + "public void " + getID() + "." + resolveAllMethod + "() {\n");
     for (RelationComponent relationComponent : relationComponents()) {
       sb.append(ind(2));
       if (useJastAddNames) {
@@ -250,9 +306,9 @@ aspect NameResolutionHelper {
       } else {
         sb.append(relationComponent.name());
       }
-      sb.append(relationComponent.isMany() && useJastAddNames ? "List" : "").append("();\n");
+      sb.append(relationComponent.isList() && useJastAddNames ? "List" : "").append("();\n");
     }
-      sb.append(ind(2) + "super.resolveAll();\n");
+      sb.append(ind(2) + "super." + resolveAllMethod + "();\n");
     sb.append(ind(1) + "}\n");
   }
 }
diff --git a/src/main/jastadd/backend/PrettyPrinting.jadd b/src/main/jastadd/backend/PrettyPrinting.jadd
index 49d33b9997f4840e54afe7de2f7dd4ba786bb45d..63c5344546c84ce11800d0171d0bb236b4da9c32 100644
--- a/src/main/jastadd/backend/PrettyPrinting.jadd
+++ b/src/main/jastadd/backend/PrettyPrinting.jadd
@@ -33,10 +33,10 @@ aspect PrettyPrinting {
 
 aspect Utils {
   public String ASTNode.ind(int n) {
-    String s = "";
+    StringBuilder s = new StringBuilder();
     for (int i = 0; i < n; i++) {
-      s += "  ";
+      s.append("  ");
     }
-    return s;
+    return s.toString();
   }
 }
diff --git a/src/main/jastadd/backend/Serializer.jadd b/src/main/jastadd/backend/Serializer.jadd
index 90ba24e30df96ba67eb8d3bec543347648d58879..21db37f69f93f2c852528ab334245ce5f3e3d9e0 100644
--- a/src/main/jastadd/backend/Serializer.jadd
+++ b/src/main/jastadd/backend/Serializer.jadd
@@ -169,10 +169,10 @@ aspect Serializer {
       case "String":
         sb.append(ind(indent) + "g.writeStringField(\"" + getID() + "\", get" + getID() + "());\n");
         break;
-      case "Instant":
+      case "java.time.Instant":
         sb.append(ind(indent) + "g.writeStringField(\"" + getID() + "\", get" + getID() + "().toString());\n");
         break;
-      case "Period":
+      case "java.time.Period":
         sb.append(ind(indent) + "g.writeStringField(\"" + getID() + "\", get" + getID() + "().toString());\n");
         break;
       default:
@@ -209,7 +209,7 @@ aspect Serializer {
       if (jsonPointer) {
         sb.append(ind(indent) + "g.writeStringField(\"" + getID() + "\", get" + getID() + "().jsonPointer());\n");
       } else if (manualReferences) {
-        sb.append(ind(indent) + "g.writeStringField(\"" + getID() + "\", createRefTo" + getID() + "(" + getID() + "()));\n");
+        sb.append(ind(indent) + "g.writeStringField(\"" + getID() + "\", " + createRefMethod + "To" + getID() + "(" + getID() + "()));\n");
       } else {
         sb.append(ind(indent) + "g.writeStringField(\"" + getID() + "\", get" + getID() + "().unique$Id());\n");
       }
@@ -217,7 +217,7 @@ aspect Serializer {
       if (jsonPointer) {
         sb.append(ind(indent) + "g.writeStringField(\"" + getID() + "\", " + getID() + "().jsonPointer());\n");
       } else if (manualReferences) {
-        sb.append(ind(indent) + "g.writeStringField(\"" + getID() + "\", createRefTo" + getID() + "(" + getID() + "()));\n");
+        sb.append(ind(indent) + "g.writeStringField(\"" + getID() + "\", " + createRefMethod + "To" + getID() + "(" + getID() + "()));\n");
       } else {
         sb.append(ind(indent) + "g.writeStringField(\"" + getID() + "\", " + getID() + "().unique$Id());\n");
       }
@@ -238,7 +238,7 @@ aspect Serializer {
       if (jsonPointer) {
         sb.append(ind(indent + 1) + "g.writeStringField(\"" + getID() + "\", " + getID() + "().jsonPointer());\n");
       } else if (manualReferences) {
-        sb.append(ind(indent + 1) + "g.writeStringField(\"" + getID() + "\", " + "createRefTo" + getID() + "(" + getID()  + "()));\n");
+        sb.append(ind(indent + 1) + "g.writeStringField(\"" + getID() + "\", " + "" + createRefMethod + "To" + getID() + "(" + getID()  + "()));\n");
       } else {
         sb.append(ind(indent + 1) + "g.writeStringField(\"" + getID() + "\", " + getID() + "().unique$Id());\n");
       }
@@ -256,7 +256,7 @@ aspect Serializer {
     if (jsonPointer) {
       sb.append(ind(indent + 1) + "g.writeString(child.jsonPointer());\n");
     }  else if (manualReferences) {
-      sb.append(ind(indent + 1) + "g.writeString(createRefTo" + getID() + "(child));\n");
+      sb.append(ind(indent + 1) + "g.writeString(" + createRefMethod + "To" + getID() + "(child));\n");
     }else {
       sb.append(ind(indent + 1) + "g.writeString(child.unique$Id());\n");
     }
@@ -390,11 +390,11 @@ aspect Serializer {
       case "String":
         sb.append(ind(indent) + "element.set" + getID() + "(children.get(\"" + getID() + "\").asText());\n");
         break;
-      case "Instant":
-        sb.append(ind(indent) + "element.set" + getID() + "(Instant.parse(children.get(\"" + getID() + "\").asText()));\n");
+      case "java.time.Instant":
+        sb.append(ind(indent) + "element.set" + getID() + "(java.time.Instant.parse(children.get(\"" + getID() + "\").asText()));\n");
         break;
-      case "Period":
-        sb.append(ind(indent) + "element.set" + getID() + "(Period.parse(children.get(\"" + getID() + "\").asText()));\n");
+      case "java.time.Period":
+        sb.append(ind(indent) + "element.set" + getID() + "(java.time.Period.parse(children.get(\"" + getID() + "\").asText()));\n");
         break;
       default:
         // assume that the type is an enum. there is no way of checking this here
@@ -565,11 +565,11 @@ aspect Serializer {
     sb.append(ind(1) + "  return null;\n");
     sb.append(ind(1) + "}\n");
     sb.append("\n");
-    sb.append(ind(1) + "ASTNode ASTNode.globallyResolveASTNodeByUID(String uid) {\n");
+    sb.append(ind(1) + "ASTNode ASTNode." + globallyResolvePrefix + "ASTNodeByUID(String uid) {\n");
     sb.append(ind(2) + "if (getParent() == null) {\n");
     sb.append(ind(3) + "return uid$Map().get(uid).get();\n");
     sb.append(ind(2) + "} else {\n");
-    sb.append(ind(3) + "return getParent().globallyResolveASTNodeByUID(uid);\n");
+    sb.append(ind(3) + "return getParent()." + globallyResolvePrefix + "ASTNodeByUID(uid);\n");
     sb.append(ind(2) + "}\n");
     sb.append(ind(1) + "}\n");
     sb.append("\n");
@@ -616,18 +616,18 @@ aspect Serializer {
   }
 
   public void OneRelationComponent.deserialize(StringBuilder sb, int indent) {
-    sb.append(ind(indent) + "element.set" + nameCapitalized() + "(" + ofTypeDecl().getID() + ".createRefDirection(relations.get(\"" + getID() + "\").asText()));\n");
+    sb.append(ind(indent) + "element.set" + nameCapitalized() + "(" + ofTypeDecl().getID() + "." + createRefMethod + "Direction(relations.get(\"" + getID() + "\").asText()));\n");
     sb.append(ind(indent - 1) + "} else {\n");
     sb.append(ind(indent) + "throw new DeserializationException(\"deserializer of missing mandatory relation child " + getID() + "\");\n");
   }
 
   public void OptionalRelationComponent.deserialize(StringBuilder sb, int indent) {
-    sb.append(ind(indent) + "element.set" + nameCapitalized() + "(" + ofTypeDecl().getID() + ".createRefDirection(relations.get(\"" + getID() + "\").asText()));\n");
+    sb.append(ind(indent) + "element.set" + nameCapitalized() + "(" + ofTypeDecl().getID() + "." + createRefMethod + "Direction(relations.get(\"" + getID() + "\").asText()));\n");
   }
 
   public void ManyRelationComponent.deserialize(StringBuilder sb, int indent) {
     sb.append(ind(indent) + "for (" + jsonNodeType + " child : relations.get(\"" + getID() + "\")) {\n");
-    sb.append(ind(indent + 1) + "element.add" + (useJastAddNames?"":"To") + nameCapitalized() + "(" + ofTypeDecl().getID() + ".createRefDirection(child.asText()));\n");
+    sb.append(ind(indent + 1) + "element.add" + (useJastAddNames?"":"To") + nameCapitalized() + "(" + ofTypeDecl().getID() + "." + createRefMethod + "Direction(child.asText()));\n");
     sb.append(ind(indent) + "}\n");
   }
 }
diff --git a/src/test/jastadd/Utils.jadd b/src/test/jastadd/Utils.jadd
index e0e012f21e6b83feffb271ea33b71d83a77e5843..bc836ea2e12f6cbd4e1ff7195039f9e1124c2d07 100644
--- a/src/test/jastadd/Utils.jadd
+++ b/src/test/jastadd/Utils.jadd
@@ -10,4 +10,4 @@ aspect Utils {
   public enum Weekday {
     MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
   }
-}
\ No newline at end of file
+}
diff --git a/src/test/jastadd/relations/Relations.jrag b/src/test/jastadd/relations/Relations.jrag
new file mode 100644
index 0000000000000000000000000000000000000000..69766fef8f5b5374fce094db182a99ef871c95e5
--- /dev/null
+++ b/src/test/jastadd/relations/Relations.jrag
@@ -0,0 +1,38 @@
+import java.util.ArrayList;
+
+aspect NTA {
+
+  // E ::= ... /<NT1>/ ... ;
+  syn String E.getNT1() = "";
+
+  // E ::= ... /<NT2:String>/ ... ;
+  syn String E.getNT2() = "";
+
+  // E ::= ... /<NT3:boolean>/ ... ;
+  syn boolean E.getNT3() = false;
+
+  // E ::= ... /<NT4:int>/ ... ;
+  syn int E.getNT4() = 1;
+
+  // E ::= ... /<NT5:float>/ ... ;
+  syn float E.getNT5() = 1.0f;
+
+  // E ::= ... /<NT6:double>/ ... ;
+  syn double E.getNT6() = 1.0d;
+
+  // E ::= ... /<NT7:long>/ ... ;
+  syn long E.getNT7() = 1l;
+
+  // E ::= ... /[NT8:A]/ ... ;
+  syn Opt<A> E.getNT8Opt() = new Opt<A>();
+
+  // E ::= ... /[A]/ ... ;
+  syn Opt<A> E.getAOpt() = new Opt<A>();
+
+  // E ::= ... /NT10:A*/ ... ;
+  syn List<A> E.getNT10List() = new List<A>();
+
+  // E ::= ... /B*/ ... ;
+  syn List<B> E.getBList() = new List<B>();
+
+}
diff --git a/src/test/jastadd/relations/Relations.relast b/src/test/jastadd/relations/Relations.relast
index 12f132e5e58968baff38120d79fde1090151acf8..b519a442e963f8fbdd184cec3b006a67e8228f68 100644
--- a/src/test/jastadd/relations/Relations.relast
+++ b/src/test/jastadd/relations/Relations.relast
@@ -51,9 +51,10 @@ D ::= SingleA:A ListOfA:A* [OptionalA:A] /NTAA:A/ ;
 // production with tokens, nonterminal-tokens, multi-line
 E ::= <T1> <T2:String> <T3:boolean> <T4:int> <T5:float> <T6:double> <T7:long>
       <T8:java.lang.Object> <T9:ArrayList<String>> <T10:java.util.ArrayList<java.lang.String>>
-      /<NT2:String>/ /<NT3:boolean>/ /<NT4:int>/ /<NT5:float>/ /<NT6:double>/ /<NT7:long>/ ;
+      /<NT1>/ /<NT2:String>/ /<NT3:boolean>/ /<NT4:int>/ /<NT5:float>/ /<NT6:double>/ /<NT7:long>/
+      /[NT8:A]/ /[A]/ /NT10:A*/ /B*/;
 
-rel E.NT1 -> A ;
+rel E.R1 -> A ;
 // inheritance and empty
 F : A ;
 
diff --git a/src/test/jastadd/serializer-names/Serializer.relast b/src/test/jastadd/serializer-names/Serializer.relast
index 6bbd44b8a4ace22be14d9c37b328d903cccda143..9106765c6ee6b8016de898c75efa2f3c38903050 100644
--- a/src/test/jastadd/serializer-names/Serializer.relast
+++ b/src/test/jastadd/serializer-names/Serializer.relast
@@ -13,8 +13,8 @@ C:NamedElement ::= D1:D [D2:D] D3:D*
     <C1:char> <C2:Character>
     <T1:String>
     <T2>
-    <N:Instant>
-    <P:Period>
+    <N:java.time.Instant>
+    <P:java.time.Period>
     <Day:Weekday>;
 D:NamedElement;
 
diff --git a/src/test/jastadd/serializer/Serializer.relast b/src/test/jastadd/serializer/Serializer.relast
index 6bbd44b8a4ace22be14d9c37b328d903cccda143..9106765c6ee6b8016de898c75efa2f3c38903050 100644
--- a/src/test/jastadd/serializer/Serializer.relast
+++ b/src/test/jastadd/serializer/Serializer.relast
@@ -13,8 +13,8 @@ C:NamedElement ::= D1:D [D2:D] D3:D*
     <C1:char> <C2:Character>
     <T1:String>
     <T2>
-    <N:Instant>
-    <P:Period>
+    <N:java.time.Instant>
+    <P:java.time.Period>
     <Day:Weekday>;
 D:NamedElement;