Skip to content
Snippets Groups Projects
Backend.jadd 62.25 KiB
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.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 TokenComponent.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 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 " + toTypeDecl() + " " + toTypeDecl());
    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() + "> " + toTypeDecl() + ".");
    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() + "> " + toTypeDecl());
      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 " + toTypeDecl() + ".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 " + toTypeDecl() + ".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 " + toTypeDecl() + ".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() + " " + toTypeDecl() + ".");
    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 " + toTypeDecl());
    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 " + toTypeDecl());
    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 " + toTypeDecl() + " " + toTypeDecl());
    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() + "> " + toTypeDecl() + ".");
    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() + "> " + toTypeDecl());
      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 + "<" + toTypeDecl() + "> 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 " + toTypeDecl() + ".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 " + toTypeDecl() + ".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 " + toTypeDecl() + ".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() + "> " + toTypeDecl() + ".");
    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() + "> " + toTypeDecl());
      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) + toTypeDecl() + " 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 " + toTypeDecl() + ".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 " + toTypeDecl() + ".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 " + toTypeDecl() + ".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 " + toTypeDecl() + " " + toTypeDecl() + ".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 + "<" + toTypeDecl() + "> 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 + "<" + toTypeDecl() + "> 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) {
      for (TypeDecl decl : getTypeDeclList()) {
        decl.generateContextIndependentNameResolution(sb);
        sb.append("\n");
      }
    }
    sb.append("}\n\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) {
      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 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() + " " + toTypeDecl() + ".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 " + toTypeDecl() + ".resolve" + nameCapitalized() + "ByToken(String id, int position);\n");
      sb.append(ind(1) + "syn " + ofTypeDecl() + " " + toTypeDecl() + ".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() + " " + toTypeDecl() + ".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 " + toTypeDecl() + ".resolve" + nameCapitalized() + "ByToken(String id);\n");
      sb.append(ind(1) + "syn " + ofTypeDecl() + " " + toTypeDecl() + ".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.generateSerializer() {
    StringBuilder sb = new StringBuilder();
    generateFromJson(sb);
    generateToJson(sb);
    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");
    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 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){
      sb.append(ind(indent) + "g.writeStringField(\""+getID()+"\", get" + getID() + "().unique$Id());\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){
      sb.append(ind(indent + 1) + "g.writeStringField(\"" + getID() + "\", get" + getID() + "().unique$Id());\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");
    }
    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");
    }

    // 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 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 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 Bidirectional.prettyPrint() {
    return "<->";
  }

}

aspect Utils {
  public String ASTNode.ind(int n) {
    String s = "";
    for (int i = 0; i < n; i++) {
      s += "  ";
    }
    return s;
  }
}