diff --git a/preprocessor/examples/TrainBenchmarkGen.ast b/preprocessor/examples/TrainBenchmarkGen.ast
new file mode 100644
index 0000000000000000000000000000000000000000..bfa2e21e11673fc80a3619d10572c2c761574993
--- /dev/null
+++ b/preprocessor/examples/TrainBenchmarkGen.ast
@@ -0,0 +1,10 @@
+RailwayContainer ::= Route* Region*;
+abstract RailwayElement ::= <id:int>;
+Region : RailwayElement ::= TrackElement* Sensor*;
+Semaphore : RailwayElement ::= <Signal:Signal>;
+Route : RailwayElement ::= <Active:boolean> SwitchPosition* <_impl_requires:RefList<Sensor>> <_impl_entry:Semaphore> <_impl_exit:Semaphore>;
+SwitchPosition : RailwayElement ::= <Position:Position> <_impl_target:Switch>;
+Sensor : RailwayElement ::= <_impl_monitors:RefList<TrackElement>>;
+abstract TrackElement : RailwayElement ::= <_impl_monitoredBy:RefList<Sensor>> <_impl_connectsTo:RefList<TrackElement>>;
+Segment : TrackElement ::= <Length:int> Semaphore*;
+Switch : TrackElement ::= <CurrentPosition:Position> <_impl_positions:RefList<SwitchPosition>>;
diff --git a/preprocessor/examples/TrainBenchmarkGen.jadd b/preprocessor/examples/TrainBenchmarkGen.jadd
new file mode 100644
index 0000000000000000000000000000000000000000..45550a2a64c5af4a4726290d4e3799893fbb3923
--- /dev/null
+++ b/preprocessor/examples/TrainBenchmarkGen.jadd
@@ -0,0 +1,254 @@
+import java.util.ArrayList;
+import java.util.Collections;
+aspect RelAstAPI {
+  public Route.Route(int id, boolean Active, List<SwitchPosition> SwitchPosition) {
+    setid(id);
+    setActive(Active);
+    setSwitchPositionList(SwitchPosition);
+  }
+  public SwitchPosition.SwitchPosition(int id, Position Position) {
+    setid(id);
+    setPosition(Position);
+  }
+  public Sensor.Sensor(int id) {
+    setid(id);
+  }
+  public TrackElement.TrackElement(int id) {
+    setid(id);
+  }
+  public Segment.Segment(int id, int Length, List<Semaphore> Semaphore) {
+    setid(id);
+    setLength(Length);
+    setSemaphoreList(Semaphore);
+  }
+  public Switch.Switch(int id, Position CurrentPosition) {
+    setid(id);
+    setCurrentPosition(CurrentPosition);
+  }
+  // rel Route.requires* -> Sensor
+  public java.util.List<Sensor> Route.requires() {
+    RefList<Sensor> l = get_impl_requires();
+    return l != null ? Collections.unmodifiableList(l) : Collections.emptyList();
+  }
+  public void Route.addToRequires(Sensor o) {
+    assertNotNull(o);
+    RefList<Sensor> list = get_impl_requires();
+    if (list == null) {
+      list = new RefList<>();
+    }
+    list.add(o);
+    set_impl_requires(list);
+  }
+  public void Route.removeFromRequires(Sensor o) {
+    assertNotNull(o);
+    RefList<Sensor> list = get_impl_requires();
+    if (list != null && list.remove(o)) {
+      set_impl_requires(list);
+    }
+  }
+
+  // rel Route.entry? -> Semaphore
+  public Semaphore Route.entry() {
+    return get_impl_entry();
+  }
+  public void Route.setEntry(Semaphore o) {
+    set_impl_entry(o);
+  }
+  public boolean Route.hasEntry() {
+    return entry() != null;
+  }
+  public void Route.clearEntry() {
+    setEntry(null);
+  }
+
+  // rel Route.exit? -> Semaphore
+  public Semaphore Route.exit() {
+    return get_impl_exit();
+  }
+  public void Route.setExit(Semaphore o) {
+    set_impl_exit(o);
+  }
+  public boolean Route.hasExit() {
+    return exit() != null;
+  }
+  public void Route.clearExit() {
+    setExit(null);
+  }
+
+  // rel Sensor.monitors* <-> TrackElement.monitoredBy*
+  public java.util.List<TrackElement> Sensor.monitors() {
+    RefList<TrackElement> l = get_impl_monitors();
+    return l != null ? Collections.unmodifiableList(l) : Collections.emptyList();
+  }
+  public void Sensor.addToMonitors(TrackElement o) {
+    assertNotNull(o);
+    RefList<TrackElement> list = get_impl_monitors();
+    if (list == null) {
+      list = new RefList<>();
+    }
+    RefList<Sensor> list2 = o.get_impl_monitoredBy();
+    if (list2 == null) {
+      list2 = new RefList<>();
+    }
+    list.add(o);
+    list2.add(this);
+    set_impl_monitors(list);
+    o.set_impl_monitoredBy(list2);
+  }
+  public void Sensor.removeFromMonitors(TrackElement o) {
+    assertNotNull(o);
+    RefList<TrackElement> list = get_impl_monitors();
+    if (list != null && list.remove(o)) {
+      RefList<Sensor> list2 = o.get_impl_monitoredBy();
+      if (list2 != null) list2.remove(this);
+      set_impl_monitors(list);
+      o.set_impl_monitoredBy(list2);
+    }
+  }
+  public java.util.List<Sensor> TrackElement.monitoredBy() {
+    RefList<Sensor> l = get_impl_monitoredBy();
+    return l != null ? Collections.unmodifiableList(l) : Collections.emptyList();
+  }
+  public void TrackElement.addToMonitoredBy(Sensor o) {
+    assertNotNull(o);
+    RefList<Sensor> list = get_impl_monitoredBy();
+    if (list == null) {
+      list = new RefList<>();
+    }
+    RefList<TrackElement> list2 = o.get_impl_monitors();
+    if (list2 == null) {
+      list2 = new RefList<>();
+    }
+    list.add(o);
+    list2.add(this);
+    set_impl_monitoredBy(list);
+    o.set_impl_monitors(list2);
+  }
+  public void TrackElement.removeFromMonitoredBy(Sensor o) {
+    assertNotNull(o);
+    RefList<Sensor> list = get_impl_monitoredBy();
+    if (list != null && list.remove(o)) {
+      RefList<TrackElement> list2 = o.get_impl_monitors();
+      if (list2 != null) list2.remove(this);
+      set_impl_monitoredBy(list);
+      o.set_impl_monitors(list2);
+    }
+  }
+
+  // rel TrackElement.connectsTo* -> TrackElement
+  public java.util.List<TrackElement> TrackElement.connectsTo() {
+    RefList<TrackElement> l = get_impl_connectsTo();
+    return l != null ? Collections.unmodifiableList(l) : Collections.emptyList();
+  }
+  public void TrackElement.addToConnectsTo(TrackElement o) {
+    assertNotNull(o);
+    RefList<TrackElement> list = get_impl_connectsTo();
+    if (list == null) {
+      list = new RefList<>();
+    }
+    list.add(o);
+    set_impl_connectsTo(list);
+  }
+  public void TrackElement.removeFromConnectsTo(TrackElement o) {
+    assertNotNull(o);
+    RefList<TrackElement> list = get_impl_connectsTo();
+    if (list != null && list.remove(o)) {
+      set_impl_connectsTo(list);
+    }
+  }
+
+  // rel SwitchPosition.target <-> Switch.positions*
+  public Switch SwitchPosition.target() {
+    return get_impl_target();
+  }
+  public void SwitchPosition.setTarget(Switch o) {
+    assertNotNull(o);
+    if (get_impl_target() != null) {
+      RefList<SwitchPosition> list2 = get_impl_target().get_impl_positions();
+      list2.remove(this);
+      get_impl_target().set_impl_positions(list2);
+    }
+    set_impl_target(o);
+    RefList<SwitchPosition> list = o.get_impl_positions();
+    if (list == null) {
+      list = new RefList<>();
+    }
+    list.add(this);
+    o.set_impl_positions(list);
+  }
+  public java.util.List<SwitchPosition> Switch.positions() {
+    RefList<SwitchPosition> l = get_impl_positions();
+    return l != null ? Collections.unmodifiableList(l) : Collections.emptyList();
+  }
+  public void Switch.addToPositions(SwitchPosition o) {
+    assertNotNull(o);
+    if (o != null && o.get_impl_target() != null) {
+      RefList<SwitchPosition> list2 = o.get_impl_target().get_impl_positions();
+      if (list2.remove(o))
+        o.get_impl_target().set_impl_positions(list2);
+    }
+    RefList<SwitchPosition> list = get_impl_positions();
+    if (list == null) {
+      list = new RefList<>();
+    }
+    list.add(o);
+    set_impl_positions(list);
+    o.set_impl_target(this);
+  }
+  public void Switch.removeFromPositions(SwitchPosition o) {
+    assertNotNull(o);
+    RefList<SwitchPosition> list = get_impl_positions();
+    if (list != null && list.remove(o)) {
+      set_impl_positions(list);
+      if (o.get_impl_target() == this) {
+        o.set_impl_target(null);
+      }
+    }
+  }
+
+  public boolean ASTNode.violateLowerBounds() {
+    return !getLowerBoundsViolations().isEmpty();
+  }
+  public java.util.List<Pair<ASTNode, String>> ASTNode.getLowerBoundsViolations() {
+    ArrayList<Pair<ASTNode, String>> list = new ArrayList<>();
+    computeLowerBoundsViolations(list);
+    return list;
+  }
+  public void ASTNode.computeLowerBoundsViolations(java.util.List<Pair<ASTNode, String>> list) {
+    for (int i = 0; i < getNumChildNoTransform(); i++) {
+      getChildNoTransform(i).computeLowerBoundsViolations(list);
+    }
+  }
+  public void SwitchPosition.computeLowerBoundsViolations(java.util.List<Pair<ASTNode, String>> list) {
+    if (target() == null) {
+      list.add(new Pair<>(this, "target"));
+    }
+    super.computeLowerBoundsViolations(list);
+  }
+  public class Pair<T1, T2> {
+    public final T1 _1;
+    public final T2 _2;
+    public Pair(T1 _1, T2 _2) {
+      ASTNode.assertNotNull(_1);
+      ASTNode.assertNotNull(_2);
+      this._1 = _1;
+      this._2 = _2;
+    }
+    public boolean equals(Object other) {
+      if (other instanceof Pair) {
+        Pair<?,?> p = (Pair<?,?>) other;
+        return _1.equals(p._1) && _2.equals(p._2);
+      } else {
+        return false;
+      }
+    }
+    public int hashCode() {
+      return 31*_1.hashCode() + _2.hashCode();
+    }
+  }
+  public static void ASTNode.assertNotNull(Object obj) {
+    if (obj == null) {
+      throw new NullPointerException();
+    }
+  }
+}
diff --git a/preprocessor/relast-compiler.jar b/preprocessor/relast-compiler.jar
new file mode 100644
index 0000000000000000000000000000000000000000..6a244fdbedc5381641587a94a6a902a7ddca7e14
Binary files /dev/null and b/preprocessor/relast-compiler.jar differ