diff --git a/src/main/jastadd/Backend.jadd b/src/main/jastadd/Backend.jadd
deleted file mode 100644
index cab17e15766bc3323d2cc8b6c1b8f5c3c754c396..0000000000000000000000000000000000000000
--- a/src/main/jastadd/Backend.jadd
+++ /dev/null
@@ -1,1744 +0,0 @@
-aspect BackendAbstractGrammar {
-
-  public static String ASTNode.listClass = "ArrayList";
-  public static String ASTNode.jastAddListType = "List";
-
-  public static boolean ASTNode.resolverHelper = false;
-  public static boolean ASTNode.serializer = false;
-  public static boolean ASTNode.jsonPointer = false;
-  public static boolean ASTNode.manualReferences = false;
-  public static boolean ASTNode.useJastAddNames = false;
-
-  public String Program.generateAbstractGrammar() {
-    StringBuilder sb = new StringBuilder();
-    generateAbstractGrammar(sb);
-    return sb.toString();
-  }
-
-  public void Program.generateAbstractGrammar(StringBuilder sb) {
-    for (TypeDecl td: getTypeDecls()) {
-      td.generateAbstractGrammar(sb);
-    }
-  }
-
-  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 ");
-    }
-    sb.append(getID());
-    if (hasSuper()) {
-      sb.append(" : " + getSuper());
-    }
-
-    if (getNumComponent() > 0 || relationComponents().size() > 0) {
-      sb.append(" ::=");
-    }
-    for (Component c: getComponents()) {
-      sb.append(" ");
-      sb.append(c.generateAbstractGrammar());
-    }
-    for (RelationComponent c: relationComponents()) {
-      sb.append(" ");
-      sb.append(c.generateAbstractGrammar());
-    }
-
-    sb.append(";\n");
-  }
-
-  public String Component.generateAbstractGrammar() {
-    if (getID().equals(getTypeUse().toString())) {
-      return getTypeUse().toString();
-    } else {
-      return getID() + ":" + getTypeUse();
-    }
-  }
-  public String ListComponent.generateAbstractGrammar() {
-    return super.generateAbstractGrammar() + "*";
-  }
-  public String OptComponent.generateAbstractGrammar() {
-    return "[" + super.generateAbstractGrammar() + "]";
-  }
-  public String NTAComponent.generateAbstractGrammar() {
-    return "/" + super.generateAbstractGrammar() + "/";
-  }
-  public String NTAListComponent.generateAbstractGrammar() {
-    return "/" + super.generateAbstractGrammar() + "*/";
-  }
-  public String NTAOptComponent.generateAbstractGrammar() {
-    return "/[" + super.generateAbstractGrammar() + "]/";
-  }
-  public String TokenComponent.generateAbstractGrammar() {
-    return "<" + getID() + ":" + getTypeUse() + ">";
-  }
-  public String NTATokenComponent.generateAbstractGrammar() {
-    return "/<" + getID() + ":" + getTypeUse() + ">/";
-  }
-
-  public String RelationComponent.generateAbstractGrammar() {
-    return "<" + getImplAttributeName() + ":" + ofTypeDecl() + ">";
-  }
-  public String ManyRelationComponent.generateAbstractGrammar() {
-    return "<" + getImplAttributeName() + ":" + ASTNode.listClass + "<" + ofTypeDecl() + ">>";
-  }
-
-  public String RelationComponent.getImplAttributeName() {
-    return "_impl_" + getID();
-  }
-
-  public String RelationComponent.getImplAttributeField() {
-    //  tt.bind("TypeInSignature", ASTNode.convTypeNameToSignature(type()));
-    return "token" + ofTypeDecl() + "__impl_" + getID();
-  }
-
-  public String ManyRelationComponent.getImplAttributeField() {
-    //  tt.bind("TypeInSignature", ASTNode.convTypeNameToSignature(type()));
-    return "token" + listClass + "_" + ofTypeDecl() + "___impl_" + getID();
-  }
-}
-
-aspect BackendAspect {
-  public String Program.generateAspect() {
-    StringBuilder sb = new StringBuilder();
-    generateAspect(sb);
-    return sb.toString();
-  }
-
-  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()) {
-      if (td.needsConstructor()) {
-        td.generateConstructor(sb);
-      }
-    }
-    for (Relation r: getRelations()) {
-      r.generateAPI(sb);
-    }
-
-    generateLowerBoundCheck(sb);
-
-    sb.append(ind(1) + "public static void ASTNode.assertNotNull(Object obj) {\n");
-    sb.append(ind(2) + "if (obj == null) {\n");
-    sb.append(ind(3) + "throw new NullPointerException();\n");
-    sb.append(ind(2) + "}\n");
-    sb.append(ind(1) + "}\n");
-    sb.append("}\n");
-  }
-
-  public void TypeDecl.generateConstructor(StringBuilder sb) {
-    sb.append(ind(1) + "public " + getID() + "." + getID() + "(");
-    int i = 0;
-    for (Component c: componentsTransitive()) {
-      sb.append(c.constructorParameter());
-      if (++i < componentsTransitive().size()) {
-        sb.append(", ");
-      }
-    }
-    sb.append(") {\n");
-    for (Component c: componentsTransitive()) {
-      sb.append(ind(2) + c.constructorSetMethod() + "(" + c.getID() + ");\n");
-    }
-    sb.append(ind(1) + "}\n");
-  }
-  public String Component.constructorParameter() {
-    return getTypeUse() + " " + getID();
-  }
-  public String ListComponent.constructorParameter() {
-    return ASTNode.jastAddListType + "<" + getTypeUse() + "> " + getID();
-  }
-  public String OptComponent.constructorParameter() {
-    return "Opt<" + getTypeUse() + "> " + getID();
-  }
-  public String Component.constructorSetMethod() {
-    return "set" + getID();
-  }
-  public String ListComponent.constructorSetMethod() {
-    return "set" + getID() + "List";
-  }
-  public String OptComponent.constructorSetMethod() {
-    return "set" + getID() + "Opt";
-  }
-}
-
-aspect BackendAPI {
-  public void Relation.generateAPI(StringBuilder sb) {
-    sb.append(ind(1) + "// " + prettyPrint() + "\n");
-    getDirection().generateAPI(sb);
-    sb.append("\n");
-  }
-  public abstract void Direction.generateAPI(StringBuilder sb);
-
-
-  inh Relation Direction.relation();
-  eq Relation.getChild().relation() = this;
-  eq Program.getChild().relation() = null;
-
-  public String RelationComponent.nameCapitalized() {
-    return name().substring(0,1).toUpperCase() + name().substring(1);
-  }
-}
-
-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);
-
-    // Set
-    sb.append(ind(1) + "public " + getTypeUse().decl() + " " + getTypeUse().decl());
-    sb.append(".set" + nameCapitalized() + "(" + ofTypeDecl() + " o) {\n");
-    if (!optional) {
-      sb.append(ind(2) + "assertNotNull(o);\n");
-    }
-    sb.append(ind(2) + "set" + getImplAttributeName() + "(o);\n");
-    sb.append(ind(2) + "return this;\n");
-    sb.append(ind(1) + "}\n");
-  }
-
-  public void ManyRelationComponent.generateDirectedAPI(StringBuilder sb) {
-    // Get
-    sb.append(ind(1) + "public java.util.List<" + ofTypeDecl() + "> " + getTypeUse().decl() + ".");
-    if (useJastAddNames) {
-      // getXs
-      sb.append("get" + nameCapitalized() + "s() {\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");
-    } else {
-      sb.append(name() + "() {\n");
-    }
-    sb.append(ind(2) + ASTNode.listClass + "<" + 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(2) + "}\n");
-    }
-    sb.append(ind(2) + "return l != null ? Collections.unmodifiableList(l) : 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");
-    sb.append(ind(2) + "assertNotNull(o);\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(o);\n");
-    sb.append(ind(2) + "set" + getImplAttributeName() + "(list);\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) + 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(1) + "}\n");
-
-    // Remove
-    sb.append(ind(1) + "public void " + getTypeUse().decl() + ".remove");
-    if (!useJastAddNames) {
-      sb.append("From");
-    }
-    sb.append(nameCapitalized() + "(" + ofTypeDecl() + " o) {\n");
-    sb.append(ind(2) + "assertNotNull(o);\n");
-    sb.append(ind(2) + ASTNode.listClass + "<" + 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");
-  }
-}
-
-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);
-
-    // Set
-    sb.append(ind(1) + "public " + getTypeUse().decl() + " " + getTypeUse().decl());
-    sb.append(".set" + nameCapitalized() + "(" + ofTypeDecl() + " o) {\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) + "}\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");
-      }
-    }
-
-    sb.append(ind(2) + "return this;\n");
-    sb.append(ind(1) + "}\n");
-
-    if (isOpt) {
-      generateExtraOptAPI(sb);
-    }
-  }
-
-  public void RelationComponent.generateBiManyMany(StringBuilder sb, RelationComponent opposite) {
-    // Get
-    sb.append(ind(1) + "public java.util.List<" + ofTypeDecl() + "> " + getTypeUse().decl() + ".");
-    if (useJastAddNames) {
-      // getXs
-      sb.append("get" + nameCapitalized() + "s() {\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");
-    } else {
-      sb.append(name() + "() {\n");
-    }
-    sb.append(ind(2) + ASTNode.listClass + "<" + 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(2) + "}\n");
-    }
-    sb.append(ind(2) + "return l != null ? Collections.unmodifiableList(l) : 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");
-    sb.append(ind(2) + "assertNotNull(o);\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) + ASTNode.listClass + "<" + otherSide().ofTypeDecl() + "> list2 = o." + otherSide().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(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) + 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) + ASTNode.listClass + "<" + otherSide().ofTypeDecl() + "> list2 = o."
-    + otherSide().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(1) + "}\n");
-
-    // Remove
-    sb.append(ind(1) + "public void " + getTypeUse().decl() + ".remove");
-    if (!useJastAddNames) {
-      sb.append("From");
-    }
-    sb.append(nameCapitalized() + "(" + ofTypeDecl() + " o) {\n");
-    sb.append(ind(2) + "assertNotNull(o);\n");
-    sb.append(ind(2) + ASTNode.listClass + "<" + 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) + "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(2) + "}\n");
-    sb.append(ind(1) + "}\n");
-  }
-
-
-  public void RelationComponent.generateBiManyOne(StringBuilder sb, RelationComponent opposite) {
-    // Get
-    sb.append(ind(1) + "public java.util.List<" + ofTypeDecl() + "> " + getTypeUse().decl() + ".");
-    if (useJastAddNames) {
-      // getXs
-      sb.append("get" + nameCapitalized() + "s() {\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");
-    } else {
-      sb.append(name() + "() {\n");
-    }
-    sb.append(ind(2) + ASTNode.listClass + "<" + 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(2) + "}\n");
-    }
-    sb.append(ind(2) + "return l != null ? Collections.unmodifiableList(l) : 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");
-    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(o);\n");
-    sb.append(ind(2) + "set" + getImplAttributeName() + "(list);\n");
-    sb.append(ind(2) + "o.set" + otherSide().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");
-    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");
-    sb.append(ind(2) + "assertNotNull(o);\n");
-    sb.append(ind(2) + ASTNode.listClass + "<" + 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) + "}\n");
-    sb.append(ind(2) + "}\n");
-    sb.append(ind(1) + "}\n");
-  }
-
-  public void RelationComponent.generateBiOneMany(StringBuilder sb, boolean isOpt) {
-    // Get
-    generateGetOne(sb);
-
-    // Set
-    sb.append(ind(1) + "public " + getTypeUse().decl() + " " + getTypeUse().decl() + ".set" + nameCapitalized()
-      + "(" + ofTypeDecl() + " o) {\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) + "list2.remove(this);\n");
-    sb.append(ind(3) + getImplAttributeField() + "." + "set"
-      + otherSide().getImplAttributeName() + "(list2);\n");
-    sb.append(ind(2) + "}\n");
-    sb.append(ind(2) + "set" + getImplAttributeName() + "(o);\n");
-
-    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) + "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(2) + "}\n");
-    }
-    sb.append(ind(2) + "return this;\n");
-    sb.append(ind(1) + "}\n");
-
-    if (isOpt) {
-      generateExtraOptAPI(sb);
-    }
-  }
-}
-
-aspect LowerBoundCheck {
-  public void Program.generateLowerBoundCheck(StringBuilder sb) {
-    sb.append(ind(1) + "public boolean ASTNode.violatesLowerBounds() {\n");
-    sb.append(ind(2) + "return !getLowerBoundsViolations().isEmpty();\n");
-    sb.append(ind(1) + "}\n");
-
-    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) + "computeLowerBoundsViolations(list);\n");
-    sb.append(ind(2) + "return list;\n");
-    sb.append(ind(1) + "}\n");
-
-    sb.append(ind(1) + "public void ASTNode.computeLowerBoundsViolations("
-      + "java.util.List<Pair<ASTNode, String>> list) {\n");
-    sb.append(ind(2) + "for (int i = 0; i < getNumChildNoTransform(); i++) {\n");
-    sb.append(ind(3) + "getChildNoTransform(i).computeLowerBoundsViolations(list);\n");
-    sb.append(ind(2) + "}\n");
-    sb.append(ind(1) + "}\n");
-
-    for (TypeDecl td: getTypeDecls()) {
-      td.generateLowerBoundCheck(sb);
-    }
-
-    generatePairClass(sb);
-  }
-
-  public void TypeDecl.generateLowerBoundCheck(StringBuilder sb) {
-    if (!oneRelationComponents().isEmpty()) {
-      sb.append(ind(1) + "public void " + getID() + ".computeLowerBoundsViolations(" +
-        "java.util.List<Pair<ASTNode, String>> list) {\n");
-      for (OneRelationComponent o: oneRelationComponents()) {
-        o.generateLowerBoundCheck(sb);
-      }
-      sb.append(ind(2) + "super.computeLowerBoundsViolations(list);\n");
-      sb.append(ind(1) + "}\n");
-    }
-  }
-
-  public void OneRelationComponent.generateLowerBoundCheck(StringBuilder sb) {
-    sb.append(ind(2) + "if (");
-    if (useJastAddNames) {
-      sb.append("get" + nameCapitalized());
-    } else {
-      sb.append(name());
-    }
-    sb.append("() == null) {\n");
-    sb.append(ind(3) + "list.add(new Pair<>(this, \"" + name() + "\"));\n");
-    sb.append(ind(2) + "}\n");
-  }
-
-
-  public void Program.generatePairClass(StringBuilder sb) {
-    sb.append(ind(1) + "public class Pair<T1, T2> {\n");
-    sb.append(ind(2) + "public final T1 _1;\n");
-    sb.append(ind(2) + "public final T2 _2;\n");
-    // Constructor
-    sb.append(ind(2) + "public Pair(T1 _1, T2 _2) {\n");
-    sb.append(ind(3) + "ASTNode.assertNotNull(_1);\n");
-    sb.append(ind(3) + "ASTNode.assertNotNull(_2);\n");
-    sb.append(ind(3) + "this._1 = _1;\n");
-    sb.append(ind(3) + "this._2 = _2;\n");
-    sb.append(ind(2) + "}\n");
-    // equals
-    sb.append(ind(2) + "public boolean equals(Object other) {\n");
-    sb.append(ind(3) + "if (other instanceof Pair) {\n");
-    sb.append(ind(4) + "Pair<?,?> p = (Pair<?,?>) other;\n");
-    sb.append(ind(4) + "return _1.equals(p._1) && _2.equals(p._2);\n");
-    sb.append(ind(3) + "} else {\n");
-    sb.append(ind(4) + "return false;\n");
-    sb.append(ind(3) + "}\n");
-    sb.append(ind(2) + "}\n");
-    // hashCode
-    sb.append(ind(2) + "public int hashCode() {\n");
-    sb.append(ind(3) + "return 31*_1.hashCode() + _2.hashCode();\n");
-    sb.append(ind(2) + "}\n");
-    sb.append(ind(1) + "}\n");
-  }
-}
-
-aspect NameResolutionHelper {
-
-  public String Program.generateRewriteToSuperTypeStub() {
-    StringBuilder sb = new StringBuilder();
-    generateRewriteToSuperTypeStub(sb);
-    return sb.toString();
-  }
-
-  public String Program.generateResolverStubs() {
-    StringBuilder sb = new StringBuilder();
-    generateResolverStubs(sb);
-    return sb.toString();
-  }
-
-  public void Program.generateResolverStubs(StringBuilder sb) {
-    sb.append("aspect RefResolverStubs {\n\n");
-
-    for (Relation r: getRelations()) {
-      r.generateContextDependentNameResolution(sb);
-    }
-
-    if (resolverHelper || ASTNode.jsonPointer || ASTNode.manualReferences) {
-      for (TypeDecl decl : getTypeDeclList()) {
-        decl.generateContextIndependentNameResolution(sb);
-        sb.append("\n");
-      }
-    }
-    sb.append("}\n\n");
-
-    if (ASTNode.manualReferences) {
-      sb.append("aspect RefCreatorStubs {\n\n");
-
-      for (Relation r: getRelations()) {
-        r.generateContextDependentRefCreation(sb);
-      }
-
-      generateGenericRefCreation(sb);
-      sb.append("\n");
-
-      for (TypeDecl decl : getTypeDeclList()) {
-        decl.generateContextIndependentRefCreation(sb);
-        sb.append("\n");
-      }
-
-      sb.append("}\n\n");
-    }
-  }
-
-  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(2) + "throw new RuntimeException(\"Generic reference creation not implemented.\");\n");
-    sb.append(ind(1) + "}\n");
-  }
-
-  public void Relation.generateContextDependentRefCreation(StringBuilder sb) {
-    sb.append(ind(1) + "// " + prettyPrint() + "\n");
-    getDirection().generateContextDependentRefCreation(sb);
-    sb.append("\n");
-  }
-
-  public abstract void Direction.generateContextDependentRefCreation(StringBuilder sb);
-  public void RightDirection.generateContextDependentRefCreation(StringBuilder sb) {
-    relation().getLeft().generateContextDependentRefCreation(sb);
-  }
-  public void LeftDirection.generateContextDependentRefCreation(StringBuilder sb) {
-    relation().getRight().generateContextDependentRefCreation(sb);
-  }
-  public void Bidirectional.generateContextDependentRefCreation(StringBuilder sb) {
-    relation().getLeft().generateContextDependentRefCreation(sb);
-    relation().getRight().generateContextDependentRefCreation(sb);
-  }
-
-  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(2) + "// default to context-independent reference creation\n");
-    sb.append(ind(2) + "return target.createReference();\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(2) + "// default to generic reference creation\n");
-    sb.append(ind(2) + "return super.createReference();\n");
-    sb.append(ind(1) + "}\n");
-  }
-
-  public void Program.generateRewriteToSuperTypeStub(StringBuilder sb) {
-
-    sb.append("aspect ReferenceCreation {\n\n");
-
-    for (TypeDecl decl : getTypeDeclList()) {
-      decl.createReferenceCreator(sb);
-    }
-
-    sb.append("}\n\n");
-
-    sb.append("aspect ResolverTrigger {\n\n");
-
-    resolveAll(sb);
-
-    for (TypeDecl decl : getTypeDeclList()) {
-      decl.resolveAll(sb);
-    }
-
-    sb.append("}\n\n");
-
-    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) + "}\n\n");
-
-    for (TypeDecl td: getTypeDecls()) {
-      if (td.needUnresolvedClass()) {
-        td.generateUnresolvedClass(sb);
-      }
-    }
-
-    sb.append("\n}\n");
-  }
-
-  public void TypeDecl.createReferenceCreator(StringBuilder sb) {
-
-    TypeDecl instantiableSubType = instantiableSubType();
-    if (instantiableSubType == null) {
-      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(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(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");
-    if (serializer && !manualReferences) {
-      if (jsonPointer) {
-        sb.append(ind(2) + "return (" + getID() + ") resolveJsonPointer(id);\n");
-      } else {
-        sb.append(ind(2) + "return (" + getID() + ") globallyResolveASTNodeByUID(id);\n");
-      }
-    } else {
-      sb.append(ind(2) + "// perform context independent name resolution here using the id\n");
-      sb.append(ind(2) + "throw new RuntimeException(\"Context-independent name resolution for " + getID() + " not implemented.\");\n");
-    }
-    sb.append(ind(1) + "}\n");
-  }
-
-  public void Relation.generateContextDependentNameResolution(StringBuilder sb) {
-    sb.append(ind(1) + "// " + prettyPrint() + "\n");
-    getDirection().generateContextDependentNameResolution(sb);
-    sb.append("\n");
-  }
-
-  public abstract void Direction.generateContextDependentNameResolution(StringBuilder sb);
-  public void RightDirection.generateContextDependentNameResolution(StringBuilder sb) {
-    relation().getLeft().generateContextDependentNameResolution(sb);
-  }
-  public void LeftDirection.generateContextDependentNameResolution(StringBuilder sb) {
-    relation().getRight().generateContextDependentNameResolution(sb);
-  }
-  public void Bidirectional.generateContextDependentNameResolution(StringBuilder sb) {
-    relation().getLeft().generateContextDependentNameResolution(sb);
-    relation().getRight().generateContextDependentNameResolution(sb);
-  }
-
-  public abstract void RelationComponent.generateContextDependentNameResolution(StringBuilder sb);
-  public void OneRelationComponent.generateContextDependentNameResolution(StringBuilder sb) {
-    generateDirectedContextDependentNameResolution(sb);
-  }
-  public void OptionalRelationComponent.generateContextDependentNameResolution(StringBuilder sb) {
-    // optional relations are resolved in the same way as mandatory relations
-    // TODO maybe, there should be a check if the id to be solved is empty or null
-    generateDirectedContextDependentNameResolution(sb);
-  }
-  public void ManyRelationComponent.generateContextDependentNameResolution(StringBuilder sb) {
-
-    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) + "}\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(2) + "// default to context-independent name resolution\n");
-        sb.append(ind(2) + "return globallyResolve" + ofTypeDecl() + "ByToken(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) + "}\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(2) + "// default to context-independent name resolution\n");
-        sb.append(ind(2) + "return globallyResolve" + ofTypeDecl() + "ByToken(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) + "}\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(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(4) + "}\n");
-        sb.append(ind(3) + "}\n");
-      sb.append(ind(2) + "}\n");
-      sb.append(ind(2) + "resolveAll();\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");
-    for (RelationComponent relationComponent : relationComponents()) {
-      sb.append(ind(2));
-      if (useJastAddNames) {
-        sb.append("get" + relationComponent.nameCapitalized());
-      } else {
-        sb.append(relationComponent.name());
-      }
-      sb.append(relationComponent.isMany() && useJastAddNames ? "List" : "").append("();\n");
-    }
-      sb.append(ind(2) + "super.resolveAll();\n");
-    sb.append(ind(1) + "}\n");
-  }
-}
-
-aspect Serializer {
-
-  protected static String ASTNode.jsonTypeKey = "type";
-  protected static String ASTNode.jsonNodeType = "com.fasterxml.jackson.databind.JsonNode";
-  protected static String ASTNode.jsonNodeTypeAccessor = ".get(\"" + jsonTypeKey + "\").asText()";
-  public String Program.generateJacksonSerializer() {
-    StringBuilder sb = new StringBuilder();
-    generateFromJson(sb);
-    generateToJson(sb);
-    if (jsonPointer) {
-      writeJsonPointer(sb);
-    } else if (manualReferences) {
-      // TODO
-    } else {
-      writeUID(sb);
-    }
-
-    return sb.toString();
-  }
-
-  public void Program.generateFromJson(StringBuilder sb) {
-    sb.append("aspect JsonToModel {\n");
-
-    sb.append(ind(1) + "public class DeserializationException extends Exception {\n");
-    sb.append(ind(2) + "public DeserializationException(String message) {\n");
-    sb.append(ind(3) + "super(message);\n");
-    sb.append(ind(2) + "}\n");
-    sb.append(ind(2) + "public DeserializationException(String message, Exception cause) {\n");
-    sb.append(ind(3) + "super(message, cause);\n");
-    sb.append(ind(2) + "}\n");
-    sb.append(ind(1) + "}\n");
-
-    sb.append(ind(1) + "public void ASTNode.serialize(com.fasterxml.jackson.core.JsonGenerator g) throws SerializationException {\n");
-    sb.append(ind(2) + "serialize(g, null);\n");
-    sb.append(ind(1) + "}\n");
-
-    sb.append(ind(1) + "public void ASTNode.serialize(com.fasterxml.jackson.core.JsonGenerator g, String field) throws SerializationException {\n");
-    sb.append(ind(2) + "throw new SerializationException(\"unable to serialize class \" + this.getClass().getSimpleName());\n");
-    sb.append(ind(1) + "}\n");
-
-
-    sb.append(ind(1) + "public void ASTNode.serialize(java.io.File file) throws SerializationException {\n");
-    sb.append(ind(2) + "serialize(file, false);\n");
-    sb.append(ind(1) + "}\n");
-
-    sb.append(ind(1) + "public void ASTNode.serialize(java.io.File file, boolean humanReadable) throws SerializationException {\n");
-    sb.append(ind(2) + "try {\n");
-    sb.append(ind(3) + "com.fasterxml.jackson.core.JsonFactory factory = new com.fasterxml.jackson.core.JsonFactory();\n");
-    sb.append(ind(3) + "com.fasterxml.jackson.core.JsonGenerator generator = factory.createGenerator(file, com.fasterxml.jackson.core.JsonEncoding.UTF8);\n");
-    sb.append(ind(3) + "if (humanReadable) {\n");
-    sb.append(ind(4) + "generator.setPrettyPrinter(new com.fasterxml.jackson.core.util.DefaultPrettyPrinter());\n");
-    sb.append(ind(3) + "}\n");
-    sb.append(ind(3) + "serialize(generator);\n");
-    sb.append(ind(3) + "generator.close();\n");
-    sb.append(ind(2) + "} catch (java.io.IOException e) {\n");
-    sb.append(ind(3) + "throw new SerializationException(\"Unable to serialize file \" + file.getAbsolutePath(), e);\n");
-    sb.append(ind(2) + "}\n");
-    sb.append(ind(1) + "}\n");
-
-    for (TypeDecl decl : getTypeDeclList()) {
-      decl.deserialize(sb);
-    }
-
-    sb.append("}\n");
-  }
-
-  public void Program.generateToJson(StringBuilder sb) {
-    sb.append("aspect ModelToJson {\n");
-
-    sb.append(ind(1) + "public class SerializationException extends Exception {\n");
-    sb.append(ind(2) + "public SerializationException(String message) {\n");
-    sb.append(ind(3) + "super(message);\n");
-    sb.append(ind(2) + "}\n");
-    sb.append(ind(2) + "public SerializationException(String message, Exception cause) {\n");
-    sb.append(ind(3) + "super(message, cause);\n");
-    sb.append(ind(2) + "}\n");
-    sb.append(ind(1) + "}\n");
-
-    for (TypeDecl decl : getTypeDeclList()) {
-      decl.serialize(sb);
-    }
-
-    sb.append("}\n");
-  }
-
-   public void TypeDecl.serialize(StringBuilder sb) {
-    sb.append(ind(1) + "public void " + getID() + ".serialize(com.fasterxml.jackson.core.JsonGenerator g, String fieldName) throws SerializationException {\n");
-    sb.append(ind(2) + "try {\n");
-    sb.append(ind(3) + "if (fieldName == null) {\n");
-    sb.append(ind(4) + "g.writeStartObject();\n");
-    sb.append(ind(3) + "} else {\n");
-    sb.append(ind(4) + "g.writeObjectFieldStart(fieldName);\n");
-    sb.append(ind(3) + "}\n");
-    if (!jsonPointer && !manualReferences) {
-      sb.append(ind(3) + "g.writeStringField(\"" + jsonTypeKey + "\", \"" + getID() + "\");\n");
-      sb.append(ind(3) + "if (unique$Id() == null) throw new SerializationException(\"The unique identifier of " + getID() + " is missing.\");\n");
-      sb.append(ind(3) + "g.writeStringField(\"id\", unique$Id());\n");
-    }
-    if (componentsTransitive().size() > 0) {
-      sb.append(ind(3) + "g.writeObjectFieldStart(\"children\");\n");
-      for (Component child : componentsTransitive()) {
-        if (child.isNullable()) {
-          String componentAccessor = child.getID();
-          if (child.isList()) {
-            componentAccessor += "List";
-          } else if (child.isOpt()) {
-            componentAccessor += "Opt";
-          }
-          sb.append(ind(3) + "if (get" + componentAccessor + "() == null) throw new SerializationException(\"The component " + child.getID() + " of " + getID() + " is missing.\");\n");
-        }
-        child.serialize(sb, 3);
-      }
-      sb.append(ind(3) + "g.writeEndObject(); // children\n");
-    }
-    if (relationComponentsTransitive().size() > 0) {
-      sb.append(ind(3) + "g.writeObjectFieldStart(\"relations\");\n");
-      for (RelationComponent relation : relationComponentsTransitive()) {
-        relation.serialize(sb, 3);
-      }
-      sb.append(ind(3) + "g.writeEndObject(); // relations\n");
-    }
-    sb.append(ind(3) + "g.writeEndObject();\n");
-    sb.append(ind(2) + "} catch (java.io.IOException e) {\n");
-    sb.append(ind(3) + "throw new SerializationException(\"unable to serialize " + getID() + "\", e);\n");
-    sb.append(ind(2) + "}\n");
-    sb.append(ind(1) + "}\n");
-
-  }
-
-  public abstract void Component.serialize(StringBuilder sb, int indent);
-
-  public void NormalComponent.serialize(StringBuilder sb, int indent) {
-    sb.append(ind(indent) + "get" + getID() + "().serialize(g, \"" + getID() + "\");\n");
-  }
-
-  public void OptComponent.serialize(StringBuilder sb, int indent) {
-    sb.append(ind(indent) + "if (has" + getID() + "()) {\n");
-    sb.append(ind(indent + 1) + "get" + getID() + "().serialize(g, \"" + getID() + "\");\n");
-    sb.append(ind(indent) + "}\n");
-  }
-
-  public void TokenComponent.serialize(StringBuilder sb, int indent) {
-
-    String type = getTypeUse().getID();
-
-    switch (type) {
-      case "float":
-      case "Float":
-      case "double":
-      case "Double":
-      case "int":
-      case "Integer":
-      case "short":
-      case "Short":
-      case "long":
-      case "Long":
-      case "byte":
-      case "Byte":
-        sb.append(ind(indent) + "g.writeNumberField(\"" + getID() + "\", get" + getID() + "());\n");
-        break;
-      case "boolean":
-      case "Boolean":
-        sb.append(ind(indent) + "g.writeBooleanField(\"" + getID() + "\", get" + getID() + "());\n");
-        break;
-      case "char":
-      case "Character":
-        sb.append(ind(indent) + "g.writeStringField(\"" + getID() + "\", Character.toString(get"+ getID() + "()));\n");
-        break;
-      case "String":
-        sb.append(ind(indent) + "g.writeStringField(\"" + getID() + "\", get" + getID() + "());\n");
-        break;
-      case "Instant":
-        sb.append(ind(indent) + "g.writeStringField(\"" + getID() + "\", get" + getID() + "().toString());\n");
-        break;
-      case "Period":
-        sb.append(ind(indent) + "g.writeStringField(\"" + getID() + "\", get" + getID() + "().toString());\n");
-        break;
-      default:
-        // assume that the type is an enum. there is no way of checking this here
-        sb.append(ind(indent) + "g.writeStringField(\"" + getID() + "\", get" + getID() + "().name());\n");
-        // sb.append("throw new DeserializationException(\"Unable to deserialize child node of type \"" + getTypeUse() + "\"\")")
-    }
-  }
-
-  public void NTAComponent.serialize(StringBuilder sb, int indent) {
-    // do not serialize NTA
-  }
-
-  public void NTAListComponent.serialize(StringBuilder sb, int indent) {
-    // do not serialize NTA
-  }
-
-  public void NTAOptComponent.serialize(StringBuilder sb, int indent) {
-    // do not serialize NTA
-  }
-
-  public void ListComponent.serialize(StringBuilder sb, int indent) {
-    sb.append(ind(indent) + "if (getNum" + getID() + "() > 0) {\n");
-    sb.append(ind(indent + 1) + "g.writeArrayFieldStart(\"" + getID() + "\");\n");
-    sb.append(ind(indent + 1) + "for (" + getTypeUse().decl().getID() + " child : get" + getID() + "List()) {\n");
-    sb.append(ind(indent + 2) + "child.serialize(g);\n");
-    sb.append(ind(indent + 1) + "}\n");
-    sb.append(ind(indent + 1) + "g.writeEndArray();\n");
-    sb.append(ind(indent) + "}\n");
-  }
-
-  public void OneRelationComponent.serialize(StringBuilder sb, int indent) {
-    if (useJastAddNames){
-      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");
-      } else {
-        sb.append(ind(indent) + "g.writeStringField(\"" + getID() + "\", get" + getID() + "().unique$Id());\n");
-      }
-    } else {
-      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");
-      } else {
-        sb.append(ind(indent) + "g.writeStringField(\"" + getID() + "\", " + getID() + "().unique$Id());\n");
-      }
-    }
-  }
-
-  public void OptionalRelationComponent.serialize(StringBuilder sb, int indent) {
-    sb.append(ind(indent) + "if (has" + nameCapitalized() + "()) {\n");
-    if (useJastAddNames){
-      if (jsonPointer) {
-        sb.append(ind(indent + 1) + "g.writeStringField(\"" + getID() + "\", get" + getID() + "().jsonPointer());\n");
-      } else if (manualReferences) {
-        sb.append(ind(indent + 1) + "g.writeStringField(\"" + getID() + "\", get" + getID() + "().createReference());\n");
-      } else {
-        sb.append(ind(indent + 1) + "g.writeStringField(\"" + getID() + "\", get" + getID() + "().unique$Id());\n");
-      }
-    } else {
-      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");
-      } else {
-        sb.append(ind(indent + 1) + "g.writeStringField(\"" + getID() + "\", " + getID() + "().unique$Id());\n");
-      }
-    }
-    sb.append(ind(indent) + "}\n");
-  }
-
-  public void ManyRelationComponent.serialize(StringBuilder sb, int indent) {
-    sb.append(ind(indent) + "g.writeArrayFieldStart(\"" + getID() + "\");\n");
-    if (useJastAddNames) {
-      sb.append(ind(indent) + "for (" + ofTypeDecl().getID() + " child : get" + getID() + "List()) {\n");
-    } else {
-      sb.append(ind(indent) + "for (" + ofTypeDecl().getID() + " child : " + getID() + "()) {\n");
-    }
-    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");
-    }else {
-      sb.append(ind(indent + 1) + "g.writeString(child.unique$Id());\n");
-    }
-
-    sb.append(ind(indent) + "}\n");
-    sb.append(ind(indent) + "g.writeEndArray();\n");
-  }
-
-  public void TypeDecl.deserialize(StringBuilder sb) {
-
-    sb.append(ind(1) + "public static " + getID() + " " + getID() + ".deserialize(java.io.File file) throws DeserializationException {\n");
-    sb.append(ind(2) + "try {\n");
-    sb.append(ind(3) + "com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();\n");
-    sb.append(ind(3) + "com.fasterxml.jackson.core.JsonFactory factory = mapper.getFactory();\n");
-    sb.append(ind(3) + "com.fasterxml.jackson.core.JsonParser parser = factory.createParser(file);\n");
-    sb.append(ind(3) + getID() + " result = deserialize((com.fasterxml.jackson.databind.JsonNode)mapper.readTree(parser));\n");
-    sb.append(ind(3) + "parser.close();\n");
-    sb.append(ind(3) + "return result;\n");
-    sb.append(ind(2) + "} catch (java.io.IOException e) {\n");
-    sb.append(ind(3) + "throw new DeserializationException(\"unable to deserialize \" + file.getAbsolutePath(), e);\n");
-    sb.append(ind(2) + "}\n");
-    sb.append(ind(1) + "}\n");
-
-    sb.append(ind(1) + "public static " + getID() + " " + getID() + ".deserialize(" + jsonNodeType + " node) throws DeserializationException {\n");
-    sb.append(ind(2) + getID() + " element;\n");
-    if (getAbstract()) {
-      // switch case between all implementations of the abstract class
-      sb.append(ind(2) + "switch (node" + jsonNodeTypeAccessor + ") {\n");
-      for (TypeDecl subType : subTypeDecls()) {
-        sb.append(ind(3) + "case \"" + subType.getID() + "\":\n");
-        sb.append(ind(4) + "element = " + subType.getID() + ".deserialize(node);\n");
-        sb.append(ind(4) + "break;\n");
-      }
-      sb.append(ind(3) + "default:\n");
-      sb.append(ind(4) + "throw new DeserializationException(\"Unable to deserialize child of unexpected type \" + node" + jsonNodeTypeAccessor + " + \"(" + getID() + " expected)\");\n");
-      sb.append(ind(2) + "}\n");
-    } else {
-      sb.append(ind(2) + "element = new " + getID() + "();\n");
-    }
-
-    if (!jsonPointer && !manualReferences) {
-      // deserialize id
-      sb.append(ind(2) + "if (node.has(\"id\")) {\n");
-      sb.append(ind(3) + "element.unique$Id = node.get(\"id\").asText();\n");
-      sb.append(ind(2) + "}\n");
-    }
-
-    // deserialize containment children
-    if (componentsTransitive().size() > 0) {
-      sb.append(ind(2) + "if (node.has(\"children\")) {\n");
-      sb.append(ind(3) + jsonNodeType + " children = node.get(\"children\");\n");
-      for (Component component : componentsTransitive()) {
-        sb.append(ind(3) + "if (children.has(\"" + component.getID() + "\")) {\n");
-        component.deserialize(sb, 4);
-        sb.append(ind(3) + "}\n");
-      }
-      sb.append(ind(2) + "}\n");
-    }
-    // deserialize non-containment children
-    Set<RelationComponent> relationComponents = relationComponents();
-    if (relationComponents.size() > 0) {
-      sb.append(ind(2) + "if (node.has(\"relations\")) {\n");
-      sb.append(ind(3) + jsonNodeType + " relations = node.get(\"relations\");\n");
-      for (RelationComponent component : relationComponents) {
-        sb.append(ind(3) + "if (relations.has(\"" + component.getID() + "\")) {\n");
-        component.deserialize(sb, 4);
-        sb.append(ind(3) + "}\n");
-      }
-      sb.append(ind(2) + "}\n");
-    }
-
-    sb.append(ind(2) + "return element;\n");
-    sb.append(ind(1) + "}\n");
-
-  }
-
-  public abstract void Component.deserialize(StringBuilder sb, int indent);
-
-  public void NormalComponent.deserialize(StringBuilder sb, int indent) {
-    sb.append(ind(indent) + "element.set" + getID() + "(" + getTypeUse().decl().getID() + ".deserialize(children.get(\"" + getID() + "\")));\n");
-    sb.append(ind(indent - 1) + "} else {\n");
-    sb.append(ind(indent) + "throw new DeserializationException(\"deserializer of missing mandatory child " + getID() + "\");\n");
-  }
-
-  public void OptComponent.deserialize(StringBuilder sb, int indent) {
-    sb.append(ind(indent + 1) + "element.set" + getID() + "(" + getTypeUse().decl().getID() + ".deserialize(children.get(\"" + getID() + "\")));\n");
-  }
-
-  public void TokenComponent.deserialize(StringBuilder sb, int indent) {
-
-    String type = getTypeUse().getID();
-
-    switch (type) {
-      case "float":
-      case "Float":
-        sb.append(ind(indent) + "element.set" + getID() + "(Float.valueOf(children.get(\"" + getID() + "\").asText()));\n");
-        break;
-      case "double":
-      case "Double":
-        sb.append(ind(indent) + "element.set" + getID() + "(children.get(\"" + getID() + "\").asDouble());\n");
-        break;
-      case "int":
-      case "Integer":
-        sb.append(ind(indent) + "element.set" + getID() + "(children.get(\"" + getID() + "\").asInt());\n");
-        break;
-      case "short":
-      case "Short":
-        sb.append(ind(indent) + "element.set" + getID() + "(Short.valueOf(children.get(\"" + getID() + "\").asText()));\n");
-        break;
-      case "long":
-      case "Long":
-        sb.append(ind(indent) + "element.set" + getID() + "(children.get(\"" + getID() + "\").asLong());\n");
-        break;
-      case "byte":
-      case "Byte":
-        sb.append(ind(indent) + "element.set" + getID() + "(Byte.valueOf(children.get(\"" + getID() + "\").asText()));\n");
-        break;
-      case "boolean":
-      case "Boolean":
-        sb.append(ind(indent) + "element.set" + getID() + "(children.get(\"" + getID() + "\").asBoolean());\n");
-        break;
-      case "char":
-      case "Character":
-        sb.append(ind(indent) + "String chars = children.get(\"" + getID() + "\").asText();\n");
-        sb.append(ind(indent) + "if (chars.length() == 1) {\n");
-        sb.append(ind(indent + 2) + "element.set" + getID() + "(chars.charAt(0));\n");
-        sb.append(ind(indent) + "} else {\n");
-        sb.append(ind(indent + 2) + "throw new DeserializationException(\"unable to deserialize char '\" + chars + \"'\");\n");
-        sb.append(ind(indent) + "}\n");
-        break;
-      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");
-        break;
-      case "Period":
-        sb.append(ind(indent) + "element.set" + getID() + "(Period.parse(children.get(\"" + getID() + "\").asText()));\n");
-        break;
-      default:
-        // assume that the type is an enum. there is no way of checking this here
-        sb.append(ind(indent) + "element.set" + getID() + "(Enum.valueOf(" + type + ".class, children.get(\"" + getID() + "\").asText()));\n");
-
-        // sb.append("throw new DeserializationException(\"Unable to deserialize child node of type \"" + getTypeUse() + "\"\")")
-    }
-  }
-
-  public void Component.writeJsonPointer(StringBuilder sb) {
-    // do nothing for other components
-  }
-
-  public void NormalComponent.writeJsonPointer(StringBuilder sb) {
-    sb.append(ind(1) + "eq " + enclosingTypeDecl().getID() + (useJastAddNames?".get":".") + getID() + "().jsonPointerInh() {\n");
-    sb.append(ind(2) + "return this.jsonPointer() + \"/children/" + getID() + "\";\n");
-    sb.append(ind(1) + "}\n");
-  }
-
-  public void ListComponent.writeJsonPointer(StringBuilder sb) {
-    sb.append(ind(1) + "eq " + enclosingTypeDecl().getID() + (useJastAddNames?".get":".") + getID() + "(int index).jsonPointerInh() {\n");
-    sb.append(ind(2) + "return this.jsonPointer() + \"/children/" + getID() + "/\" + index;\n");
-    sb.append(ind(1) + "}\n");
-  }
-
-  public void OptComponent.writeJsonPointer(StringBuilder sb) {
-    sb.append(ind(1) + "eq " + enclosingTypeDecl().getID() + (useJastAddNames?".get":".") + getID() + "().jsonPointerInh() {\n");
-    sb.append(ind(2) + "return this.jsonPointer() + \"/children/" + getID() + "\";\n");
-    sb.append(ind(1) + "}\n");
-  }
-
-  public void NTAComponent.writeJsonPointer(StringBuilder sb) {
-    sb.append(ind(1) + "eq " + enclosingTypeDecl().getID() + (useJastAddNames?".get":".") + getID() + "().jsonPointerInh() {\n");
-    sb.append(ind(2) + "return this.jsonPointer() + \"/children/" + getID() + "\";\n");
-    sb.append(ind(1) + "}\n");
-  }
-
-  public void NTAListComponent.writeJsonPointer(StringBuilder sb) {
-    sb.append(ind(1) + "eq " + enclosingTypeDecl().getID() + (useJastAddNames?".get":".") + getID() + "(int index).jsonPointerInh() {\n");
-    sb.append(ind(2) + "return this.jsonPointer() + \"/children/" + getID() + "/\" + index;\n");
-    sb.append(ind(1) + "}\n");
-  }
-
-  public void NTAOptComponent.writeJsonPointer(StringBuilder sb) {
-    sb.append(ind(1) + "eq " + enclosingTypeDecl().getID() + (useJastAddNames?".get":".") + getID() + "().jsonPointerInh() {\n");
-    sb.append(ind(2) + "return this.jsonPointer() + \"/children/" + getID() + "\";\n");
-    sb.append(ind(1) + "}\n");
-  }
-
-  public void Component.resolveJsonPointer(StringBuilder sb) {
-    // do nothing for other components
-  }
-
-  public void NormalComponent.resolveJsonPointer(StringBuilder sb) {
-    sb.append(ind(4) + "case \"" + getID() + "\":\n");
-    sb.append(ind(5) + "return get" + getID() + "().resolveJsonPointer(pointer, index + 2);\n");
-  }
-
-  public void ListComponent.resolveJsonPointer(StringBuilder sb) {
-    sb.append(ind(4) + "case \"" + getID() + "\":\n");
-    sb.append(ind(5) + "return get" + getID() + "(Integer.valueOf(pointer[index + 2])).resolveJsonPointer(pointer, index + 3);\n");
-  }
-
-  public void OptComponent.resolveJsonPointer(StringBuilder sb) {
-    sb.append(ind(4) + "case \"" + getID() + "\":\n");
-    sb.append(ind(5) + "return get" + getID() + "().resolveJsonPointer(pointer, index + 2);\n");
-  }
-
-  public void NTAComponent.resolveJsonPointer(StringBuilder sb) {
-    sb.append(ind(4) + "case \"" + getID() + "\":\n");
-    sb.append(ind(5) + "return get" + getID() + "().resolveJsonPointer(pointer, index + 2);\n");
-  }
-
-  public void NTAListComponent.resolveJsonPointer(StringBuilder sb) {
-    sb.append(ind(4) + "case \"" + getID() + "\":\n");
-    sb.append(ind(5) + "return get" + getID() + "(Integer.valueOf(pointer[index + 2])).resolveJsonPointer(pointer, index + 3);\n");
-  }
-
-  public void NTAOptComponent.resolveJsonPointer(StringBuilder sb) {
-    sb.append(ind(4) + "case \"" + getID() + "\":\n");
-    sb.append(ind(5) + "return get" + getID() + "().resolveJsonPointer(pointer, index + 2);\n");
-  }
-
-  public void TypeDecl.resolveJsonPointer(StringBuilder sb) {
-    sb.append(ind(1) + "eq " + getID() + ".resolveJsonPointer(String[] pointer, int index) {\n");
-    sb.append(ind(2) + "if (pointer.length == 0 || pointer.length == index) {\n");
-    sb.append(ind(3) + "return this;\n");
-    sb.append(ind(2) + "} else if (pointer.length == index + 1) {\n");
-    sb.append(ind(3) + "throw new RuntimeException(\"there is only one child called \" + pointer[index]);\n");
-    sb.append(ind(2) + "} else {\n");
-    sb.append(ind(3) + "switch (pointer[index + 1]) {\n");
-
-    for (Component c: getComponentList()) {
-      c.resolveJsonPointer(sb);
-    }
-
-    sb.append(ind(4) + "default:\n");
-    sb.append(ind(5) + "return super.resolveJsonPointer(pointer, index);\n");
-    sb.append(ind(3) + "}\n");
-    sb.append(ind(2) + "}\n");
-    sb.append(ind(1) + "}\n");
-  }
-
-  public void Program.writeJsonPointer(StringBuilder sb) {
-    sb.append("aspect JsonPointer {\n");
-
-    sb.append(ind(1) + "syn String ASTNode.jsonPointer() {\n");
-    sb.append(ind(2) + "if (getParent() == null) {\n");
-    sb.append(ind(3) + "return \"\";\n");
-    sb.append(ind(2) + "} else {\n");
-    sb.append(ind(3) + "return jsonPointerInh();\n");
-    sb.append(ind(2) + "}\n");
-    sb.append(ind(1) + "}\n");
-    sb.append(ind(1) + "inh String ASTNode.jsonPointerInh();\n");
-
-    // define json pointer for each component
-    for (TypeDecl td: getTypeDeclList()) {
-      for (Component c: td.getComponentList()) {
-        c.writeJsonPointer(sb);
-      }
-    }
-
-    sb.append(ind(1) + "syn ASTNode ASTNode.resolveJsonPointer(String pointer) = root().resolveJsonPointer(pointer.split(\"/\"), 1);\n");
-    sb.append(ind(1) + "ASTNode ASTNode.root() {\n");
-    sb.append(ind(2) + "if (getParent() == null) return this;\n");
-    sb.append(ind(2) + "else return getParent().root();\n");
-    sb.append(ind(1) + "}\n");
-    sb.append(ind(1) + "syn ASTNode ASTNode.resolveJsonPointer(String[] pointer, int index) {\n");
-    sb.append(ind(2) + "if (index < pointer.length) {\n");
-    sb.append(ind(3) + "throw new RuntimeException(\"found wrong child  \" + pointer[index + 1] + \" in class \" + this.getClass().getSimpleName());\n");
-    sb.append(ind(2) + "} else {\n");
-    sb.append(ind(3) + "throw new RuntimeException(\"Cannot resolve JSON pointer for class \" + this.getClass().getSimpleName());\n");
-    sb.append(ind(2) + "}\n");
-    sb.append(ind(1) + "}\n");
-
-    // resolve JSON pointers in each nonterminal
-    for (TypeDecl td: getTypeDeclList()) {
-      td.resolveJsonPointer(sb);
-    }
-
-    sb.append("}\n");
-  }
-
-  public void Program.writeUID(StringBuilder sb) {
-    sb.append("aspect UID {\n");
-    sb.append(ind(1) + "class UIDProvider {\n");
-    sb.append(ind(2) + "private static long nextUID = 0;\n");
-    sb.append(ind(2) + "public static String getUID() {\n");
-    sb.append(ind(3) + "return String.valueOf(nextUID++);\n");
-    sb.append(ind(2) + "}\n");
-    sb.append(ind(1) + "}\n");
-    sb.append("\n");
-    sb.append(ind(1) + "protected String ASTNode.unique$Id = null;\n");
-    sb.append("\n");
-    sb.append(ind(1) + "protected String ASTNode.unique$Id() {\n");
-    sb.append(ind(2) + "String customUID = customID();\n");
-    sb.append(ind(2) + "if (customUID == null) {\n");
-    sb.append(ind(3) + "if (unique$Id == null) {\n");
-    sb.append(ind(4) + "unique$Id = UIDProvider.getUID();\n");
-    sb.append(ind(3) + "}\n");
-    sb.append(ind(3) + "return unique$Id;\n");
-    sb.append(ind(2) + "} else {\n");
-    sb.append(ind(3) + "return customUID;\n");
-    sb.append(ind(2) + "}\n");
-    sb.append(ind(1) + "}\n");
-    sb.append("\n");
-    sb.append(ind(1) + "protected String ASTNode.customID() {\n");
-    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(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(2) + "}\n");
-    sb.append(ind(1) + "}\n");
-    sb.append("\n");
-    sb.append(ind(1) + "java.util.Map<String, java.lang.ref.WeakReference<ASTNode>> ASTNode.uid$Map;\n");
-    sb.append("\n");
-    sb.append(ind(1) + "java.util.Map<String, java.lang.ref.WeakReference<ASTNode>> ASTNode.uid$Map() {\n");
-    sb.append(ind(2) + "if (uid$Map == null) {\n");
-    sb.append(ind(3) + "uid$Map = uid$Map(new java.util.HashMap());\n");
-    sb.append(ind(2) + "}\n");
-    sb.append(ind(2) + "return uid$Map;\n");
-    sb.append(ind(1) + "}\n");
-    sb.append(ind(1) + "protected java.util.Map<String, java.lang.ref.WeakReference<ASTNode>> ASTNode.uid$Map(java.util.Map<String, java.lang.ref.WeakReference<ASTNode>> map) {\n");
-    sb.append(ind(2) + "if (!(this instanceof " + jastAddListType + " || this instanceof Opt)) {\n");
-    sb.append(ind(3) + "if (map.keySet().contains(unique$Id())) {\n");
-    sb.append(ind(4) + "throw new RuntimeException(new SerializationException(\"UID \" + this.unique$Id() + \" is assigned to both \" + this.getClass().getSimpleName() + \":\" + this.hashCode() + \" and \" + map.get(unique$Id()).getClass().getSimpleName() + \":\" + map.get(unique$Id()).hashCode()));\n");
-    sb.append(ind(3) + "} else {\n");
-    sb.append(ind(4) + "map.put(this.unique$Id, new java.lang.ref.WeakReference(this));\n");
-    sb.append(ind(3) + "}\n");
-    sb.append(ind(2) + "}\n");
-    sb.append(ind(2) + "for (ASTNode child : astChildren()) {\n");
-    sb.append(ind(3) + "child.uid$Map(map);\n");
-    sb.append(ind(2) + "}\n");
-    sb.append(ind(2) + "return map;\n");
-    sb.append(ind(1) + "}\n");
-    sb.append("}\n");
-  }
-
-  public void NTAComponent.deserialize(StringBuilder sb, int indent) {
-    // do not serialize NTA
-  }
-
-  public void NTAListComponent.deserialize(StringBuilder sb, int indent) {
-    // do not serialize NTA
-  }
-
-  public void NTAOptComponent.deserialize(StringBuilder sb, int indent) {
-    // do not serialize NTA
-  }
-
-  public void ListComponent.deserialize(StringBuilder sb, int indent) {
-    sb.append(ind(indent) + "for (" + jsonNodeType + " child : children.get(\"" + getID() + "\")) {\n");
-    sb.append(ind(indent + 1) + "element.add" + getID() + "(" + getTypeUse().decl().getID() + ".deserialize(child));\n");
-    sb.append(ind(indent) + "}\n");
-  }
-
-  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 - 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");
-  }
-
-  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) + "}\n");
-  }
-}
-
-aspect PrettyPrint {
-  public String Relation.prettyPrint() {
-    return "rel "
-      + getLeft().prettyPrint() + " "
-      + getDirection().prettyPrint() + " "
-      + getRight().prettyPrint();
-  }
-  public String RelationComponent.prettyPrint() {
-    if (getID().isEmpty()) {
-      return getTypeUse().toString();
-    } else {
-      return getTypeUse() + "." +  getID();
-    }
-  }
-  public String OptionalRelationComponent.prettyPrint() {
-    return super.prettyPrint() + "?";
-  }
-  public String ManyRelationComponent.prettyPrint() {
-    return super.prettyPrint() + "*";
-  }
-  abstract public String Direction.prettyPrint();
-  public String RightDirection.prettyPrint() {
-    return "->";
-  }
-  public String LeftDirection.prettyPrint() {
-    return "<-";
-  }
-  public String Bidirectional.prettyPrint() {
-    return "<->";
-  }
-
-}
-
-aspect Utils {
-  public String ASTNode.ind(int n) {
-    String s = "";
-    for (int i = 0; i < n; i++) {
-      s += "  ";
-    }
-    return s;
-  }
-}
diff --git a/src/main/jastadd/backend/API.jadd b/src/main/jastadd/backend/API.jadd
new file mode 100644
index 0000000000000000000000000000000000000000..481bc2dbbea2bb55c7d0fd6f0bf350e739fba416
--- /dev/null
+++ b/src/main/jastadd/backend/API.jadd
@@ -0,0 +1,17 @@
+aspect BackendAPI {
+  public void Relation.generateAPI(StringBuilder sb) {
+    sb.append(ind(1) + "// " + prettyPrint() + "\n");
+    getDirection().generateAPI(sb);
+    sb.append("\n");
+  }
+  public abstract void Direction.generateAPI(StringBuilder sb);
+
+
+  inh Relation Direction.relation();
+  eq Relation.getChild().relation() = this;
+  eq Program.getChild().relation() = null;
+
+  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
new file mode 100644
index 0000000000000000000000000000000000000000..a9728d231ef25162218719dc08a47853003abbdb
--- /dev/null
+++ b/src/main/jastadd/backend/AbstractGrammar.jadd
@@ -0,0 +1,138 @@
+aspect BackendAbstractGrammar {
+
+  public static String ASTNode.listClass = "ArrayList";
+  public static String ASTNode.jastAddListType = "List";
+
+  public static boolean ASTNode.resolverHelper = false;
+  public static boolean ASTNode.serializer = false;
+  public static boolean ASTNode.jsonPointer = false;
+  public static boolean ASTNode.manualReferences = false;
+  public static boolean ASTNode.useJastAddNames = false;
+
+  public String Program.generateAbstractGrammar() {
+    StringBuilder sb = new StringBuilder();
+    generateAbstractGrammar(sb);
+    return sb.toString();
+  }
+
+  public void Program.generateAbstractGrammar(StringBuilder sb) {
+    for (TypeDecl td: getTypeDecls()) {
+      td.generateAbstractGrammar(sb);
+    }
+  }
+
+  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 ");
+    }
+    sb.append(getID());
+    if (hasSuper()) {
+      sb.append(" : " + getSuper());
+    }
+
+    if (getNumComponent() > 0 || relationComponents().size() > 0) {
+      sb.append(" ::=");
+    }
+    for (Component c: getComponents()) {
+      sb.append(" ");
+      sb.append(c.generateAbstractGrammar());
+    }
+    for (RelationComponent c: relationComponents()) {
+      sb.append(" ");
+      sb.append(c.generateAbstractGrammar());
+    }
+
+    sb.append(";\n");
+  }
+
+  public String Component.generateAbstractGrammar() {
+    if (getID().equals(getTypeUse().toString())) {
+      return getTypeUse().toString();
+    } else {
+      return getID() + ":" + getTypeUse();
+    }
+  }
+  public String ListComponent.generateAbstractGrammar() {
+    return super.generateAbstractGrammar() + "*";
+  }
+  public String OptComponent.generateAbstractGrammar() {
+    return "[" + super.generateAbstractGrammar() + "]";
+  }
+  public String NTAComponent.generateAbstractGrammar() {
+    return "/" + super.generateAbstractGrammar() + "/";
+  }
+  public String NTAListComponent.generateAbstractGrammar() {
+    return "/" + super.generateAbstractGrammar() + "*/";
+  }
+  public String NTAOptComponent.generateAbstractGrammar() {
+    return "/[" + super.generateAbstractGrammar() + "]/";
+  }
+  public String TokenComponent.generateAbstractGrammar() {
+    return "<" + getID() + ":" + getTypeUse() + ">";
+  }
+  public String NTATokenComponent.generateAbstractGrammar() {
+    return "/<" + getID() + ":" + getTypeUse() + ">/";
+  }
+
+  public String RelationComponent.generateAbstractGrammar() {
+    return "<" + getImplAttributeName() + ":" + ofTypeDecl() + ">";
+  }
+  public String ManyRelationComponent.generateAbstractGrammar() {
+    return "<" + getImplAttributeName() + ":" + ASTNode.listClass + "<" + ofTypeDecl() + ">>";
+  }
+
+  public String RelationComponent.getImplAttributeName() {
+    return "_impl_" + getID();
+  }
+
+  public String RelationComponent.getImplAttributeField() {
+    //  tt.bind("TypeInSignature", ASTNode.convTypeNameToSignature(type()));
+    return "token" + ofTypeDecl() + "__impl_" + getID();
+  }
+
+  public String ManyRelationComponent.getImplAttributeField() {
+    //  tt.bind("TypeInSignature", ASTNode.convTypeNameToSignature(type()));
+    return "token" + listClass + "_" + ofTypeDecl() + "___impl_" + getID();
+  }
+}
diff --git a/src/main/jastadd/backend/Backend.jadd b/src/main/jastadd/backend/Backend.jadd
new file mode 100644
index 0000000000000000000000000000000000000000..c751488eb18df1a1e59b4a93edebf341bcdea0c0
--- /dev/null
+++ b/src/main/jastadd/backend/Backend.jadd
@@ -0,0 +1,67 @@
+aspect BackendAspect {
+  public String Program.generateAspect() {
+    StringBuilder sb = new StringBuilder();
+    generateAspect(sb);
+    return sb.toString();
+  }
+
+  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()) {
+      if (td.needsConstructor()) {
+        td.generateConstructor(sb);
+      }
+    }
+    for (Relation r: getRelations()) {
+      r.generateAPI(sb);
+    }
+
+    generateLowerBoundCheck(sb);
+
+    sb.append(ind(1) + "public static void ASTNode.assertNotNull(Object obj) {\n");
+    sb.append(ind(2) + "if (obj == null) {\n");
+    sb.append(ind(3) + "throw new NullPointerException();\n");
+    sb.append(ind(2) + "}\n");
+    sb.append(ind(1) + "}\n");
+    sb.append("}\n");
+  }
+
+  public void TypeDecl.generateConstructor(StringBuilder sb) {
+    sb.append(ind(1) + "public " + getID() + "." + getID() + "(");
+    int i = 0;
+    for (Component c: componentsTransitive()) {
+      sb.append(c.constructorParameter());
+      if (++i < componentsTransitive().size()) {
+        sb.append(", ");
+      }
+    }
+    sb.append(") {\n");
+    for (Component c: componentsTransitive()) {
+      sb.append(ind(2) + c.constructorSetMethod() + "(" + c.getID() + ");\n");
+    }
+    sb.append(ind(1) + "}\n");
+  }
+  public String Component.constructorParameter() {
+    return getTypeUse() + " " + getID();
+  }
+  public String ListComponent.constructorParameter() {
+    return ASTNode.jastAddListType + "<" + getTypeUse() + "> " + getID();
+  }
+  public String OptComponent.constructorParameter() {
+    return "Opt<" + getTypeUse() + "> " + getID();
+  }
+  public String Component.constructorSetMethod() {
+    return "set" + getID();
+  }
+  public String ListComponent.constructorSetMethod() {
+    return "set" + getID() + "List";
+  }
+  public String OptComponent.constructorSetMethod() {
+    return "set" + getID() + "Opt";
+  }
+}
diff --git a/src/main/jastadd/backend/BidirectionalAPI.jadd b/src/main/jastadd/backend/BidirectionalAPI.jadd
new file mode 100644
index 0000000000000000000000000000000000000000..75f28ab40df71d2690208408e6cb314d9107d886
--- /dev/null
+++ b/src/main/jastadd/backend/BidirectionalAPI.jadd
@@ -0,0 +1,349 @@
+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);
+
+    // Set
+    sb.append(ind(1) + "public " + getTypeUse().decl() + " " + getTypeUse().decl());
+    sb.append(".set" + nameCapitalized() + "(" + ofTypeDecl() + " o) {\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) + "}\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");
+      }
+    }
+
+    sb.append(ind(2) + "return this;\n");
+    sb.append(ind(1) + "}\n");
+
+    if (isOpt) {
+      generateExtraOptAPI(sb);
+    }
+  }
+
+  public void RelationComponent.generateBiManyMany(StringBuilder sb, RelationComponent opposite) {
+    // Get
+    sb.append(ind(1) + "public java.util.List<" + ofTypeDecl() + "> " + getTypeUse().decl() + ".");
+    if (useJastAddNames) {
+      // getXs
+      sb.append("get" + nameCapitalized() + "s() {\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");
+    } else {
+      sb.append(name() + "() {\n");
+    }
+    sb.append(ind(2) + ASTNode.listClass + "<" + 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(2) + "}\n");
+    }
+    sb.append(ind(2) + "return l != null ? Collections.unmodifiableList(l) : 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");
+    sb.append(ind(2) + "assertNotNull(o);\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) + ASTNode.listClass + "<" + otherSide().ofTypeDecl() + "> list2 = o." + otherSide().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(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) + 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) + ASTNode.listClass + "<" + otherSide().ofTypeDecl() + "> list2 = o."
+    + otherSide().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(1) + "}\n");
+
+    // Remove
+    sb.append(ind(1) + "public void " + getTypeUse().decl() + ".remove");
+    if (!useJastAddNames) {
+      sb.append("From");
+    }
+    sb.append(nameCapitalized() + "(" + ofTypeDecl() + " o) {\n");
+    sb.append(ind(2) + "assertNotNull(o);\n");
+    sb.append(ind(2) + ASTNode.listClass + "<" + 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) + "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(2) + "}\n");
+    sb.append(ind(1) + "}\n");
+  }
+
+
+  public void RelationComponent.generateBiManyOne(StringBuilder sb, RelationComponent opposite) {
+    // Get
+    sb.append(ind(1) + "public java.util.List<" + ofTypeDecl() + "> " + getTypeUse().decl() + ".");
+    if (useJastAddNames) {
+      // getXs
+      sb.append("get" + nameCapitalized() + "s() {\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");
+    } else {
+      sb.append(name() + "() {\n");
+    }
+    sb.append(ind(2) + ASTNode.listClass + "<" + 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(2) + "}\n");
+    }
+    sb.append(ind(2) + "return l != null ? Collections.unmodifiableList(l) : 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");
+    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(o);\n");
+    sb.append(ind(2) + "set" + getImplAttributeName() + "(list);\n");
+    sb.append(ind(2) + "o.set" + otherSide().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");
+    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");
+    sb.append(ind(2) + "assertNotNull(o);\n");
+    sb.append(ind(2) + ASTNode.listClass + "<" + 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) + "}\n");
+    sb.append(ind(2) + "}\n");
+    sb.append(ind(1) + "}\n");
+  }
+
+  public void RelationComponent.generateBiOneMany(StringBuilder sb, boolean isOpt) {
+    // Get
+    generateGetOne(sb);
+
+    // Set
+    sb.append(ind(1) + "public " + getTypeUse().decl() + " " + getTypeUse().decl() + ".set" + nameCapitalized()
+      + "(" + ofTypeDecl() + " o) {\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) + "list2.remove(this);\n");
+    sb.append(ind(3) + getImplAttributeField() + "." + "set"
+      + otherSide().getImplAttributeName() + "(list2);\n");
+    sb.append(ind(2) + "}\n");
+    sb.append(ind(2) + "set" + getImplAttributeName() + "(o);\n");
+
+    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) + "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(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
new file mode 100644
index 0000000000000000000000000000000000000000..000c47614ec48abbf110f18090a1d08ec55eb758
--- /dev/null
+++ b/src/main/jastadd/backend/DirectedAPI.jadd
@@ -0,0 +1,153 @@
+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);
+
+    // Set
+    sb.append(ind(1) + "public " + getTypeUse().decl() + " " + getTypeUse().decl());
+    sb.append(".set" + nameCapitalized() + "(" + ofTypeDecl() + " o) {\n");
+    if (!optional) {
+      sb.append(ind(2) + "assertNotNull(o);\n");
+    }
+    sb.append(ind(2) + "set" + getImplAttributeName() + "(o);\n");
+    sb.append(ind(2) + "return this;\n");
+    sb.append(ind(1) + "}\n");
+  }
+
+  public void ManyRelationComponent.generateDirectedAPI(StringBuilder sb) {
+    // Get
+    sb.append(ind(1) + "public java.util.List<" + ofTypeDecl() + "> " + getTypeUse().decl() + ".");
+    if (useJastAddNames) {
+      // getXs
+      sb.append("get" + nameCapitalized() + "s() {\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");
+    } else {
+      sb.append(name() + "() {\n");
+    }
+    sb.append(ind(2) + ASTNode.listClass + "<" + 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(2) + "}\n");
+    }
+    sb.append(ind(2) + "return l != null ? Collections.unmodifiableList(l) : 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");
+    sb.append(ind(2) + "assertNotNull(o);\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(o);\n");
+    sb.append(ind(2) + "set" + getImplAttributeName() + "(list);\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) + 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(1) + "}\n");
+
+    // Remove
+    sb.append(ind(1) + "public void " + getTypeUse().decl() + ".remove");
+    if (!useJastAddNames) {
+      sb.append("From");
+    }
+    sb.append(nameCapitalized() + "(" + ofTypeDecl() + " o) {\n");
+    sb.append(ind(2) + "assertNotNull(o);\n");
+    sb.append(ind(2) + ASTNode.listClass + "<" + 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
new file mode 100644
index 0000000000000000000000000000000000000000..e7690cacca84527435dc09e7e6a1ff6991089795
--- /dev/null
+++ b/src/main/jastadd/backend/LowerBoundCheck.jadd
@@ -0,0 +1,79 @@
+aspect LowerBoundCheck {
+  public void Program.generateLowerBoundCheck(StringBuilder sb) {
+    sb.append(ind(1) + "public boolean ASTNode.violatesLowerBounds() {\n");
+    sb.append(ind(2) + "return !getLowerBoundsViolations().isEmpty();\n");
+    sb.append(ind(1) + "}\n");
+
+    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) + "computeLowerBoundsViolations(list);\n");
+    sb.append(ind(2) + "return list;\n");
+    sb.append(ind(1) + "}\n");
+
+    sb.append(ind(1) + "public void ASTNode.computeLowerBoundsViolations("
+      + "java.util.List<Pair<ASTNode, String>> list) {\n");
+    sb.append(ind(2) + "for (int i = 0; i < getNumChildNoTransform(); i++) {\n");
+    sb.append(ind(3) + "getChildNoTransform(i).computeLowerBoundsViolations(list);\n");
+    sb.append(ind(2) + "}\n");
+    sb.append(ind(1) + "}\n");
+
+    for (TypeDecl td: getTypeDecls()) {
+      td.generateLowerBoundCheck(sb);
+    }
+
+    generatePairClass(sb);
+  }
+
+  public void TypeDecl.generateLowerBoundCheck(StringBuilder sb) {
+    if (!oneRelationComponents().isEmpty()) {
+      sb.append(ind(1) + "public void " + getID() + ".computeLowerBoundsViolations(" +
+        "java.util.List<Pair<ASTNode, String>> list) {\n");
+      for (OneRelationComponent o: oneRelationComponents()) {
+        o.generateLowerBoundCheck(sb);
+      }
+      sb.append(ind(2) + "super.computeLowerBoundsViolations(list);\n");
+      sb.append(ind(1) + "}\n");
+    }
+  }
+
+  public void OneRelationComponent.generateLowerBoundCheck(StringBuilder sb) {
+    sb.append(ind(2) + "if (");
+    if (useJastAddNames) {
+      sb.append("get" + nameCapitalized());
+    } else {
+      sb.append(name());
+    }
+    sb.append("() == null) {\n");
+    sb.append(ind(3) + "list.add(new Pair<>(this, \"" + name() + "\"));\n");
+    sb.append(ind(2) + "}\n");
+  }
+
+
+  public void Program.generatePairClass(StringBuilder sb) {
+    sb.append(ind(1) + "public class Pair<T1, T2> {\n");
+    sb.append(ind(2) + "public final T1 _1;\n");
+    sb.append(ind(2) + "public final T2 _2;\n");
+    // Constructor
+    sb.append(ind(2) + "public Pair(T1 _1, T2 _2) {\n");
+    sb.append(ind(3) + "ASTNode.assertNotNull(_1);\n");
+    sb.append(ind(3) + "ASTNode.assertNotNull(_2);\n");
+    sb.append(ind(3) + "this._1 = _1;\n");
+    sb.append(ind(3) + "this._2 = _2;\n");
+    sb.append(ind(2) + "}\n");
+    // equals
+    sb.append(ind(2) + "public boolean equals(Object other) {\n");
+    sb.append(ind(3) + "if (other instanceof Pair) {\n");
+    sb.append(ind(4) + "Pair<?,?> p = (Pair<?,?>) other;\n");
+    sb.append(ind(4) + "return _1.equals(p._1) && _2.equals(p._2);\n");
+    sb.append(ind(3) + "} else {\n");
+    sb.append(ind(4) + "return false;\n");
+    sb.append(ind(3) + "}\n");
+    sb.append(ind(2) + "}\n");
+    // hashCode
+    sb.append(ind(2) + "public int hashCode() {\n");
+    sb.append(ind(3) + "return 31*_1.hashCode() + _2.hashCode();\n");
+    sb.append(ind(2) + "}\n");
+    sb.append(ind(1) + "}\n");
+  }
+}
diff --git a/src/main/jastadd/backend/NameResolution.jadd b/src/main/jastadd/backend/NameResolution.jadd
new file mode 100644
index 0000000000000000000000000000000000000000..1d373e9e4b4c224bf5c65227182e0c2493197737
--- /dev/null
+++ b/src/main/jastadd/backend/NameResolution.jadd
@@ -0,0 +1,258 @@
+aspect NameResolutionHelper {
+
+  public String Program.generateRewriteToSuperTypeStub() {
+    StringBuilder sb = new StringBuilder();
+    generateRewriteToSuperTypeStub(sb);
+    return sb.toString();
+  }
+
+  public String Program.generateResolverStubs() {
+    StringBuilder sb = new StringBuilder();
+    generateResolverStubs(sb);
+    return sb.toString();
+  }
+
+  public void Program.generateResolverStubs(StringBuilder sb) {
+    sb.append("aspect RefResolverStubs {\n\n");
+
+    for (Relation r: getRelations()) {
+      r.generateContextDependentNameResolution(sb);
+    }
+
+    if (resolverHelper || ASTNode.jsonPointer || ASTNode.manualReferences) {
+      for (TypeDecl decl : getTypeDeclList()) {
+        decl.generateContextIndependentNameResolution(sb);
+        sb.append("\n");
+      }
+    }
+    sb.append("}\n\n");
+
+    if (ASTNode.manualReferences) {
+      sb.append("aspect RefCreatorStubs {\n\n");
+
+      for (Relation r: getRelations()) {
+        r.generateContextDependentRefCreation(sb);
+      }
+
+      generateGenericRefCreation(sb);
+      sb.append("\n");
+
+      for (TypeDecl decl : getTypeDeclList()) {
+        decl.generateContextIndependentRefCreation(sb);
+        sb.append("\n");
+      }
+
+      sb.append("}\n\n");
+    }
+  }
+
+  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(2) + "throw new RuntimeException(\"Generic reference creation not implemented.\");\n");
+    sb.append(ind(1) + "}\n");
+  }
+
+  public void Relation.generateContextDependentRefCreation(StringBuilder sb) {
+    sb.append(ind(1) + "// " + prettyPrint() + "\n");
+    getDirection().generateContextDependentRefCreation(sb);
+    sb.append("\n");
+  }
+
+  public abstract void Direction.generateContextDependentRefCreation(StringBuilder sb);
+  public void RightDirection.generateContextDependentRefCreation(StringBuilder sb) {
+    relation().getLeft().generateContextDependentRefCreation(sb);
+  }
+  public void LeftDirection.generateContextDependentRefCreation(StringBuilder sb) {
+    relation().getRight().generateContextDependentRefCreation(sb);
+  }
+  public void Bidirectional.generateContextDependentRefCreation(StringBuilder sb) {
+    relation().getLeft().generateContextDependentRefCreation(sb);
+    relation().getRight().generateContextDependentRefCreation(sb);
+  }
+
+  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(2) + "// default to context-independent reference creation\n");
+    sb.append(ind(2) + "return target.createReference();\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(2) + "// default to generic reference creation\n");
+    sb.append(ind(2) + "return super.createReference();\n");
+    sb.append(ind(1) + "}\n");
+  }
+
+  public void Program.generateRewriteToSuperTypeStub(StringBuilder sb) {
+
+    sb.append("aspect ReferenceCreation {\n\n");
+
+    for (TypeDecl decl : getTypeDeclList()) {
+      decl.createReferenceCreator(sb);
+    }
+
+    sb.append("}\n\n");
+
+    sb.append("aspect ResolverTrigger {\n\n");
+
+    resolveAll(sb);
+
+    for (TypeDecl decl : getTypeDeclList()) {
+      decl.resolveAll(sb);
+    }
+
+    sb.append("}\n\n");
+
+    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) + "}\n\n");
+
+    for (TypeDecl td: getTypeDecls()) {
+      if (td.needUnresolvedClass()) {
+        td.generateUnresolvedClass(sb);
+      }
+    }
+
+    sb.append("\n}\n");
+  }
+
+  public void TypeDecl.createReferenceCreator(StringBuilder sb) {
+
+    TypeDecl instantiableSubType = instantiableSubType();
+    if (instantiableSubType == null) {
+      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(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(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");
+    if (serializer && !manualReferences) {
+      if (jsonPointer) {
+        sb.append(ind(2) + "return (" + getID() + ") resolveJsonPointer(id);\n");
+      } else {
+        sb.append(ind(2) + "return (" + getID() + ") globallyResolveASTNodeByUID(id);\n");
+      }
+    } else {
+      sb.append(ind(2) + "// perform context independent name resolution here using the id\n");
+      sb.append(ind(2) + "throw new RuntimeException(\"Context-independent name resolution for " + getID() + " not implemented.\");\n");
+    }
+    sb.append(ind(1) + "}\n");
+  }
+
+  public void Relation.generateContextDependentNameResolution(StringBuilder sb) {
+    sb.append(ind(1) + "// " + prettyPrint() + "\n");
+    getDirection().generateContextDependentNameResolution(sb);
+    sb.append("\n");
+  }
+
+  public abstract void Direction.generateContextDependentNameResolution(StringBuilder sb);
+  public void RightDirection.generateContextDependentNameResolution(StringBuilder sb) {
+    relation().getLeft().generateContextDependentNameResolution(sb);
+  }
+  public void LeftDirection.generateContextDependentNameResolution(StringBuilder sb) {
+    relation().getRight().generateContextDependentNameResolution(sb);
+  }
+  public void Bidirectional.generateContextDependentNameResolution(StringBuilder sb) {
+    relation().getLeft().generateContextDependentNameResolution(sb);
+    relation().getRight().generateContextDependentNameResolution(sb);
+  }
+
+  public abstract void RelationComponent.generateContextDependentNameResolution(StringBuilder sb);
+  public void OneRelationComponent.generateContextDependentNameResolution(StringBuilder sb) {
+    generateDirectedContextDependentNameResolution(sb);
+  }
+  public void OptionalRelationComponent.generateContextDependentNameResolution(StringBuilder sb) {
+    // optional relations are resolved in the same way as mandatory relations
+    // TODO maybe, there should be a check if the id to be solved is empty or null
+    generateDirectedContextDependentNameResolution(sb);
+  }
+  public void ManyRelationComponent.generateContextDependentNameResolution(StringBuilder sb) {
+
+    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) + "}\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(2) + "// default to context-independent name resolution\n");
+        sb.append(ind(2) + "return globallyResolve" + ofTypeDecl() + "ByToken(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) + "}\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(2) + "// default to context-independent name resolution\n");
+        sb.append(ind(2) + "return globallyResolve" + ofTypeDecl() + "ByToken(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) + "}\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(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(4) + "}\n");
+        sb.append(ind(3) + "}\n");
+      sb.append(ind(2) + "}\n");
+      sb.append(ind(2) + "resolveAll();\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");
+    for (RelationComponent relationComponent : relationComponents()) {
+      sb.append(ind(2));
+      if (useJastAddNames) {
+        sb.append("get" + relationComponent.nameCapitalized());
+      } else {
+        sb.append(relationComponent.name());
+      }
+      sb.append(relationComponent.isMany() && useJastAddNames ? "List" : "").append("();\n");
+    }
+      sb.append(ind(2) + "super.resolveAll();\n");
+    sb.append(ind(1) + "}\n");
+  }
+}
diff --git a/src/main/jastadd/backend/PrettyPrinting.jadd b/src/main/jastadd/backend/PrettyPrinting.jadd
new file mode 100644
index 0000000000000000000000000000000000000000..49d33b9997f4840e54afe7de2f7dd4ba786bb45d
--- /dev/null
+++ b/src/main/jastadd/backend/PrettyPrinting.jadd
@@ -0,0 +1,42 @@
+aspect PrettyPrinting {
+  public String Relation.prettyPrint() {
+    return "rel "
+      + getLeft().prettyPrint() + " "
+      + getDirection().prettyPrint() + " "
+      + getRight().prettyPrint();
+  }
+  public String RelationComponent.prettyPrint() {
+    if (getID().isEmpty()) {
+      return getTypeUse().toString();
+    } else {
+      return getTypeUse() + "." +  getID();
+    }
+  }
+  public String OptionalRelationComponent.prettyPrint() {
+    return super.prettyPrint() + "?";
+  }
+  public String ManyRelationComponent.prettyPrint() {
+    return super.prettyPrint() + "*";
+  }
+  abstract public String Direction.prettyPrint();
+  public String RightDirection.prettyPrint() {
+    return "->";
+  }
+  public String LeftDirection.prettyPrint() {
+    return "<-";
+  }
+  public String Bidirectional.prettyPrint() {
+    return "<->";
+  }
+
+}
+
+aspect Utils {
+  public String ASTNode.ind(int n) {
+    String s = "";
+    for (int i = 0; i < n; i++) {
+      s += "  ";
+    }
+    return s;
+  }
+}
diff --git a/src/main/jastadd/backend/Serializer.jadd b/src/main/jastadd/backend/Serializer.jadd
new file mode 100644
index 0000000000000000000000000000000000000000..90ba24e30df96ba67eb8d3bec543347648d58879
--- /dev/null
+++ b/src/main/jastadd/backend/Serializer.jadd
@@ -0,0 +1,633 @@
+aspect Serializer {
+
+  protected static String ASTNode.jsonTypeKey = "type";
+  protected static String ASTNode.jsonNodeType = "com.fasterxml.jackson.databind.JsonNode";
+  protected static String ASTNode.jsonNodeTypeAccessor = ".get(\"" + jsonTypeKey + "\").asText()";
+  public String Program.generateJacksonSerializer() {
+    StringBuilder sb = new StringBuilder();
+    generateFromJson(sb);
+    generateToJson(sb);
+    if (jsonPointer) {
+      writeJsonPointer(sb);
+    } else if (manualReferences) {
+      // TODO
+    } else {
+      writeUID(sb);
+    }
+
+    return sb.toString();
+  }
+
+  public void Program.generateFromJson(StringBuilder sb) {
+    sb.append("aspect JsonToModel {\n");
+
+    sb.append(ind(1) + "public class DeserializationException extends Exception {\n");
+    sb.append(ind(2) + "public DeserializationException(String message) {\n");
+    sb.append(ind(3) + "super(message);\n");
+    sb.append(ind(2) + "}\n");
+    sb.append(ind(2) + "public DeserializationException(String message, Exception cause) {\n");
+    sb.append(ind(3) + "super(message, cause);\n");
+    sb.append(ind(2) + "}\n");
+    sb.append(ind(1) + "}\n");
+
+    sb.append(ind(1) + "public void ASTNode.serialize(com.fasterxml.jackson.core.JsonGenerator g) throws SerializationException {\n");
+    sb.append(ind(2) + "serialize(g, null);\n");
+    sb.append(ind(1) + "}\n");
+
+    sb.append(ind(1) + "public void ASTNode.serialize(com.fasterxml.jackson.core.JsonGenerator g, String field) throws SerializationException {\n");
+    sb.append(ind(2) + "throw new SerializationException(\"unable to serialize class \" + this.getClass().getSimpleName());\n");
+    sb.append(ind(1) + "}\n");
+
+
+    sb.append(ind(1) + "public void ASTNode.serialize(java.io.File file) throws SerializationException {\n");
+    sb.append(ind(2) + "serialize(file, false);\n");
+    sb.append(ind(1) + "}\n");
+
+    sb.append(ind(1) + "public void ASTNode.serialize(java.io.File file, boolean humanReadable) throws SerializationException {\n");
+    sb.append(ind(2) + "try {\n");
+    sb.append(ind(3) + "com.fasterxml.jackson.core.JsonFactory factory = new com.fasterxml.jackson.core.JsonFactory();\n");
+    sb.append(ind(3) + "com.fasterxml.jackson.core.JsonGenerator generator = factory.createGenerator(file, com.fasterxml.jackson.core.JsonEncoding.UTF8);\n");
+    sb.append(ind(3) + "if (humanReadable) {\n");
+    sb.append(ind(4) + "generator.setPrettyPrinter(new com.fasterxml.jackson.core.util.DefaultPrettyPrinter());\n");
+    sb.append(ind(3) + "}\n");
+    sb.append(ind(3) + "serialize(generator);\n");
+    sb.append(ind(3) + "generator.close();\n");
+    sb.append(ind(2) + "} catch (java.io.IOException e) {\n");
+    sb.append(ind(3) + "throw new SerializationException(\"Unable to serialize file \" + file.getAbsolutePath(), e);\n");
+    sb.append(ind(2) + "}\n");
+    sb.append(ind(1) + "}\n");
+
+    for (TypeDecl decl : getTypeDeclList()) {
+      decl.deserialize(sb);
+    }
+
+    sb.append("}\n");
+  }
+
+  public void Program.generateToJson(StringBuilder sb) {
+    sb.append("aspect ModelToJson {\n");
+
+    sb.append(ind(1) + "public class SerializationException extends Exception {\n");
+    sb.append(ind(2) + "public SerializationException(String message) {\n");
+    sb.append(ind(3) + "super(message);\n");
+    sb.append(ind(2) + "}\n");
+    sb.append(ind(2) + "public SerializationException(String message, Exception cause) {\n");
+    sb.append(ind(3) + "super(message, cause);\n");
+    sb.append(ind(2) + "}\n");
+    sb.append(ind(1) + "}\n");
+
+    for (TypeDecl decl : getTypeDeclList()) {
+      decl.serialize(sb);
+    }
+
+    sb.append("}\n");
+  }
+
+   public void TypeDecl.serialize(StringBuilder sb) {
+    sb.append(ind(1) + "public void " + getID() + ".serialize(com.fasterxml.jackson.core.JsonGenerator g, String fieldName) throws SerializationException {\n");
+    sb.append(ind(2) + "try {\n");
+    sb.append(ind(3) + "if (fieldName == null) {\n");
+    sb.append(ind(4) + "g.writeStartObject();\n");
+    sb.append(ind(3) + "} else {\n");
+    sb.append(ind(4) + "g.writeObjectFieldStart(fieldName);\n");
+    sb.append(ind(3) + "}\n");
+    if (!jsonPointer && !manualReferences) {
+      sb.append(ind(3) + "g.writeStringField(\"" + jsonTypeKey + "\", \"" + getID() + "\");\n");
+      sb.append(ind(3) + "if (unique$Id() == null) throw new SerializationException(\"The unique identifier of " + getID() + " is missing.\");\n");
+      sb.append(ind(3) + "g.writeStringField(\"id\", unique$Id());\n");
+    }
+    if (componentsTransitive().size() > 0) {
+      sb.append(ind(3) + "g.writeObjectFieldStart(\"children\");\n");
+      for (Component child : componentsTransitive()) {
+        if (child.isNullable()) {
+          String componentAccessor = child.getID();
+          if (child.isList()) {
+            componentAccessor += "List";
+          } else if (child.isOpt()) {
+            componentAccessor += "Opt";
+          }
+          sb.append(ind(3) + "if (get" + componentAccessor + "() == null) throw new SerializationException(\"The component " + child.getID() + " of " + getID() + " is missing.\");\n");
+        }
+        child.serialize(sb, 3);
+      }
+      sb.append(ind(3) + "g.writeEndObject(); // children\n");
+    }
+    if (relationComponentsTransitive().size() > 0) {
+      sb.append(ind(3) + "g.writeObjectFieldStart(\"relations\");\n");
+      for (RelationComponent relation : relationComponentsTransitive()) {
+        relation.serialize(sb, 3);
+      }
+      sb.append(ind(3) + "g.writeEndObject(); // relations\n");
+    }
+    sb.append(ind(3) + "g.writeEndObject();\n");
+    sb.append(ind(2) + "} catch (java.io.IOException e) {\n");
+    sb.append(ind(3) + "throw new SerializationException(\"unable to serialize " + getID() + "\", e);\n");
+    sb.append(ind(2) + "}\n");
+    sb.append(ind(1) + "}\n");
+
+  }
+
+  public abstract void Component.serialize(StringBuilder sb, int indent);
+
+  public void NormalComponent.serialize(StringBuilder sb, int indent) {
+    sb.append(ind(indent) + "get" + getID() + "().serialize(g, \"" + getID() + "\");\n");
+  }
+
+  public void OptComponent.serialize(StringBuilder sb, int indent) {
+    sb.append(ind(indent) + "if (has" + getID() + "()) {\n");
+    sb.append(ind(indent + 1) + "get" + getID() + "().serialize(g, \"" + getID() + "\");\n");
+    sb.append(ind(indent) + "}\n");
+  }
+
+  public void TokenComponent.serialize(StringBuilder sb, int indent) {
+
+    String type = getTypeUse().getID();
+
+    switch (type) {
+      case "float":
+      case "Float":
+      case "double":
+      case "Double":
+      case "int":
+      case "Integer":
+      case "short":
+      case "Short":
+      case "long":
+      case "Long":
+      case "byte":
+      case "Byte":
+        sb.append(ind(indent) + "g.writeNumberField(\"" + getID() + "\", get" + getID() + "());\n");
+        break;
+      case "boolean":
+      case "Boolean":
+        sb.append(ind(indent) + "g.writeBooleanField(\"" + getID() + "\", get" + getID() + "());\n");
+        break;
+      case "char":
+      case "Character":
+        sb.append(ind(indent) + "g.writeStringField(\"" + getID() + "\", Character.toString(get"+ getID() + "()));\n");
+        break;
+      case "String":
+        sb.append(ind(indent) + "g.writeStringField(\"" + getID() + "\", get" + getID() + "());\n");
+        break;
+      case "Instant":
+        sb.append(ind(indent) + "g.writeStringField(\"" + getID() + "\", get" + getID() + "().toString());\n");
+        break;
+      case "Period":
+        sb.append(ind(indent) + "g.writeStringField(\"" + getID() + "\", get" + getID() + "().toString());\n");
+        break;
+      default:
+        // assume that the type is an enum. there is no way of checking this here
+        sb.append(ind(indent) + "g.writeStringField(\"" + getID() + "\", get" + getID() + "().name());\n");
+        // sb.append("throw new DeserializationException(\"Unable to deserialize child node of type \"" + getTypeUse() + "\"\")")
+    }
+  }
+
+  public void NTAComponent.serialize(StringBuilder sb, int indent) {
+    // do not serialize NTA
+  }
+
+  public void NTAListComponent.serialize(StringBuilder sb, int indent) {
+    // do not serialize NTA
+  }
+
+  public void NTAOptComponent.serialize(StringBuilder sb, int indent) {
+    // do not serialize NTA
+  }
+
+  public void ListComponent.serialize(StringBuilder sb, int indent) {
+    sb.append(ind(indent) + "if (getNum" + getID() + "() > 0) {\n");
+    sb.append(ind(indent + 1) + "g.writeArrayFieldStart(\"" + getID() + "\");\n");
+    sb.append(ind(indent + 1) + "for (" + getTypeUse().decl().getID() + " child : get" + getID() + "List()) {\n");
+    sb.append(ind(indent + 2) + "child.serialize(g);\n");
+    sb.append(ind(indent + 1) + "}\n");
+    sb.append(ind(indent + 1) + "g.writeEndArray();\n");
+    sb.append(ind(indent) + "}\n");
+  }
+
+  public void OneRelationComponent.serialize(StringBuilder sb, int indent) {
+    if (useJastAddNames){
+      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");
+      } else {
+        sb.append(ind(indent) + "g.writeStringField(\"" + getID() + "\", get" + getID() + "().unique$Id());\n");
+      }
+    } else {
+      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");
+      } else {
+        sb.append(ind(indent) + "g.writeStringField(\"" + getID() + "\", " + getID() + "().unique$Id());\n");
+      }
+    }
+  }
+
+  public void OptionalRelationComponent.serialize(StringBuilder sb, int indent) {
+    sb.append(ind(indent) + "if (has" + nameCapitalized() + "()) {\n");
+    if (useJastAddNames){
+      if (jsonPointer) {
+        sb.append(ind(indent + 1) + "g.writeStringField(\"" + getID() + "\", get" + getID() + "().jsonPointer());\n");
+      } else if (manualReferences) {
+        sb.append(ind(indent + 1) + "g.writeStringField(\"" + getID() + "\", get" + getID() + "().createReference());\n");
+      } else {
+        sb.append(ind(indent + 1) + "g.writeStringField(\"" + getID() + "\", get" + getID() + "().unique$Id());\n");
+      }
+    } else {
+      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");
+      } else {
+        sb.append(ind(indent + 1) + "g.writeStringField(\"" + getID() + "\", " + getID() + "().unique$Id());\n");
+      }
+    }
+    sb.append(ind(indent) + "}\n");
+  }
+
+  public void ManyRelationComponent.serialize(StringBuilder sb, int indent) {
+    sb.append(ind(indent) + "g.writeArrayFieldStart(\"" + getID() + "\");\n");
+    if (useJastAddNames) {
+      sb.append(ind(indent) + "for (" + ofTypeDecl().getID() + " child : get" + getID() + "List()) {\n");
+    } else {
+      sb.append(ind(indent) + "for (" + ofTypeDecl().getID() + " child : " + getID() + "()) {\n");
+    }
+    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");
+    }else {
+      sb.append(ind(indent + 1) + "g.writeString(child.unique$Id());\n");
+    }
+
+    sb.append(ind(indent) + "}\n");
+    sb.append(ind(indent) + "g.writeEndArray();\n");
+  }
+
+  public void TypeDecl.deserialize(StringBuilder sb) {
+
+    sb.append(ind(1) + "public static " + getID() + " " + getID() + ".deserialize(java.io.File file) throws DeserializationException {\n");
+    sb.append(ind(2) + "try {\n");
+    sb.append(ind(3) + "com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();\n");
+    sb.append(ind(3) + "com.fasterxml.jackson.core.JsonFactory factory = mapper.getFactory();\n");
+    sb.append(ind(3) + "com.fasterxml.jackson.core.JsonParser parser = factory.createParser(file);\n");
+    sb.append(ind(3) + getID() + " result = deserialize((com.fasterxml.jackson.databind.JsonNode)mapper.readTree(parser));\n");
+    sb.append(ind(3) + "parser.close();\n");
+    sb.append(ind(3) + "return result;\n");
+    sb.append(ind(2) + "} catch (java.io.IOException e) {\n");
+    sb.append(ind(3) + "throw new DeserializationException(\"unable to deserialize \" + file.getAbsolutePath(), e);\n");
+    sb.append(ind(2) + "}\n");
+    sb.append(ind(1) + "}\n");
+
+    sb.append(ind(1) + "public static " + getID() + " " + getID() + ".deserialize(" + jsonNodeType + " node) throws DeserializationException {\n");
+    sb.append(ind(2) + getID() + " element;\n");
+    if (getAbstract()) {
+      // switch case between all implementations of the abstract class
+      sb.append(ind(2) + "switch (node" + jsonNodeTypeAccessor + ") {\n");
+      for (TypeDecl subType : subTypeDecls()) {
+        sb.append(ind(3) + "case \"" + subType.getID() + "\":\n");
+        sb.append(ind(4) + "element = " + subType.getID() + ".deserialize(node);\n");
+        sb.append(ind(4) + "break;\n");
+      }
+      sb.append(ind(3) + "default:\n");
+      sb.append(ind(4) + "throw new DeserializationException(\"Unable to deserialize child of unexpected type \" + node" + jsonNodeTypeAccessor + " + \"(" + getID() + " expected)\");\n");
+      sb.append(ind(2) + "}\n");
+    } else {
+      sb.append(ind(2) + "element = new " + getID() + "();\n");
+    }
+
+    if (!jsonPointer && !manualReferences) {
+      // deserialize id
+      sb.append(ind(2) + "if (node.has(\"id\")) {\n");
+      sb.append(ind(3) + "element.unique$Id = node.get(\"id\").asText();\n");
+      sb.append(ind(2) + "}\n");
+    }
+
+    // deserialize containment children
+    if (componentsTransitive().size() > 0) {
+      sb.append(ind(2) + "if (node.has(\"children\")) {\n");
+      sb.append(ind(3) + jsonNodeType + " children = node.get(\"children\");\n");
+      for (Component component : componentsTransitive()) {
+        sb.append(ind(3) + "if (children.has(\"" + component.getID() + "\")) {\n");
+        component.deserialize(sb, 4);
+        sb.append(ind(3) + "}\n");
+      }
+      sb.append(ind(2) + "}\n");
+    }
+    // deserialize non-containment children
+    Set<RelationComponent> relationComponents = relationComponents();
+    if (relationComponents.size() > 0) {
+      sb.append(ind(2) + "if (node.has(\"relations\")) {\n");
+      sb.append(ind(3) + jsonNodeType + " relations = node.get(\"relations\");\n");
+      for (RelationComponent component : relationComponents) {
+        sb.append(ind(3) + "if (relations.has(\"" + component.getID() + "\")) {\n");
+        component.deserialize(sb, 4);
+        sb.append(ind(3) + "}\n");
+      }
+      sb.append(ind(2) + "}\n");
+    }
+
+    sb.append(ind(2) + "return element;\n");
+    sb.append(ind(1) + "}\n");
+
+  }
+
+  public abstract void Component.deserialize(StringBuilder sb, int indent);
+
+  public void NormalComponent.deserialize(StringBuilder sb, int indent) {
+    sb.append(ind(indent) + "element.set" + getID() + "(" + getTypeUse().decl().getID() + ".deserialize(children.get(\"" + getID() + "\")));\n");
+    sb.append(ind(indent - 1) + "} else {\n");
+    sb.append(ind(indent) + "throw new DeserializationException(\"deserializer of missing mandatory child " + getID() + "\");\n");
+  }
+
+  public void OptComponent.deserialize(StringBuilder sb, int indent) {
+    sb.append(ind(indent + 1) + "element.set" + getID() + "(" + getTypeUse().decl().getID() + ".deserialize(children.get(\"" + getID() + "\")));\n");
+  }
+
+  public void TokenComponent.deserialize(StringBuilder sb, int indent) {
+
+    String type = getTypeUse().getID();
+
+    switch (type) {
+      case "float":
+      case "Float":
+        sb.append(ind(indent) + "element.set" + getID() + "(Float.valueOf(children.get(\"" + getID() + "\").asText()));\n");
+        break;
+      case "double":
+      case "Double":
+        sb.append(ind(indent) + "element.set" + getID() + "(children.get(\"" + getID() + "\").asDouble());\n");
+        break;
+      case "int":
+      case "Integer":
+        sb.append(ind(indent) + "element.set" + getID() + "(children.get(\"" + getID() + "\").asInt());\n");
+        break;
+      case "short":
+      case "Short":
+        sb.append(ind(indent) + "element.set" + getID() + "(Short.valueOf(children.get(\"" + getID() + "\").asText()));\n");
+        break;
+      case "long":
+      case "Long":
+        sb.append(ind(indent) + "element.set" + getID() + "(children.get(\"" + getID() + "\").asLong());\n");
+        break;
+      case "byte":
+      case "Byte":
+        sb.append(ind(indent) + "element.set" + getID() + "(Byte.valueOf(children.get(\"" + getID() + "\").asText()));\n");
+        break;
+      case "boolean":
+      case "Boolean":
+        sb.append(ind(indent) + "element.set" + getID() + "(children.get(\"" + getID() + "\").asBoolean());\n");
+        break;
+      case "char":
+      case "Character":
+        sb.append(ind(indent) + "String chars = children.get(\"" + getID() + "\").asText();\n");
+        sb.append(ind(indent) + "if (chars.length() == 1) {\n");
+        sb.append(ind(indent + 2) + "element.set" + getID() + "(chars.charAt(0));\n");
+        sb.append(ind(indent) + "} else {\n");
+        sb.append(ind(indent + 2) + "throw new DeserializationException(\"unable to deserialize char '\" + chars + \"'\");\n");
+        sb.append(ind(indent) + "}\n");
+        break;
+      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");
+        break;
+      case "Period":
+        sb.append(ind(indent) + "element.set" + getID() + "(Period.parse(children.get(\"" + getID() + "\").asText()));\n");
+        break;
+      default:
+        // assume that the type is an enum. there is no way of checking this here
+        sb.append(ind(indent) + "element.set" + getID() + "(Enum.valueOf(" + type + ".class, children.get(\"" + getID() + "\").asText()));\n");
+
+        // sb.append("throw new DeserializationException(\"Unable to deserialize child node of type \"" + getTypeUse() + "\"\")")
+    }
+  }
+
+  public void Component.writeJsonPointer(StringBuilder sb) {
+    // do nothing for other components
+  }
+
+  public void NormalComponent.writeJsonPointer(StringBuilder sb) {
+    sb.append(ind(1) + "eq " + enclosingTypeDecl().getID() + (useJastAddNames?".get":".") + getID() + "().jsonPointerInh() {\n");
+    sb.append(ind(2) + "return this.jsonPointer() + \"/children/" + getID() + "\";\n");
+    sb.append(ind(1) + "}\n");
+  }
+
+  public void ListComponent.writeJsonPointer(StringBuilder sb) {
+    sb.append(ind(1) + "eq " + enclosingTypeDecl().getID() + (useJastAddNames?".get":".") + getID() + "(int index).jsonPointerInh() {\n");
+    sb.append(ind(2) + "return this.jsonPointer() + \"/children/" + getID() + "/\" + index;\n");
+    sb.append(ind(1) + "}\n");
+  }
+
+  public void OptComponent.writeJsonPointer(StringBuilder sb) {
+    sb.append(ind(1) + "eq " + enclosingTypeDecl().getID() + (useJastAddNames?".get":".") + getID() + "().jsonPointerInh() {\n");
+    sb.append(ind(2) + "return this.jsonPointer() + \"/children/" + getID() + "\";\n");
+    sb.append(ind(1) + "}\n");
+  }
+
+  public void NTAComponent.writeJsonPointer(StringBuilder sb) {
+    sb.append(ind(1) + "eq " + enclosingTypeDecl().getID() + (useJastAddNames?".get":".") + getID() + "().jsonPointerInh() {\n");
+    sb.append(ind(2) + "return this.jsonPointer() + \"/children/" + getID() + "\";\n");
+    sb.append(ind(1) + "}\n");
+  }
+
+  public void NTAListComponent.writeJsonPointer(StringBuilder sb) {
+    sb.append(ind(1) + "eq " + enclosingTypeDecl().getID() + (useJastAddNames?".get":".") + getID() + "(int index).jsonPointerInh() {\n");
+    sb.append(ind(2) + "return this.jsonPointer() + \"/children/" + getID() + "/\" + index;\n");
+    sb.append(ind(1) + "}\n");
+  }
+
+  public void NTAOptComponent.writeJsonPointer(StringBuilder sb) {
+    sb.append(ind(1) + "eq " + enclosingTypeDecl().getID() + (useJastAddNames?".get":".") + getID() + "().jsonPointerInh() {\n");
+    sb.append(ind(2) + "return this.jsonPointer() + \"/children/" + getID() + "\";\n");
+    sb.append(ind(1) + "}\n");
+  }
+
+  public void Component.resolveJsonPointer(StringBuilder sb) {
+    // do nothing for other components
+  }
+
+  public void NormalComponent.resolveJsonPointer(StringBuilder sb) {
+    sb.append(ind(4) + "case \"" + getID() + "\":\n");
+    sb.append(ind(5) + "return get" + getID() + "().resolveJsonPointer(pointer, index + 2);\n");
+  }
+
+  public void ListComponent.resolveJsonPointer(StringBuilder sb) {
+    sb.append(ind(4) + "case \"" + getID() + "\":\n");
+    sb.append(ind(5) + "return get" + getID() + "(Integer.valueOf(pointer[index + 2])).resolveJsonPointer(pointer, index + 3);\n");
+  }
+
+  public void OptComponent.resolveJsonPointer(StringBuilder sb) {
+    sb.append(ind(4) + "case \"" + getID() + "\":\n");
+    sb.append(ind(5) + "return get" + getID() + "().resolveJsonPointer(pointer, index + 2);\n");
+  }
+
+  public void NTAComponent.resolveJsonPointer(StringBuilder sb) {
+    sb.append(ind(4) + "case \"" + getID() + "\":\n");
+    sb.append(ind(5) + "return get" + getID() + "().resolveJsonPointer(pointer, index + 2);\n");
+  }
+
+  public void NTAListComponent.resolveJsonPointer(StringBuilder sb) {
+    sb.append(ind(4) + "case \"" + getID() + "\":\n");
+    sb.append(ind(5) + "return get" + getID() + "(Integer.valueOf(pointer[index + 2])).resolveJsonPointer(pointer, index + 3);\n");
+  }
+
+  public void NTAOptComponent.resolveJsonPointer(StringBuilder sb) {
+    sb.append(ind(4) + "case \"" + getID() + "\":\n");
+    sb.append(ind(5) + "return get" + getID() + "().resolveJsonPointer(pointer, index + 2);\n");
+  }
+
+  public void TypeDecl.resolveJsonPointer(StringBuilder sb) {
+    sb.append(ind(1) + "eq " + getID() + ".resolveJsonPointer(String[] pointer, int index) {\n");
+    sb.append(ind(2) + "if (pointer.length == 0 || pointer.length == index) {\n");
+    sb.append(ind(3) + "return this;\n");
+    sb.append(ind(2) + "} else if (pointer.length == index + 1) {\n");
+    sb.append(ind(3) + "throw new RuntimeException(\"there is only one child called \" + pointer[index]);\n");
+    sb.append(ind(2) + "} else {\n");
+    sb.append(ind(3) + "switch (pointer[index + 1]) {\n");
+
+    for (Component c: getComponentList()) {
+      c.resolveJsonPointer(sb);
+    }
+
+    sb.append(ind(4) + "default:\n");
+    sb.append(ind(5) + "return super.resolveJsonPointer(pointer, index);\n");
+    sb.append(ind(3) + "}\n");
+    sb.append(ind(2) + "}\n");
+    sb.append(ind(1) + "}\n");
+  }
+
+  public void Program.writeJsonPointer(StringBuilder sb) {
+    sb.append("aspect JsonPointer {\n");
+
+    sb.append(ind(1) + "syn String ASTNode.jsonPointer() {\n");
+    sb.append(ind(2) + "if (getParent() == null) {\n");
+    sb.append(ind(3) + "return \"\";\n");
+    sb.append(ind(2) + "} else {\n");
+    sb.append(ind(3) + "return jsonPointerInh();\n");
+    sb.append(ind(2) + "}\n");
+    sb.append(ind(1) + "}\n");
+    sb.append(ind(1) + "inh String ASTNode.jsonPointerInh();\n");
+
+    // define json pointer for each component
+    for (TypeDecl td: getTypeDeclList()) {
+      for (Component c: td.getComponentList()) {
+        c.writeJsonPointer(sb);
+      }
+    }
+
+    sb.append(ind(1) + "syn ASTNode ASTNode.resolveJsonPointer(String pointer) = root().resolveJsonPointer(pointer.split(\"/\"), 1);\n");
+    sb.append(ind(1) + "ASTNode ASTNode.root() {\n");
+    sb.append(ind(2) + "if (getParent() == null) return this;\n");
+    sb.append(ind(2) + "else return getParent().root();\n");
+    sb.append(ind(1) + "}\n");
+    sb.append(ind(1) + "syn ASTNode ASTNode.resolveJsonPointer(String[] pointer, int index) {\n");
+    sb.append(ind(2) + "if (index < pointer.length) {\n");
+    sb.append(ind(3) + "throw new RuntimeException(\"found wrong child  \" + pointer[index + 1] + \" in class \" + this.getClass().getSimpleName());\n");
+    sb.append(ind(2) + "} else {\n");
+    sb.append(ind(3) + "throw new RuntimeException(\"Cannot resolve JSON pointer for class \" + this.getClass().getSimpleName());\n");
+    sb.append(ind(2) + "}\n");
+    sb.append(ind(1) + "}\n");
+
+    // resolve JSON pointers in each nonterminal
+    for (TypeDecl td: getTypeDeclList()) {
+      td.resolveJsonPointer(sb);
+    }
+
+    sb.append("}\n");
+  }
+
+  public void Program.writeUID(StringBuilder sb) {
+    sb.append("aspect UID {\n");
+    sb.append(ind(1) + "class UIDProvider {\n");
+    sb.append(ind(2) + "private static long nextUID = 0;\n");
+    sb.append(ind(2) + "public static String getUID() {\n");
+    sb.append(ind(3) + "return String.valueOf(nextUID++);\n");
+    sb.append(ind(2) + "}\n");
+    sb.append(ind(1) + "}\n");
+    sb.append("\n");
+    sb.append(ind(1) + "protected String ASTNode.unique$Id = null;\n");
+    sb.append("\n");
+    sb.append(ind(1) + "protected String ASTNode.unique$Id() {\n");
+    sb.append(ind(2) + "String customUID = customID();\n");
+    sb.append(ind(2) + "if (customUID == null) {\n");
+    sb.append(ind(3) + "if (unique$Id == null) {\n");
+    sb.append(ind(4) + "unique$Id = UIDProvider.getUID();\n");
+    sb.append(ind(3) + "}\n");
+    sb.append(ind(3) + "return unique$Id;\n");
+    sb.append(ind(2) + "} else {\n");
+    sb.append(ind(3) + "return customUID;\n");
+    sb.append(ind(2) + "}\n");
+    sb.append(ind(1) + "}\n");
+    sb.append("\n");
+    sb.append(ind(1) + "protected String ASTNode.customID() {\n");
+    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(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(2) + "}\n");
+    sb.append(ind(1) + "}\n");
+    sb.append("\n");
+    sb.append(ind(1) + "java.util.Map<String, java.lang.ref.WeakReference<ASTNode>> ASTNode.uid$Map;\n");
+    sb.append("\n");
+    sb.append(ind(1) + "java.util.Map<String, java.lang.ref.WeakReference<ASTNode>> ASTNode.uid$Map() {\n");
+    sb.append(ind(2) + "if (uid$Map == null) {\n");
+    sb.append(ind(3) + "uid$Map = uid$Map(new java.util.HashMap());\n");
+    sb.append(ind(2) + "}\n");
+    sb.append(ind(2) + "return uid$Map;\n");
+    sb.append(ind(1) + "}\n");
+    sb.append(ind(1) + "protected java.util.Map<String, java.lang.ref.WeakReference<ASTNode>> ASTNode.uid$Map(java.util.Map<String, java.lang.ref.WeakReference<ASTNode>> map) {\n");
+    sb.append(ind(2) + "if (!(this instanceof " + jastAddListType + " || this instanceof Opt)) {\n");
+    sb.append(ind(3) + "if (map.keySet().contains(unique$Id())) {\n");
+    sb.append(ind(4) + "throw new RuntimeException(new SerializationException(\"UID \" + this.unique$Id() + \" is assigned to both \" + this.getClass().getSimpleName() + \":\" + this.hashCode() + \" and \" + map.get(unique$Id()).getClass().getSimpleName() + \":\" + map.get(unique$Id()).hashCode()));\n");
+    sb.append(ind(3) + "} else {\n");
+    sb.append(ind(4) + "map.put(this.unique$Id, new java.lang.ref.WeakReference(this));\n");
+    sb.append(ind(3) + "}\n");
+    sb.append(ind(2) + "}\n");
+    sb.append(ind(2) + "for (ASTNode child : astChildren()) {\n");
+    sb.append(ind(3) + "child.uid$Map(map);\n");
+    sb.append(ind(2) + "}\n");
+    sb.append(ind(2) + "return map;\n");
+    sb.append(ind(1) + "}\n");
+    sb.append("}\n");
+  }
+
+  public void NTAComponent.deserialize(StringBuilder sb, int indent) {
+    // do not serialize NTA
+  }
+
+  public void NTAListComponent.deserialize(StringBuilder sb, int indent) {
+    // do not serialize NTA
+  }
+
+  public void NTAOptComponent.deserialize(StringBuilder sb, int indent) {
+    // do not serialize NTA
+  }
+
+  public void ListComponent.deserialize(StringBuilder sb, int indent) {
+    sb.append(ind(indent) + "for (" + jsonNodeType + " child : children.get(\"" + getID() + "\")) {\n");
+    sb.append(ind(indent + 1) + "element.add" + getID() + "(" + getTypeUse().decl().getID() + ".deserialize(child));\n");
+    sb.append(ind(indent) + "}\n");
+  }
+
+  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 - 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");
+  }
+
+  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) + "}\n");
+  }
+}