diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index a4809bd6690274cea3db131f6ed49289898027cb..e86bf3282801b54c1a6a592dc6b388f595684e6a 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -12,7 +12,7 @@ variables:
 #  # Improve performance with overlayfs.
 #  DOCKER_DRIVER: overlay2
   GRADLE_OPTS: "-Dorg.gradle.daemon=false"
-  TEST_REPORTS: "eraser-base/build/reports/tests/"
+  TEST_REPORTS: "*/build/test-results/**/TEST-*.xml"
   TEST_LOG: "eraser-base/logs/eraser-test.log"
   JACOCO_REPORT: "*/build/reports/jacoco/all-tests/jacoco*Report.xml"
   # settings for influxdb
@@ -55,8 +55,9 @@ test:
     when: always
     paths:
       - $TEST_LOG
-      - $TEST_REPORTS
       - $JACOCO_REPORT
+    reports:
+      junit: $TEST_REPORTS
 
 coverage:
   image: python:3.7.1-alpine
@@ -121,4 +122,4 @@ pages:
     - master
   artifacts:
     paths:
-    - "public"
+      - "public"
diff --git a/buildSrc/src/main/groovy/eraser.java-common-conventions.gradle b/buildSrc/src/main/groovy/eraser.java-common-conventions.gradle
index 35d106e2f655c9e9d6b627913c4e9d68ba82480f..690297198bac473226b40d4da608316b7997e531 100644
--- a/buildSrc/src/main/groovy/eraser.java-common-conventions.gradle
+++ b/buildSrc/src/main/groovy/eraser.java-common-conventions.gradle
@@ -23,6 +23,10 @@ tasks.named('test') {
   }
 }
 
+tasks.withType(JavaCompile) {
+    options.deprecation = true
+}
+
 task allTests(type: Test, dependsOn: testClasses) {
   description = 'Run every test'
   group = 'verification'
diff --git a/eraser-base/src/main/jastadd/Expression.jrag b/eraser-base/src/main/jastadd/Expression.jrag
index e3d6a6b6b01effb2b014bbe85b7360aea5314a3a..9e1d4f52d57a1aab055a397983578f2e38f356ab 100644
--- a/eraser-base/src/main/jastadd/Expression.jrag
+++ b/eraser-base/src/main/jastadd/Expression.jrag
@@ -30,4 +30,15 @@ aspect Expression {
   eq Designator.eval() = getItem().getStateAsDouble();
   eq ParenthesizedNumberExpression.eval() = getOperand().eval();
   eq NumberLiteralExpression.eval() = getValue();
+
+  // is-X
+  syn boolean Expression.isLogicalExpression() = false;
+  eq LogicalExpression.isLogicalExpression() = true;
+  syn boolean Expression.isNumberExpression() = false;
+  eq NumberExpression.isNumberExpression() = true;
+  // as-X
+  syn LogicalExpression Expression.asLogicalExpression() = null;
+  eq LogicalExpression.asLogicalExpression() = this;
+  syn NumberExpression Expression.asNumberExpression() = null;
+  eq NumberExpression.asNumberExpression() = this;
 }
diff --git a/eraser-base/src/main/jastadd/Item.jrag b/eraser-base/src/main/jastadd/Item.jrag
index 94ebdce1bc189ccbfa73b767ccacd9488936fc5c..967cc061d56453edbe95dee5cc4eba3583eb8c5a 100644
--- a/eraser-base/src/main/jastadd/Item.jrag
+++ b/eraser-base/src/main/jastadd/Item.jrag
@@ -375,6 +375,8 @@ aspect ItemHandling {
   syn boolean ItemWithDoubleState.isItemWithDoubleState() = true;
   syn boolean Item.isDateTimeItem() = false;
   syn boolean DateTimeItem.isDateTimeItem() = true;
+  syn boolean Item.isActivityItem() = false;
+  syn boolean ActivityItem.isActivityItem() = true;
 
   //--- as$ItemType ---
   // those attributes will raise a ClassCastException if called on the wrong item type. But what else can we do?
@@ -471,7 +473,7 @@ aspect ItemHandling {
       //Instant lastChange = this.
   }
 
-  
+
 
 
 
@@ -519,5 +521,5 @@ aspect ItemHandling {
   eq ItemUpdateColor.getNewStateAsString() = getNewHSB().toString();
   eq ItemUpdateDouble.getNewStateAsString() = Double.toString(getNewValue());
 
-  
+
 }
diff --git a/eraser-base/src/main/jastadd/LastChanged.jrag b/eraser-base/src/main/jastadd/LastChanged.jrag
index e79bfe7c8c394154f78307ca629ff64ddfd4eb2f..2ac7dfc95e247aa988f9c518b64c687477b9bbb5 100644
--- a/eraser-base/src/main/jastadd/LastChanged.jrag
+++ b/eraser-base/src/main/jastadd/LastChanged.jrag
@@ -1,20 +1,18 @@
 aspect LastChanged {
-    public void LastChanged.afterStateChangeProcessed() {
-        this.setValue(Instant.now());
-    }
-
-    public boolean LastChanged.checkStateProcessingTime(FrequencySetting FrequencySetting) {
-        if (FrequencySetting==null) {
-            return true;
-        }
-        double frequency = FrequencySetting.getEventProcessingFrequency();
-        Instant lastStateChange = this.getValue();
-        if (lastStateChange==null) {
-            return true;
-        }
-
-        return lastStateChange.toEpochMilli() + (1/frequency)*1000 < Instant.now().toEpochMilli();
+  public void LastChanged.afterStateChangeProcessed() {
+    this.setValue(Instant.now());
+  }
 
+  public boolean LastChanged.checkStateProcessingTime(FrequencySetting FrequencySetting) {
+    if (FrequencySetting == null) {
+      return true;
+    }
+    double frequency = FrequencySetting.getEventProcessingFrequency();
+    Instant lastStateChange = this.getValue();
+    if (lastStateChange == null) {
+      return true;
     }
+    return lastStateChange.toEpochMilli() + (1 / frequency) * 1000 < Instant.now().toEpochMilli();
+  }
 
-}
\ No newline at end of file
+}
diff --git a/eraser-base/src/main/jastadd/ModelStatistics.jrag b/eraser-base/src/main/jastadd/ModelStatistics.jrag
index 9ed07bd1947dd5c83792f7371d2c209e88ea3329..786f00e4b5b48ee9857d5b4154cee90aa6803880 100644
--- a/eraser-base/src/main/jastadd/ModelStatistics.jrag
+++ b/eraser-base/src/main/jastadd/ModelStatistics.jrag
@@ -1,19 +1,10 @@
 aspect ModelStatistics {
 
-  //--- numChannels ---
-  syn int SmartHomeEntityModel.numChannels() {
-    int sum = 0;
-    for (Thing thing : getThingList()) {
-      sum += thing.getNumChannel();
-    }
-    return sum;
-  }
-
   //--- description ---
   syn String SmartHomeEntityModel.description() = "["
     + this.getNumThingType() + " thing type(s), "
     + this.getNumChannelType() + " channel type(s), "
-    + this.numChannels() + " channel(s), "
+    + this.getNumChannel() + " channel(s), "
     + this.getNumThing() + " thing(s), "
     + this.getNumGroup() + " group(s), "
     + this.items().size() + " item(s)]";
diff --git a/eraser-base/src/main/jastadd/Navigation.jrag b/eraser-base/src/main/jastadd/Navigation.jrag
index 984e1029e1aa7817396d6676f7772d9bc824d1ba..acadcc8b1a9bec73378c33aea27dc95b9ee94ecb 100644
--- a/eraser-base/src/main/jastadd/Navigation.jrag
+++ b/eraser-base/src/main/jastadd/Navigation.jrag
@@ -7,7 +7,27 @@ aspect Navigation {
   //--- items ---
   syn java.util.List<Item> SmartHomeEntityModel.items() {
     java.util.List<Item> result = new java.util.ArrayList<>();
-    addItems(result, getGroupList());
+    getGroupList().forEach(group -> result.addAll(group.items()));
+    result.addAll(unknownGroup().items());
+    return result;
+  }
+  syn java.util.List<Item> Group.items() {
+    java.util.List<Item> result = new java.util.ArrayList<>();
+    getItemList().forEach(item -> result.add(item));
+    getGroupList().forEach(subgroup -> result.addAll(subgroup.items()));
+    return result;
+  }
+
+  //--- groups ---
+  syn java.util.List<Group> SmartHomeEntityModel.groups() {
+    java.util.List<Group> result = new java.util.ArrayList<>();
+    getGroupList().forEach(group -> result.addAll(group.groups()));
+    return result;
+  }
+  syn java.util.List<Group> Group.groups() {
+    java.util.List<Group> result = new java.util.ArrayList<>();
+    getGroupList().forEach(subgroup -> result.addAll(subgroup.groups()));
+    result.add(this);
     return result;
   }
 
@@ -16,9 +36,22 @@ aspect Navigation {
   inh Group Item.enclosingGroup();
   eq Group.getItem().enclosingGroup() = this;
   eq Group.getGroup().enclosingGroup() = this;
+  eq SmartHomeEntityModel.unknownGroup().enclosingGroup() = null;
   eq SmartHomeEntityModel.getGroup().enclosingGroup() = null;
   eq SmartHomeEntityModel.getActivityItem().enclosingGroup() = null;
 
+  //--- containingSmartHomeEntityModel ---
+  inh SmartHomeEntityModel Thing.containingSmartHomeEntityModel();
+  inh SmartHomeEntityModel Group.containingSmartHomeEntityModel();
+  inh SmartHomeEntityModel Item.containingSmartHomeEntityModel();
+  inh SmartHomeEntityModel ThingType.containingSmartHomeEntityModel();
+  inh SmartHomeEntityModel Parameter.containingSmartHomeEntityModel();
+  inh SmartHomeEntityModel ChannelType.containingSmartHomeEntityModel();
+  inh SmartHomeEntityModel Channel.containingSmartHomeEntityModel();
+  inh SmartHomeEntityModel ItemCategory.containingSmartHomeEntityModel();
+  inh SmartHomeEntityModel ChannelCategory.containingSmartHomeEntityModel();
+  eq SmartHomeEntityModel.getChild().containingSmartHomeEntityModel() = this;
+
   //--- relevantFrequencySetting ---
   syn FrequencySetting Group.relevantFrequencySetting() {
     // first, use value defined on group itself, if any
@@ -52,11 +85,6 @@ aspect Navigation {
     return null;
   }
 
-  //--- addItems ---
-  private void SmartHomeEntityModel.addItems(java.util.List<Item> result, JastAddList<Group> groups) {
-    groups.forEach(group -> group.getItemList().forEach(item -> result.add(item)));
-  }
-
   //--- parameters ---
   syn java.util.Set<Parameter> SmartHomeEntityModel.parameters() {
     java.util.Set<Parameter> result = new java.util.TreeSet<>(modelElementComparator());
@@ -71,10 +99,6 @@ aspect Navigation {
     return result;
   }
 
-  //--- containingThing ---
-  inh Thing Channel.containingThing();
-  eq Thing.getChannel().containingThing() = this;
-
   //--- containingNeuralNetwork ---
   inh NeuralNetworkRoot OutputLayer.containingNeuralNetwork();
   eq NeuralNetworkRoot.getOutputLayer().containingNeuralNetwork() = this;
@@ -85,7 +109,7 @@ aspect Navigation {
       return Optional.empty();
     }
     Channel channel = this.getChannel();
-    Thing thing = channel.containingThing();
+    Thing thing = channel.getThing();
     return Optional.of(thing);
   }
 
diff --git a/eraser-base/src/main/jastadd/Printing.jrag b/eraser-base/src/main/jastadd/Printing.jrag
index 80661e43e7f221d38f8c5e242abe0bd3753259ec..ba63b98b78dfeaaa99a4df1fbcdc117ac05ec7ff 100644
--- a/eraser-base/src/main/jastadd/Printing.jrag
+++ b/eraser-base/src/main/jastadd/Printing.jrag
@@ -1,3 +1,5 @@
+import java.util.StringJoiner;
+
 aspect Printing {
   syn String ASTNode.prettyPrint() { throw new UnsupportedOperationException(); }
 
@@ -12,7 +14,7 @@ aspect Printing {
     return sb.toString();
   }
 
-//--- SmartHomeEntityModel.prettyPrint() ---
+  //--- SmartHomeEntityModel.prettyPrint() ---
   eq SmartHomeEntityModel.prettyPrint() {
     StringBuilder sb = new StringBuilder();
     for (Thing t : getThingList()) {
@@ -21,9 +23,12 @@ aspect Printing {
     for (Item i : items()) {
       sb.append(i.prettyPrint());
     }
-    for (Group g : getGroupList()) {
+    for (Group g : groups()) {
       sb.append(g.prettyPrint());
     }
+    if (unknownGroup().getNumItem() > 0 ) {
+      sb.append(unknownGroup().prettyPrint());
+    }
     for (ThingType tt : getThingTypeList()) {
       sb.append(tt.prettyPrint());
     }
@@ -39,17 +44,17 @@ aspect Printing {
     return sb.toString();
   }
 
-//Thing: id="" label="" type="" channels=["CHANNEL_ID", "CHANNEL_ID"] ;
+  // Thing: id="" label="" type="" channels=["CHANNEL_ID", "CHANNEL_ID"] ;
   eq Thing.prettyPrint() {
     return new MemberPrinter("Thing")
         .addRequired("id", getID())
         .addNonDefault("label", getLabel())
         .addRequired("type", getType(), ThingType::getID)
-        .addIds("channels", getNumChannel(), getChannelList())
+        .addIds("channels", getChannelList())
         .build();
   }
 
-//ITEM_TYPE Item: id="" label="" state="" category="" topic="";
+  // ITEM_TYPE Item: id="" label="" state="" category="" topic="";
   eq Item.prettyPrint() {
     return new MemberPrinter(prettyPrintType())
         .addRequired("id", getID())
@@ -57,9 +62,7 @@ aspect Printing {
         .addRequired("state", getStateAsString())
         .addOptional("category", hasCategory(), () -> getCategory().getName())
         .addOptional("topic", hasTopic(), () -> getTopic().getTopicString())
-        .addNodes("metaData", getNumMetaData(), getMetaDataList(),
-                  md -> "\"" + md.getKey() + "\":\"" + md.getValue() + "\"",
-                  MemberPrinter.ListBracketType.CURLY)
+        .addOptionalPrettyPrint(getMetaData())
         .build();
   }
 
@@ -77,6 +80,7 @@ aspect Printing {
   eq SwitchItem.prettyPrintType() = "Switch Item" ;
   eq ActivityItem.prettyPrintType() = "Activity Item" ;
   eq DefaultItem.prettyPrintType() = "Item" ;
+  eq ItemPrototype.prettyPrintType() = "!! prototype not converted !!" ;
 
   // special ActivityItem printing. Always omit state.
   eq ActivityItem.prettyPrint() {
@@ -85,15 +89,24 @@ aspect Printing {
         .addNonDefault("label", getLabel())
         .addOptional("category", hasCategory(), () -> getCategory().getName())
         .addOptional("topic", hasTopic(), () -> getTopic().getTopicString())
-        .addNodes("metaData", getNumMetaData(), getMetaDataList(),
-                  md -> "\"" + md.getKey() + "\":\"" + md.getValue() + "\"",
-                  MemberPrinter.ListBracketType.CURLY)
+        .addOptionalPrettyPrint(getMetaData())
         .build();
   }
 
+  // MetaData: metaData={"key": "value", "key": "value"}
+  eq MetaData.prettyPrint() {
+    if (getNumKeyValuePair() == 0) {
+      return "";
+    }
+    StringJoiner sj = new StringJoiner(", ", " metaData={", "}");
+    for (KeyValuePair keyValuePair : getKeyValuePairList()) {
+      sj.add("\"" + keyValuePair.getKey() + "\":\"" + keyValuePair.getValue() + "\"");
+    }
+    return sj.toString();
+  }
 
-//Group: id="" groups=["GROUP_ID", "GROUP_ID"] items=["ITEM_ID", "ITEM_ID"] aggregation=AGG;
-//       AGG either '"agg-name"', or '"agg-name" ("param1", "param2")'
+  // Group: id="" groups=["GROUP_ID", "GROUP_ID"] items=["ITEM_ID", "ITEM_ID"] aggregation=AGG;
+  //        AGG either '"agg-name"', or '"agg-name" ("param1", "param2")'
   eq Group.prettyPrint() {
     return new MemberPrinter("Group")
         .addRequired("id", getID())
@@ -118,18 +131,18 @@ aspect Printing {
     return sb.toString();
   }
 
-//ThingType: id="" label="" description="" parameters=["PARAM_ID", "PARAM_ID"] channelTypes=["CHANNEL_TYPE_ID", "CHANNEL_TYPE_ID"];
+  // ThingType: id="" label="" description="" parameters=["PARAM_ID", "PARAM_ID"] channelTypes=["CHANNEL_TYPE_ID", "CHANNEL_TYPE_ID"];
   eq ThingType.prettyPrint() {
     return new MemberPrinter("ThingType")
         .addRequired("id", getID())
         .addNonDefault("label", getLabel())
         .addNonDefault("description", getDescription())
-        .addIds("parameters", getNumParameter(), getParameters())
+        .addIds("parameters", getParameters())
         .addIds("channelTypes", getChannelTypes())
         .build();
   }
 
-//Parameter: id="" label="" description="" type="" default="" required;
+  // Parameter: id="" label="" description="" type="" default="" required;
   eq Parameter.prettyPrint() {
     return new MemberPrinter("Parameter")
         .addRequired("id", getID())
@@ -142,7 +155,7 @@ aspect Printing {
         .build();
   }
 
-//ChannelType: id="" label="" description="" itemType="" category="" readyOnly;
+  // ChannelType: id="" label="" description="" itemType="" category="" readyOnly;
   eq ChannelType.prettyPrint() {
     return new MemberPrinter("ChannelType")
         .addRequired("id", getID())
@@ -154,11 +167,12 @@ aspect Printing {
         .build();
   }
 
+  // ChannelCategory
   syn String DefaultChannelCategory.prettyPrint() = getValue().name();
-
   syn String SimpleChannelCategory.prettyPrint() = getValue();
+  syn String ReferringChannelCategory.prettyPrint() = getChannelCategory().prettyPrint();
 
-//Channel: id="" type="" links=["ITEM_ID", "ITEM_ID"];
+  // Channel: id="" type="" links=["ITEM_ID", "ITEM_ID"];
   eq Channel.prettyPrint() {
     return new MemberPrinter("Channel")
         .addRequired("id", getID())
@@ -167,7 +181,7 @@ aspect Printing {
         .build();
   }
 
-//ExternalHost: "hostName:port"
+  // ExternalHost: "hostName:port"
   syn String ExternalHost.prettyPrint(int defaultPort) {
     if (getPort() == defaultPort) {
       // default port, do not add
@@ -176,7 +190,7 @@ aspect Printing {
     return getHostName() + ":" + getPort();
   }
 
-//Mqtt: incoming="" outgoing="" host="";
+  // Mqtt: incoming="" outgoing="" host="";
   eq MqttRoot.prettyPrint() {
     return new MemberPrinter("Mqtt")
         .addNonDefault("incoming", getIncomingPrefix())
@@ -185,7 +199,7 @@ aspect Printing {
         .build();
   }
 
-//Influx: user="" password="" dbName="" host="" ;
+  // Influx: user="" password="" dbName="" host="" ;
   eq InfluxRoot.prettyPrint() {
     return new MemberPrinter("Influx")
         .addNonDefault("user", getUser(), DEFAULT_USER)
@@ -195,7 +209,7 @@ aspect Printing {
         .build();
   }
 
-// Activities: { index: "name" }
+  // Activities: { index: "name" }
   eq MachineLearningRoot.prettyPrint() {
     return new MemberPrinter("ML")
         .addNodes("activities", getNumActivity(), getActivityList(),
@@ -204,7 +218,7 @@ aspect Printing {
         .build();
   }
 
-// Expressions
+  // Expressions
   syn String ParenthesizedNumberExpression.prettyPrint() = "(" + getOperand().prettyPrint() + ")";
   syn String NumberLiteralExpression.prettyPrint() = Double.toString(getValue());
   syn String AddExpression.prettyPrint() = "(" + getLeftOperand().prettyPrint() + " + " + getRightOperand().prettyPrint() + ")";
diff --git a/eraser-base/src/main/jastadd/Resolving.jrag b/eraser-base/src/main/jastadd/Resolving.jrag
index 2d3a99f76bb63228bf41c315c9c9a86a768653cd..f93ac84b73cb0032a2eec15a0f949ee9a35c9e1b 100644
--- a/eraser-base/src/main/jastadd/Resolving.jrag
+++ b/eraser-base/src/main/jastadd/Resolving.jrag
@@ -12,11 +12,9 @@ aspect Resolving {
 
   //--- resolveChannel ---
   syn java.util.Optional<Channel> SmartHomeEntityModel.resolveChannel(String channelId) {
-    for (Thing thing : this.getThingList()) {
-      for (Channel channel : thing.getChannelList()) {
-        if (channel.getID().equals(channelId)) {
-          return java.util.Optional.of(channel);
-        }
+    for (Channel channel : this.getChannelList()) {
+      if (channel.getID().equals(channelId)) {
+        return java.util.Optional.of(channel);
       }
     }
     return java.util.Optional.empty();
@@ -32,13 +30,33 @@ aspect Resolving {
     return java.util.Optional.empty();
   }
 
+  //--- resolveDefaultChannelCategory ---
+  syn java.util.Optional<DefaultChannelCategory> SmartHomeEntityModel.resolveDefaultChannelCategory(String name) {
+    for (DefaultChannelCategory dcc : this.defaultChannelCategoryList()) {
+      if (dcc.getValue().name().equals(name)) {
+        return java.util.Optional.of(dcc);
+      }
+    }
+    return java.util.Optional.empty();
+  }
+
+  //--- resolveParameter ---
+  syn java.util.Optional<Parameter> SmartHomeEntityModel.resolveParameter(String parameterId) {
+    for (Parameter parameter : this.getParameterList()) {
+      if (parameter.getID().equals(parameterId)) {
+        return java.util.Optional.of(parameter);
+      }
+    }
+    return java.util.Optional.empty();
+  }
+
   //--- resolveItem ---
   syn java.util.Optional<Item> SmartHomeEntityModel.resolveItem(String itemId) {
     if ("activity".equals(itemId)) {
       return Optional.of(getActivityItem());
     }
     for (Item item : items()) {
-      if (item.getID().equals(itemId)) {
+      if (item.getID().equals(itemId) && !item.isItemPlaceHolder()) {
         return java.util.Optional.of(item);
       }
     }
@@ -108,12 +126,51 @@ aspect Resolving {
   }
 
   // implementing resolving for relations
-  refine RefResolverStubs eq StateSyncGroup.resolveTargetItemByToken(String id, int position) {
+  // _._ -> Item
+  refine RefResolverStubs eq ASTNode.globallyResolveItemByToken(String id) {
     return getRoot().getSmartHomeEntityModel().resolveItem(id).orElseThrow(() -> new RuntimeException("Item '" + id + "' not found!"));
   }
 
+  // _._ -> FrequencySetting
   refine RefResolverStubs eq ASTNode.globallyResolveFrequencySettingByToken(String id) {
     return getRoot().resolveFrequencySetting(id).orElseThrow(() -> new RuntimeException("FrequencySetting '" + id + "' not found!"));
   }
 
+  // Thing.Channel* -> Channel
+  refine RefResolverStubs eq Thing.resolveChannelByToken(String id, int position) {
+    return containingSmartHomeEntityModel().resolveChannel(id).orElseThrow(() -> new RuntimeException("Channel '" + id + "' not found!"));
+  }
+
+  // Thing.Type -> ThingType
+  refine RefResolverStubs eq Thing.resolveTypeByToken(String id) {
+    return containingSmartHomeEntityModel().resolveThingType(id).orElseThrow(() -> new RuntimeException("ThingType '" + id + "' not found!"));
+  }
+
+  // ThingType.Parameter* -> Parameter
+  refine RefResolverStubs eq ThingType.resolveParameterByToken(String id, int position) {
+    return containingSmartHomeEntityModel().resolveParameter(id).orElseThrow(() -> new RuntimeException("Parameter '" + id + "' not found!"));
+  }
+
+  // _._ -> ChannelType
+  refine RefResolverStubs eq ASTNode.globallyResolveChannelTypeByToken(String id) {
+    return getRoot().getSmartHomeEntityModel().resolveChannelType(id).orElseThrow(() -> new RuntimeException("ChannelType '" + id + "' not found!"));
+  }
+
+  // ReferringChannelCategory.ChannelCategory -> DefaultChannelCategory
+  refine RefResolverStubs eq ReferringChannelCategory.resolveChannelCategoryByToken(String id) {
+    return containingSmartHomeEntityModel().resolveDefaultChannelCategory(id).orElseThrow(() -> new RuntimeException("DefaultChannelCategory '" + id + "' not found!"));
+  }
+
+  // Item.Topic? <-> MqttTopic.Item*
+  refine RefResolverStubs eq Item.resolveTopicByToken(String id) {
+    // not an actual resolving, also adds the new mqtt-topic under mqtt-root
+    return getRoot().getMqttRoot().getOrCreateMqttTopic(id);
+  }
+
+  // Item.Category? <-> ItemCategory.Items*
+  refine RefResolverStubs eq Item.resolveCategoryByToken(String id) {
+    // not an actual resolving, also adds the new item-category under containing model
+    return containingSmartHomeEntityModel().getOrCreateCategoryByName(id);
+  }
+
 }
diff --git a/eraser-base/src/main/jastadd/Util.jrag b/eraser-base/src/main/jastadd/Util.jrag
index de0dfb9ef17a41f46885ca2432ced96dd5b5c449..201582e3b9facf3029ffe799a4f69c66c3671174 100644
--- a/eraser-base/src/main/jastadd/Util.jrag
+++ b/eraser-base/src/main/jastadd/Util.jrag
@@ -34,4 +34,35 @@ aspect Util {
     model.setMachineLearningRoot(new MachineLearningRoot());
     return model;
   }
+  /**
+   * Performs a safe full traversal of the tree using getChild to trigger rewrites
+   */
+  public void ASTNode.doSafeFullTraversal() {
+    for (int i = 0; i < getNumChild(); i++) {
+      ASTNode child = getChild(i);
+      if (child != null) {
+        child.doSafeFullTraversal();
+      }
+    }
+  }
+
+  /**
+   * removes the object from the AST, i.e. removes the reference from its parent to the object
+   *
+   * Please note that any intrinsic non-containment relations to the object are not removed.
+   * @return true, if the object had a parent.
+   */
+  public boolean ASTNode.removeSelf() {
+    if (getParent() == null) {
+      return false;
+    } else {
+      for (int childIndex = 0; childIndex < getParent().numChildren(); childIndex++) {
+        if (getParent().getChild(childIndex) == this) {
+          getParent().removeChild(childIndex);
+          return true;
+        }
+      }
+    }
+    throw new RuntimeException("unable to remove child, because it was not contained in its parent!");
+  }
 }
diff --git a/eraser-base/src/main/jastadd/eraser.flex b/eraser-base/src/main/jastadd/eraser.flex
index 621d9d044c0dc2d82695221d28c1da16f6dadd5c..54dff35b310e573c8efe700596f57b36b276235a 100644
--- a/eraser-base/src/main/jastadd/eraser.flex
+++ b/eraser-base/src/main/jastadd/eraser.flex
@@ -57,7 +57,7 @@ Comment = "//" [^\n\r]+
 "Influx"       { return sym(Terminals.INFLUX); }
 "ML"           { return sym(Terminals.ML); }
 "Rule"         { return sym(Terminals.RULE); }
-"SyncState"    { return sym(Terminals.SYNCSTATE); }
+"SyncState"    { return sym(Terminals.SYNC_STATE); }
 // special items (group already has a token definition)
 "Activity"     { return sym(Terminals.ACTIVITY); }
 "Color"        { return sym(Terminals.COLOR); }
diff --git a/eraser-base/src/main/jastadd/eraser.parser b/eraser-base/src/main/jastadd/eraser.parser
index f58525a329bc0e858283e1b5f95ef14297a3f14c..019e0c309eba8f991dc5749da407be5261a7a7ea 100644
--- a/eraser-base/src/main/jastadd/eraser.parser
+++ b/eraser-base/src/main/jastadd/eraser.parser
@@ -1,61 +1,137 @@
 %header {:
 package de.tudresden.inf.st.eraser.jastadd.parser;
 import de.tudresden.inf.st.eraser.jastadd.model.*;
-import de.tudresden.inf.st.eraser.jastadd.model.Action;
-import de.tudresden.inf.st.eraser.parser.EraserParserHelper;
-import de.tudresden.inf.st.eraser.jastadd.model.StateSyncGroup;
+import de.tudresden.inf.st.eraser.jastadd.model.Action;  // explicit import need to distinguish from beaver.Action
+import de.tudresden.inf.st.eraser.jastadd.scanner.EraserScanner;
+import de.tudresden.inf.st.eraser.util.JavaUtils;
 import java.util.Map;
 import java.util.HashMap;
 :} ;
 
 %embed {:
-  private EraserParserHelper eph = new EraserParserHelper();
   private static <T extends ASTNode<?>> void insertZero(JastAddList<T> listNode, T child) {
     listNode.insertChild(child, 0);
   }
 
+  private static boolean checkUnusedElements = true;
+  private static Root initialRoot = null;
+
   /**
-   * Post processing step after parsing a model, to resolve all references within the model.
-   * @throws java.util.NoSuchElementException if a reference can not be resolved
+   * Changes the behavior of the parser to check for unused elements. (Default: true)
+   * @param checkUnusedElements <code>true</code> to check for unused elements, <code>false</code> to skip the check
    */
-  public void resolveReferences() {
-    eph.resolveReferences();
+  public static void setCheckUnusedElements(boolean checkUnusedElements) {
+    checkUnusedElements = checkUnusedElements;
+  }
+
+  public static void setNextInitialRoot(Root root) {
+    initialRoot = root;
+  }
+
+  private String ensureTrailingSlash(String input) {
+    return input != null && !input.isEmpty() && !input.endsWith("/") ? input + "/" : input;
+  }
+
+  private GroupPlaceHolder createGroupPlaceHolder(String id) {
+    GroupPlaceHolder result = new GroupPlaceHolder();
+    result.setID(id);
+    return result;
+  }
+
+  private ItemPlaceHolder createItemPlaceHolder(String id) {
+    ItemPlaceHolder result = new ItemPlaceHolder();
+    result.setID(id);
+    return result;
+  }
+
+  public Root parseRoot(EraserScanner scanner) throws java.io.IOException, beaver.Parser.Exception {
+    if (checkUnusedElements) {
+      //fillUnused();
+    }
+    Root root = (Root) parse(scanner);
+    root.treeResolveAll();
+    root.doSafeFullTraversal();
+
+    // resolve ItemPlaceHolders
+    for (Group g : root.getSmartHomeEntityModel().groups()) {
+      JastAddList<Item> items = g.getItemList();
+      for (int i = 0; i < g.getNumItem(); i++) {
+        Item item = items.getChild(i);
+        Item realItem = item.realItem();
+        if (item != realItem) {
+          realItem.removeSelf();
+          item.removeSelf();
+          items.insertChild(realItem, i);
+        }
+      }
+    }
+    // resolve GroupPlaceHolders
+    for (Group g : root.getSmartHomeEntityModel().groups()) {
+      JastAddList<Group> groups = g.getGroupList();
+      for (int i = 0; i < g.getNumGroup(); i++) {
+        Group group = groups.getChild(i);
+        Group realGroup = group.realGroup();
+        if (group != realGroup) {
+          realGroup.removeSelf();
+          group.removeSelf();
+          groups.insertChild(realGroup, i);
+        }
+      }
+    }
+    return root;
+  }
+
+  public Expression parseExpression(EraserScanner scanner, short alt_goal) throws java.io.IOException, beaver.Parser.Exception {
+    Expression exp = (Expression) parse(scanner, alt_goal);
+
+    // create some rule containing the expression to make resolving work
+    Root root = initialRoot != null ? initialRoot : Root.createEmptyRoot();
+    initialRoot = null;
+    Rule rule = new Rule();
+    switch (alt_goal) {
+      case AltGoals.logical_expression:
+        ExpressionCondition condition = new ExpressionCondition();
+        condition.setLogicalExpression(exp.asLogicalExpression());
+        rule.addCondition(condition);
+        break;
+      case AltGoals.number_expression:
+        SetStateFromExpression action = new SetStateFromExpression();
+        action.setNumberExpression(exp.asNumberExpression());
+        rule.addAction(action);
+        break;
+      default:
+        throw new IllegalArgumentException("Wrong goal passed " + alt_goal);
+    }
+    root.addRule(rule);
+    exp.treeResolveAll();
+    exp.doSafeFullTraversal();
+    return exp;
   }
 
 :} ;
 
-%goal goal;
+%goal root;
 %goal number_expression;
 %goal logical_expression;
 
-Root goal =
-     thing.t goal.r                     {: insertZero(r.getSmartHomeEntityModel().getThingList(), t); return r; :}
-  |  item.i goal.r                      {: return r; :}
-  |  group.g goal.r                     {: insertZero(r.getSmartHomeEntityModel().getGroupList(), g); return r; :}
-  |  state_sync_group.ss goal.r         {: r.addRule(ss); return r; :}
-  |  thing_type.tt goal.r               {: insertZero(r.getSmartHomeEntityModel().getThingTypeList(), tt); return r; :}
-  |  parameter goal.r                   {: return r; :}
-  |  channel_type.ct goal.r             {: insertZero(r.getSmartHomeEntityModel().getChannelTypeList(), ct); return r; :}
-  |  channel.c goal.r                   {: return r; :}
-  |  mqtt_root.mr goal.r                {: r.setMqttRoot(mr); return r; :}
-  |  influx_root.ir goal.r              {: r.setInfluxRoot(ir); return r; :}
-  |  machine_learning_root.ml goal.r    {: r.setMachineLearningRoot(ml); return r; :}
-  |  rule.rule goal.r                   {: r.addRule(rule); return r; :}
-  |  frequency_setting.ip goal.r         {: r.addFrequencySetting(ip); return r; :}
-  |  thing.t                            {: return eph.createRoot(t); :}
-  |  item.i                             {: return eph.createRoot(); :}
-  |  group.g                            {: return eph.createRoot(g); :}
-  |  state_sync_group.ss                {: return eph.createRoot(ss); :}
-  |  thing_type.tt                      {: return eph.createRoot(tt); :}
-  |  parameter                          {: return eph.createRoot(); :}
-  |  channel_type.ct                    {: return eph.createRoot(ct); :}
-  |  channel.c                          {: return eph.createRoot(); :}
-  |  mqtt_root.mr                       {: return eph.createRoot(mr); :}
-  |  influx_root.ir                     {: return eph.createRoot(ir); :}
-  |  machine_learning_root.ml           {: return eph.createRoot(ml); :}
-  |  rule.rule                          {: return eph.createRoot(rule); :}
-  |  frequency_setting.ip                {: return eph.createRoot(ip); :}
-  ;
+Root root =
+     thing.t root.r                     {: insertZero(r.getSmartHomeEntityModel().getThingList(), t); return r; :}
+  |  item.i root.r                      {: insertZero(r.getSmartHomeEntityModel().unknownGroup().getItemList(), i); return r; :}
+  |  group.g root.r                     {: insertZero(r.getSmartHomeEntityModel().getGroupList(), g); return r; :}
+  |  state_sync_group.ss root.r         {: r.addRule(ss); return r; :}
+  |  thing_type.tt root.r               {: insertZero(r.getSmartHomeEntityModel().getThingTypeList(), tt); return r; :}
+  |  parameter.p root.r                 {: insertZero(r.getSmartHomeEntityModel().getParameterList(), p); return r; :}
+  |  channel_type.ct root.r             {: insertZero(r.getSmartHomeEntityModel().getChannelTypeList(), ct); return r; :}
+  |  channel.c root.r                   {: insertZero(r.getSmartHomeEntityModel().getChannelList(), c); return r; :}
+  |  mqtt_root.mr root.r                {: r.setMqttRoot(mr); return r; :}
+  |  influx_root.ir root.r              {: r.setInfluxRoot(ir); return r; :}
+  |  machine_learning_root.ml root.r    {: r.setMachineLearningRoot(ml); return r; :}
+  |  rule.rule root.r                   {: r.addRule(rule); return r; :}
+  |  frequency_setting.ip root.r        {: r.addFrequencySetting(ip); return r; :}
+  |                                     {: return Root.createEmptyRoot(); :}
+  ;
+
+/// Expressions ///
 
 %left RB_ROUND;
 %left MULT, DIV;
@@ -66,86 +142,81 @@ Root goal =
 %left AND;
 
 NumberExpression number_expression =
-    LB_ROUND number_expression.a MULT  number_expression.b RB_ROUND     {: return new MultExpression(a, b); :}
-  | LB_ROUND number_expression.a DIV   number_expression.b RB_ROUND     {: return new DivExpression(a, b); :}
-  | LB_ROUND number_expression.a PLUS  number_expression.b RB_ROUND     {: return new AddExpression(a, b); :}
-  | LB_ROUND number_expression.a MINUS number_expression.b RB_ROUND     {: return new SubExpression(a, b); :}
-  | LB_ROUND number_expression.a POW number_expression.b RB_ROUND       {: return new PowerExpression(a, b); :}
-  | literal_expression.l                                                {: return l; :}
-  | designator.d                                                        {: return d; :}
-  | LB_ROUND number_expression.e RB_ROUND                               {: return new ParenthesizedNumberExpression(e); :}
+    LB_ROUND number_expression.a MULT  number_expression.b RB_ROUND    {: return new MultExpression(a, b); :}
+  | LB_ROUND number_expression.a DIV   number_expression.b RB_ROUND    {: return new DivExpression(a, b); :}
+  | LB_ROUND number_expression.a PLUS  number_expression.b RB_ROUND    {: return new AddExpression(a, b); :}
+  | LB_ROUND number_expression.a MINUS number_expression.b RB_ROUND    {: return new SubExpression(a, b); :}
+  | LB_ROUND number_expression.a POW number_expression.b RB_ROUND      {: return new PowerExpression(a, b); :}
+  | literal_expression.l                                               {: return l; :}
+  | designator.d                                                       {: return d; :}
+  | LB_ROUND number_expression.e RB_ROUND                              {: return new ParenthesizedNumberExpression(e); :}
   ;
 
 LogicalExpression logical_expression =
-    LB_ROUND logical_expression.a AND   logical_expression.b RB_ROUND     {: return new AndExpression(a, b); :}
-  | LB_ROUND logical_expression.a OR    logical_expression.b RB_ROUND     {: return new OrExpression(a, b); :}
-  | EXCLAMATION logical_expression.e                                      {: return new NotExpression(e); :}
-  | LB_ROUND logical_expression.e RB_ROUND                                {: return new ParenthesizedLogicalExpression(e); :}
-  | LB_ROUND number_expression.a LT    number_expression.b RB_ROUND       {: return new ComparingExpression(a, b, ComparatorType.LessThan); :}
-  | LB_ROUND number_expression.a LE    number_expression.b RB_ROUND       {: return new ComparingExpression(a, b, ComparatorType.LessOrEqualThan); :}
-  | LB_ROUND number_expression.a EQ    number_expression.b RB_ROUND       {: return new ComparingExpression(a, b, ComparatorType.Equals); :}
-  | LB_ROUND number_expression.a NE    number_expression.b RB_ROUND       {: return new ComparingExpression(a, b, ComparatorType.NotEquals); :}
-  | LB_ROUND number_expression.a GE    number_expression.b RB_ROUND       {: return new ComparingExpression(a, b, ComparatorType.GreaterOrEqualThan); :}
-  | LB_ROUND number_expression.a GT    number_expression.b RB_ROUND       {: return new ComparingExpression(a, b, ComparatorType.GreaterThan); :}
+    LB_ROUND logical_expression.a AND   logical_expression.b RB_ROUND    {: return new AndExpression(a, b); :}
+  | LB_ROUND logical_expression.a OR    logical_expression.b RB_ROUND    {: return new OrExpression(a, b); :}
+  | EXCLAMATION logical_expression.e                                     {: return new NotExpression(e); :}
+  | LB_ROUND logical_expression.e RB_ROUND                               {: return new ParenthesizedLogicalExpression(e); :}
+  | LB_ROUND number_expression.a LT    number_expression.b RB_ROUND      {: return new ComparingExpression(a, b, ComparatorType.LessThan); :}
+  | LB_ROUND number_expression.a LE    number_expression.b RB_ROUND      {: return new ComparingExpression(a, b, ComparatorType.LessOrEqualThan); :}
+  | LB_ROUND number_expression.a EQ    number_expression.b RB_ROUND      {: return new ComparingExpression(a, b, ComparatorType.Equals); :}
+  | LB_ROUND number_expression.a NE    number_expression.b RB_ROUND      {: return new ComparingExpression(a, b, ComparatorType.NotEquals); :}
+  | LB_ROUND number_expression.a GE    number_expression.b RB_ROUND      {: return new ComparingExpression(a, b, ComparatorType.GreaterOrEqualThan); :}
+  | LB_ROUND number_expression.a GT    number_expression.b RB_ROUND      {: return new ComparingExpression(a, b, ComparatorType.GreaterThan); :}
   ;
 
 NumberLiteralExpression literal_expression =
-    INTEGER.n             {: return new NumberLiteralExpression(Integer.parseInt(n)); :}
-  | REAL.n                {: return new NumberLiteralExpression(Double.parseDouble(n)); :}
+    INTEGER.n    {: return new NumberLiteralExpression(Integer.parseInt(n)); :}
+  | REAL.n       {: return new NumberLiteralExpression(Double.parseDouble(n)); :}
   ;
 
 Designator designator =
-    NAME.n                    {: return eph.createDesignator(n); :}
+    NAME.n    {: Designator result = new Designator(); result.setItem(Item.createRef(n)); return result; :}
   ;
 
+/// SHEM ///
+
 Thing thing =
-    THING COLON thing_body.tb SEMICOLON {: return tb; :}
+    THING COLON thing_body.tb SEMICOLON    {: return tb; :}
   ;
 
 // Thing: id="" label="" type="" channels=["CHANNEL_ID", "CHANNEL_ID"] ;
 Thing thing_body =
-     ID EQUALS TEXT.n thing_body.t                        {: return eph.setID(t, n); :}
+     ID EQUALS TEXT.n thing_body.t                        {: t.setID(n); return t; :}
   |  LABEL EQUALS TEXT.n thing_body.t                     {: t.setLabel(n); return t; :}
-  |  TYPE EQUALS TEXT.n thing_body.t                      {: return eph.addThingType(t, n); :}
-  |  CHANNELS EQUALS string_list.channels thing_body.t    {: return eph.setChannels(t, channels); :}
-  |  ID EQUALS TEXT.n                       {: return eph.setID(new Thing(), n); :}
-  |  LABEL EQUALS TEXT.n                    {: Thing t = new Thing(); t.setLabel(n); return t; :}
-  |  TYPE EQUALS TEXT.n                     {: return eph.addThingType(new Thing(), n); :}
-  |  CHANNELS EQUALS string_list.channels   {: return eph.setChannels(new Thing(), channels); :}
+  |  TYPE EQUALS TEXT.n thing_body.t                      {: t.setType(ThingType.createRef(n)); return t; :}
+  |  CHANNELS EQUALS string_list.channels thing_body.t    {: channels.forEach(ch -> t.addChannel(Channel.createRef(ch))); return t; :}
+  |                                                       {: return new Thing(); :}
   ;
 
 Item item =
-     COLOR ITEM COLON item_body.ib SEMICOLON          {: return eph.retype(new ColorItem(), ib); :}
-  |  CONTACT ITEM COLON item_body.ib SEMICOLON        {: return eph.retype(new ContactItem(), ib); :}
-  |  DATE_TIME ITEM COLON item_body.ib SEMICOLON      {: return eph.retype(new DateTimeItem(), ib); :}
-  |  DIMMER ITEM COLON item_body.ib SEMICOLON         {: return eph.retype(new DimmerItem(), ib); :}
-  |  IMAGE ITEM COLON item_body.ib SEMICOLON          {: return eph.retype(new ImageItem(), ib); :}
-  |  LOCATION ITEM COLON item_body.ib SEMICOLON       {: return eph.retype(new LocationItem(), ib); :}
-  |  NUMBER ITEM COLON item_body.ib SEMICOLON         {: return eph.retype(new NumberItem(), ib); :}
-  |  PLAYER ITEM COLON item_body.ib SEMICOLON         {: return eph.retype(new PlayerItem(), ib); :}
-  |  ROLLER_SHUTTER ITEM COLON item_body.ib SEMICOLON {: return eph.retype(new RollerShutterItem(), ib); :}
-  |  STRING ITEM COLON item_body.ib SEMICOLON         {: return eph.retype(new StringItem(), ib); :}
-  |  SWITCH ITEM COLON item_body.ib SEMICOLON         {: return eph.retype(new SwitchItem(), ib); :}
-  |  ACTIVITY ITEM COLON item_body.ib SEMICOLON       {: return eph.retype(new ActivityItem(), ib); :}
-  |  ITEM COLON item_body.ib SEMICOLON                {: return eph.retype(new DefaultItem(), ib); :}
+     COLOR ITEM COLON item_body.ib SEMICOLON             {: ib.setItemWithCorrectType(new ColorItem()); return ib; :}
+  |  CONTACT ITEM COLON item_body.ib SEMICOLON           {: ib.setItemWithCorrectType(new ContactItem()); return ib; :}
+  |  DATE_TIME ITEM COLON item_body.ib SEMICOLON         {: ib.setItemWithCorrectType(new DateTimeItem()); return ib; :}
+  |  DIMMER ITEM COLON item_body.ib SEMICOLON            {: ib.setItemWithCorrectType(new DimmerItem()); return ib; :}
+  |  IMAGE ITEM COLON item_body.ib SEMICOLON             {: ib.setItemWithCorrectType(new ImageItem()); return ib; :}
+  |  LOCATION ITEM COLON item_body.ib SEMICOLON          {: ib.setItemWithCorrectType(new LocationItem()); return ib; :}
+  |  NUMBER ITEM COLON item_body.ib SEMICOLON            {: ib.setItemWithCorrectType(new NumberItem()); return ib; :}
+  |  PLAYER ITEM COLON item_body.ib SEMICOLON            {: ib.setItemWithCorrectType(new PlayerItem()); return ib; :}
+  |  ROLLER_SHUTTER ITEM COLON item_body.ib SEMICOLON    {: ib.setItemWithCorrectType(new RollerShutterItem()); return ib; :}
+  |  STRING ITEM COLON item_body.ib SEMICOLON            {: ib.setItemWithCorrectType(new StringItem()); return ib; :}
+  |  SWITCH ITEM COLON item_body.ib SEMICOLON            {: ib.setItemWithCorrectType(new SwitchItem()); return ib; :}
+  |  ACTIVITY ITEM COLON item_body.ib SEMICOLON          {: ib.setItemWithCorrectType(new ActivityItem()); return ib; :}
+  |  ITEM COLON item_body.ib SEMICOLON                   {: ib.setItemWithCorrectType(new DefaultItem()); return ib; :}
   ;
 
 // ITEM_TYPE Item: id="" label="" state="" category="" topic="" performance="" metaData={"key":"value"} ;
-Item item_body =
-     ID EQUALS TEXT.n item_body.i       {: return eph.setID(i, n); :}
-  |  LABEL EQUALS TEXT.n item_body.i    {: i.setLabel(n); return i; :}
-  |  STATE EQUALS TEXT.n item_body.i    {: i.setStateFromString(n); return i; :}
-  |  TOPIC EQUALS TEXT.n item_body.i    {: return eph.setTopic(i, n); :}
-  |  CATEGORY EQUALS TEXT.n item_body.i {: return eph.setCategory(i, n); :}
-  |  PERFORMANCE EQUALS frequency_setting_ref.ip item_body.i           {: i.setFrequencySetting(ip); return i; :}
-  |  META_DATA EQUALS string_map.md item_body.i
-    {: return eph.setMetaData(i, md); :}
-  |                                     {: return eph.createItem(); :}
-  ;
-
-Item item_ref = TEXT.n {: return Item.createRef(n); :};
-FrequencySetting frequency_setting_ref = TEXT.n {: return FrequencySetting.createRef(n); :};
-
+ItemPrototype item_body =
+     ID EQUALS TEXT.n item_body.i                              {: i.setID(n); return i; :}
+  |  LABEL EQUALS TEXT.n item_body.i                           {: i.setLabel(n); return i; :}
+  |  STATE EQUALS TEXT.n item_body.i                           {: i.setStateFromString(n); return i; :}
+  |  TOPIC EQUALS TEXT.n item_body.i                           {: i.setTopic(MqttTopic.createRef(n)); return i; :}
+  |  CATEGORY EQUALS TEXT.n item_body.i                        {: i.setCategory(ItemCategory.createRef(n)); return i; :}
+  |  PERFORMANCE EQUALS TEXT.n item_body.i                     {: i.setFrequencySetting(FrequencySetting.createRef(n)); return i; :}
+  |  META_DATA EQUALS meta_data.md item_body.i                 {: i.setMetaData(md); return i; :}
+  |                                                            {: ItemPrototype result = new ItemPrototype();
+                                                                  result.disableSendState();
+                                                                  return result; :};
 Group group =
      GROUP COLON group_body.gb SEMICOLON    {: return gb; :}
   ;
@@ -153,30 +224,35 @@ Group group =
 // Group: id="" groups=["GROUP_ID", "GROUP_ID"] items=["ITEM_ID", "ITEM_ID"] performance="" aggregation="";
 // Group: id="" groups=["GROUP_ID", "GROUP_ID"] items=["ITEM_ID", "ITEM_ID"] performance="" aggregation="" ("","");
 Group group_body =
-     ID EQUALS TEXT.n group_body.g                    {: return eph.setID(g, n); :}
-  |  LABEL EQUALS TEXT.n group_body.g                 {: g.setLabel(n); return g; :}
-  |  GROUPS EQUALS string_list.groups group_body.g    {: return eph.setSubGroups(g, groups); :}
-  |  ITEMS EQUALS string_list.items group_body.g      {: return eph.setItems(g, items); :}
-  |  AGGREGATION EQUALS TEXT.n group_body.g           {: return eph.setSimpleAggregationFunction(g, n); :}
-  |  AGGREGATION EQUALS TEXT.n round_string_list.params group_body.g
-    {: return eph.setParameterizedAggregationFunction(g, n, params); :}
-  |  PERFORMANCE EQUALS frequency_setting_ref.ip group_body.g           {: g.setFrequencySetting(ip); return g; :}
+     ID EQUALS TEXT.n group_body.g                                 {: g.setID(n); return g; :}
+  |  LABEL EQUALS TEXT.n group_body.g                              {: g.setLabel(n); return g; :}
+  |  GROUPS EQUALS string_list.groups group_body.g                 {: groups.forEach(sg -> g.addGroup(createGroupPlaceHolder(sg))); return g; :}
+  |  ITEMS EQUALS string_list.items group_body.g                   {: items.forEach(i -> g.addItem(createItemPlaceHolder(i))); return g; :}
+  |  AGGREGATION EQUALS TEXT.n group_body.g                        {: SimpleGroupAggregationFunctionName name = SimpleGroupAggregationFunctionName.valueOf(n.toUpperCase());
+                                                                      g.setAggregationFunction(new SimpleGroupAggregationFunction(name));
+                                                                      return g; :}
+  |  AGGREGATION EQUALS TEXT.n round_string_list.p group_body.g    {: ParameterizedGroupAggregationFunctionName name = ParameterizedGroupAggregationFunctionName.valueOf(n.toUpperCase());
+                                                                      String[] params = { "?", "?" };
+                                                                      java.util.Iterator<String> iterator = p.iterator();
+                                                                      for (int index = 0; index < 2; index++) {
+                                                                        params[index] = iterator.next();
+                                                                      }
+                                                                      g.setAggregationFunction(new ParameterizedGroupAggregationFunction(name, params[0], params[1]));
+                                                                      return g; :}
+  | PERFORMANCE EQUALS TEXT.n group_body.g           {: g.setFrequencySetting(FrequencySetting.createRef(n)); return g; :}
   |                                                   {: return new Group(); :}
   ;
 
 
-StateSyncGroup state_sync_group = SYNCSTATE COLON syncstate_body.ssb SEMICOLON {: return ssb; :};
+StateSyncGroup state_sync_group = SYNC_STATE COLON sync_state_body.ssb SEMICOLON    {: return ssb; :};
 
 // SyncState: items=["ITEM_ID", "ITEM_ID"];
-StateSyncGroup syncstate_body =
-     ITEMS EQUALS item_list.items syncstate_body.ss
-     {:
-       for (Item item : items) {
-         ss.addTargetItem(item);
-       }
-       return ss;
-     :}
-  |                                         {: return new StateSyncGroup(); :}
+StateSyncGroup sync_state_body =
+     ITEMS EQUALS string_list.items sync_state_body.ssb    {: for (String itemName : items) {
+                                                                ssb.addTargetItem(Item.createRef(itemName));
+                                                              }
+                                                              return ssb; :}
+  |                                                        {: return new StateSyncGroup(); :}
   ;
 
 ThingType thing_type =
@@ -185,16 +261,12 @@ ThingType thing_type =
 
 // ThingType: id="" label="" description="" parameters=[] channelTypes=[];
 ThingType thing_type_body =
-     ID EQUALS TEXT.n thing_type_body.ttb                           {: return eph.setID(ttb, n); :}
-  |  LABEL EQUALS TEXT.n thing_type_body.ttb                        {: ttb.setLabel(n); return ttb; :}
-  |  DESCRIPTION EQUALS TEXT.n thing_type_body.ttb                  {: ttb.setDescription(n); return ttb; :}
-  |  PARAMETERS EQUALS string_list.p thing_type_body.ttb            {: return eph.setParameters(ttb, p); :}
-  |  CHANNEL_TYPES EQUALS string_list.c thing_type_body.ttb         {: return eph.setChannelTypes(ttb, c); :}
-  |  ID EQUALS TEXT.n                           {: return eph.setID(new ThingType(), n); :}
-  |  LABEL EQUALS TEXT.n                        {: ThingType tt = new ThingType(); tt.setLabel(n); return tt; :}
-  |  DESCRIPTION EQUALS TEXT.n                  {: ThingType tt = new ThingType(); tt.setDescription(n); return tt; :}
-  |  PARAMETERS EQUALS string_list.p            {: return eph.setParameters(new ThingType(), p); :}
-  |  CHANNEL_TYPES EQUALS string_list.c         {: return eph.setChannelTypes(new ThingType(), c); :}
+     ID EQUALS TEXT.n thing_type_body.t                        {: t.setID(n); return t; :}
+  |  LABEL EQUALS TEXT.n thing_type_body.t                     {: t.setLabel(n); return t; :}
+  |  DESCRIPTION EQUALS TEXT.n thing_type_body.t               {: t.setDescription(n); return t; :}
+  |  PARAMETERS EQUALS string_list.ps thing_type_body.t        {: ps.forEach(p -> t.addParameter(Parameter.createRef(p))); return t; :}
+  |  CHANNEL_TYPES EQUALS string_list.cts thing_type_body.t    {: cts.forEach(ct -> t.addChannelType(ChannelType.createRef(ct))); return t; :}
+  |                                                            {: return new ThingType(); :}
   ;
 
 Parameter parameter =
@@ -204,14 +276,14 @@ Parameter parameter =
 // Parameter: id="" label="" description="" type="" default="" required;
 // Parameter: id="" label="" description="" type="" ;
 Parameter parameter_body =
-     ID EQUALS TEXT.n parameter_body.pb               {: return eph.setID(pb, n); :}
-  |  LABEL EQUALS TEXT.n parameter_body.pb            {: pb.setLabel(n); return pb; :}
-  |  DESCRIPTION EQUALS TEXT.n parameter_body.pb      {: pb.setDescription(n); return pb; :}
-  |  CONTEXT EQUALS TEXT.n parameter_body.pb          {: pb.setContext(n); return pb; :}
-  |  TYPE EQUALS TEXT.n parameter_body.pb             {: return eph.setParameterValueType(pb, n); :}
-  |  DEFAULT EQUALS TEXT.n parameter_body.pb          {: return eph.setDefault(pb, n); :}
-  |  REQUIRED parameter_body.pb                       {: pb.setRequired(true); return pb; :}
-  |                                                   {: return new Parameter(); :}
+     ID EQUALS TEXT.n parameter_body.p               {: p.setID(n); return p; :}
+  |  LABEL EQUALS TEXT.n parameter_body.p            {: p.setLabel(n); return p; :}
+  |  DESCRIPTION EQUALS TEXT.n parameter_body.p      {: p.setDescription(n); return p; :}
+  |  CONTEXT EQUALS TEXT.n parameter_body.p          {: p.setContext(n); return p; :}
+  |  TYPE EQUALS TEXT.n parameter_body.p             {: p.setType(ParameterValueType.valueOf(JavaUtils.toTitleCase(n))); return p; :}
+  |  DEFAULT EQUALS TEXT.n parameter_body.p          {: p.setDefaultValue(new ParameterDefaultValue(n)); return p; :}
+  |  REQUIRED parameter_body.p                       {: p.setRequired(true); return p; :}
+  |                                                  {: return new Parameter(); :}
   ;
 
 ChannelType channel_type =
@@ -220,13 +292,21 @@ ChannelType channel_type =
 
 // ChannelType: id="" label="" description="" itemType="" category="TEXT" readyOnly;
 ChannelType channel_type_body =
-     ID EQUALS TEXT.n channel_type_body.ctb                         {: return eph.setID(ctb, n); :}
-  |  LABEL EQUALS TEXT.n channel_type_body.ctb                      {: ctb.setLabel(n); return ctb; :}
-  |  DESCRIPTION EQUALS TEXT.n channel_type_body.ctb                {: ctb.setDescription(n); return ctb; :}
-  |  ITEM_TYPE EQUALS TEXT.n channel_type_body.ctb                  {: return eph.setItemType(ctb, n); :}
-  |  CATEGORY EQUALS TEXT.c channel_type_body.ctb                   {: return eph.setChannelCategory(ctb, c); :}
-  |  READ_ONLY channel_type_body.ctb                                {: ctb.setReadOnly(true); return ctb; :}
-  |                                                                 {: return new ChannelType(); :}
+     ID EQUALS TEXT.n channel_type_body.c                         {: c.setID(n); return c; :}
+  |  LABEL EQUALS TEXT.n channel_type_body.c                      {: c.setLabel(n); return c; :}
+  |  DESCRIPTION EQUALS TEXT.n channel_type_body.c                {: c.setDescription(n); return c; :}
+  |  ITEM_TYPE EQUALS TEXT.n channel_type_body.c                  {: c.setItemType(ItemType.valueOf(n)); return c; :}
+  |  CATEGORY EQUALS TEXT.n channel_type_body.c                   {: try {
+                                                                       DefaultChannelCategoryValue.valueOf(n);
+                                                                       ReferringChannelCategory rcc = new ReferringChannelCategory();
+                                                                       rcc.setChannelCategory(DefaultChannelCategory.createRef(n));
+                                                                       c.setChannelCategory(rcc);
+                                                                     } catch (IllegalArgumentException e) {
+                                                                       c.setChannelCategory(new SimpleChannelCategory(n));
+                                                                     }
+                                                                     return c; :}
+  |  READ_ONLY channel_type_body.c                                {: c.setReadOnly(true); return c; :}
+  |                                                               {: return new ChannelType(); :}
   ;
 
 Channel channel =
@@ -235,26 +315,30 @@ Channel channel =
 
 // Channel: id="" type="" links=["ITEM_ID", "ITEM_ID"];
 Channel channel_body =
-     ID EQUALS TEXT.n channel_body.c                  {: return eph.setID(c, n); :}
-  |  TYPE EQUALS TEXT.n channel_body.c                {: return eph.setChannelType(c, n); :}
-  |  LINKS EQUALS string_list.links channel_body.c    {: return eph.setLinks(c, links); :}
+     ID EQUALS TEXT.n channel_body.c                  {: c.setID(n); return c; :}
+  |  TYPE EQUALS TEXT.n channel_body.c                {: c.setType(ChannelType.createRef(n)); return c; :}
+  |  LINKS EQUALS string_list.links channel_body.c    {: links.forEach(i -> c.addLinkedItem(Item.createRef(i))); return c; :}
   |                                                   {: return new Channel(); :}
   ;
 
+/// MQTT ///
+
 MqttRoot mqtt_root =
      MQTT COLON mqtt_root_body.mrb SEMICOLON    {: return mrb; :}
   ;
 
 // Mqtt: incoming="" outgoing="" host="";
 MqttRoot mqtt_root_body =
-     INCOMING EQUALS TEXT.n mqtt_root_body.mrb          {: mrb.setIncomingPrefix(n); return mrb; :}
-  |  OUTGOING EQUALS TEXT.n mqtt_root_body.mrb          {: mrb.setOutgoingPrefix(n); return mrb; :}
+     INCOMING EQUALS TEXT.n mqtt_root_body.mrb          {: mrb.setIncomingPrefix(ensureTrailingSlash(n)); return mrb; :}
+  |  OUTGOING EQUALS TEXT.n mqtt_root_body.mrb          {: mrb.setOutgoingPrefix(ensureTrailingSlash(n)); return mrb; :}
   |  HOST EQUALS TEXT.n mqtt_root_body.mrb              {: mrb.setHostByName(n); return mrb; :}
   |  USER EQUALS TEXT.n mqtt_root_body.mrb              {: mrb.setUser(n); return mrb; :}
   |  PASSWORD EQUALS TEXT.n mqtt_root_body.mrb          {: mrb.setPassword(n); return mrb; :}
   |                                                     {: return new MqttRoot(); :}
   ;
 
+/// Influx ///
+
 InfluxRoot influx_root =
      INFLUX COLON influx_root_body.irb SEMICOLON    {: return irb; :}
   ;
@@ -268,25 +352,35 @@ InfluxRoot influx_root_body =
   |                                                  {: return InfluxRoot.createDefault(); :}
   ;
 
-// Machine Learning
+/// Machine Learning ///
+
 MachineLearningRoot machine_learning_root =
      ML COLON machine_learning_root_body.b SEMICOLON     {: return b; :}
   ;
 
 // ML: activities={index:"name"} ;
 MachineLearningRoot machine_learning_root_body =
-    ACTIVITIES EQUALS integer_map.map machine_learning_root_body.b    {: return eph.setActivities(b, map); :}
+    ACTIVITIES EQUALS integer_map.map machine_learning_root_body.b    {: for (java.util.AbstractMap.SimpleEntry<Integer,String> entry : map) {
+                                                                           Activity activity = new Activity();
+                                                                           activity.setIdentifier(entry.getKey());
+                                                                           activity.setLabel(entry.getValue());
+                                                                           b.addActivity(activity);
+                                                                         }
+                                                                         return b; :}
   |                                                                   {: return MachineLearningRoot.createDefault(); :}
   ;
 
 // Rule: condition=condition action=a1 action=a2;
 // (only allow one condition and action for now)
 Rule rule =
-    RULE COLON CONDITION EQUALS condition.c action.a SEMICOLON          {: return eph.createRule(c, a); :}
+    RULE COLON CONDITION EQUALS condition.c action.a SEMICOLON    {: Rule result = new Rule();
+                                                                     result.addCondition(c);
+                                                                     result.addAction(a);
+                                                                     return result; :}
   ;
 
 Condition condition =
-    logical_expression.be                            {: return new ExpressionCondition(be); :}
+    logical_expression.be    {: return new ExpressionCondition(be); :}
   ;
 
 // TODO implement action cases
@@ -309,64 +403,40 @@ StringList string_list_body =
     :}
   ;
 
-ItemList item_list =
-     LB_SQUARE item_list_body.ilb RB_SQUARE         {: return ilb; :}
-  |  LB_SQUARE RB_SQUARE                            {: return new ItemList(); :}
-  ;
-
-ItemList item_list_body =
-     item_ref.ir COMMA item_list_body.ilb           {: ilb.add(ir); return ilb; :}
-  |  item_ref.ir
-    {:
-       ItemList result = new ItemList();
-       result.add(ir);
-       return result;
-    :}
-  ;
-
 StringList round_string_list =
      LB_ROUND round_string_list_body.slb RB_ROUND     {: return slb; :}
   |  LB_ROUND RB_ROUND                                {: return new StringList(); :}
   ;
 
 StringList round_string_list_body =
-     TEXT.n COMMA round_string_list_body.slb          {: slb.add(n); return slb; :}
-  |  TEXT.n
-    {:
-       StringList result = new StringList();
-       result.add(n);
-       return result;
-    :}
+     TEXT.n COMMA round_string_list_body.slb    {: slb.add(n); return slb; :}
+  |  TEXT.n                                     {: StringList result = new StringList();
+                                                   result.add(n);
+                                                   return result; :}
   ;
 
-StringKeyMap string_map =
-     LB_CURLY string_map_body.smb RB_CURLY             {: return smb; :}
-  |  LB_CURLY RB_CURLY                                 {: return new StringKeyMap(); :}
+MetaData meta_data =
+     LB_CURLY meta_data_body.mdb RB_CURLY    {: return mdb; :}
   ;
 
-StringKeyMap string_map_body =
-     TEXT.key COLON TEXT.value COMMA string_map_body.smb {: smb.put(key, value); return smb; :}
-  |  TEXT.key COLON TEXT.value
-    {:
-       StringKeyMap result = new StringKeyMap();
-       result.put(key, value);
-       return result;
-    :}
+MetaData meta_data_body =
+     TEXT.key COLON TEXT.value COMMA meta_data_body.mdb    {: insertZero(mdb.getKeyValuePairList(), new KeyValuePair(key, value)); return mdb; :}
+  |  TEXT.key COLON TEXT.value                             {: MetaData mdb = new MetaData();
+                                                              insertZero(mdb.getKeyValuePairList(), new KeyValuePair(key, value));
+                                                              return mdb; :}
+  |                                                        {: return new MetaData(); :}
   ;
 
 IntegerKeyMap integer_map =
-     LB_CURLY integer_map_body.imb RB_CURLY             {: return imb; :}
-  |  LB_CURLY RB_CURLY                                  {: return new IntegerKeyMap(); :}
+     LB_CURLY integer_map_body.imb RB_CURLY    {: return imb; :}
+  |  LB_CURLY RB_CURLY                         {: return new IntegerKeyMap(); :}
   ;
 
 IntegerKeyMap integer_map_body =
-     INTEGER.key COLON TEXT.value COMMA integer_map_body.imb {: imb.put(Integer.parseInt(key), value); return imb; :}
-  |  INTEGER.key COLON TEXT.value
-    {:
-       IntegerKeyMap result = new IntegerKeyMap();
-       result.put(Integer.parseInt(key), value);
-       return result;
-    :}
+     INTEGER.key COLON TEXT.value COMMA integer_map_body.imb    {: imb.put(Integer.parseInt(key), value); return imb; :}
+  |  INTEGER.key COLON TEXT.value                               {: IntegerKeyMap result = new IntegerKeyMap();
+                                                                   result.put(Integer.parseInt(key), value);
+                                                                   return result; :}
   ;
 
 FrequencySetting frequency_setting =
@@ -375,7 +445,7 @@ FrequencySetting frequency_setting =
 
 // FrequencySetting: id="" procFreq="" persFreq="";
 FrequencySetting frequency_setting_body =
-     ID EQUALS TEXT.n frequency_setting_body.ip                  {: return eph.setID(ip, n); :}
-  |  PROCESS_FREQUENCY EQUALS TEXT.n frequency_setting_body.ip        {: ip.setEventProcessingFrequency(Double.parseDouble(n)); return ip; :}
-  |                                                   {: return new FrequencySetting(); :}
+     ID EQUALS TEXT.n frequency_setting_body.fs                   {: fs.setID(n); return fs; :}
+  |  PROCESS_FREQUENCY EQUALS TEXT.n frequency_setting_body.fs    {: fs.setEventProcessingFrequency(Double.parseDouble(n)); return fs; :}
+  |                                                               {: return new FrequencySetting(); :}
   ;
diff --git a/eraser-base/src/main/jastadd/mqtt.jrag b/eraser-base/src/main/jastadd/mqtt.jrag
index 93e11442e9f277455b8b2a62e32375fd091c94e8..a33f024b510ce0b3fb642a3a2fcae98cf43eb29b 100644
--- a/eraser-base/src/main/jastadd/mqtt.jrag
+++ b/eraser-base/src/main/jastadd/mqtt.jrag
@@ -77,11 +77,16 @@ aspect MQTT {
   refine SmartHomeEntityModel public void SmartHomeEntityModel.addNewItem(Item item) {
     refined(item);
     // update mqtt-topic to new mqtt-root
-    JavaUtils.ifPresentOrElse(
-        getRoot().getMqttRoot().resolveTopicSuffix(item.getTopic().getTopicString()),
-        topic -> item.setTopic(topic),
-        () -> de.tudresden.inf.st.eraser.util.ParserUtils.createMqttTopic(item, item.getTopic().getTopicString(), getRoot())
-    );
+    item.setTopic(getRoot().getMqttRoot().getOrCreateMqttTopic(item.getTopic().getTopicString()));
+  }
+
+  public MqttTopic MqttRoot.getOrCreateMqttTopic(String topicString) {
+    return resolveTopicSuffix(topicString).orElseGet(() -> {
+      MqttTopic result = new MqttTopic();
+      result.setTopicString(topicString);
+      addTopic(result);
+      return result;
+    });
   }
 
 }
diff --git a/eraser-base/src/main/jastadd/shem.jrag b/eraser-base/src/main/jastadd/shem.jrag
index 4cdc35da0e05c77be4ea4ab58d0f5699f87ae100..f163ebf4824dab8d3c3107a35fe4654b48e6c2ad 100644
--- a/eraser-base/src/main/jastadd/shem.jrag
+++ b/eraser-base/src/main/jastadd/shem.jrag
@@ -4,10 +4,73 @@ aspect SmartHomeEntityModel {
   }
 
   public void SmartHomeEntityModel.addNewItem(Item item) {
-    JavaUtils.ifPresentOrElse(
-        resolveGroup(de.tudresden.inf.st.eraser.util.ParserUtils.UNKNOWN_GROUP_NAME),
-        group -> group.addItem(item),
-        () -> de.tudresden.inf.st.eraser.util.ParserUtils.createUnknownGroup(this, Collections.singletonList(item)));
+    unknownGroup().addItem(item);
+  }
+
+  public MetaData MetaData.add(String key, String value) {
+    addKeyValuePair(new KeyValuePair(key, value));
+    return this;
+  }
+
+  syn nta Group SmartHomeEntityModel.unknownGroup() {
+    return new Group().setID("Unknown");
+  }
+
+  syn nta JastAddList<DefaultChannelCategory> SmartHomeEntityModel.defaultChannelCategoryList() {
+    JastAddList<DefaultChannelCategory> result = new JastAddList<>();
+    Arrays.stream(DefaultChannelCategoryValue.values()).sorted().forEach(ccv -> result.add(new DefaultChannelCategory(ccv)));
+    return result;
+  }
+
+  rewrite ItemPrototype {
+    to Item {
+      Item result = getItemWithCorrectType();
+      result.setID(this.getID());
+      result.setLabel(this.getLabel());
+      result.setMetaData(this.getMetaData());
+      if (this.hasTopic()) {
+        result.setTopic(this.getTopic());
+      }
+      if (this.hasCategory()) {
+        result.setCategory(this.getCategory());
+      }
+      if (!result.isActivityItem()) {
+        String state = this.getStateAsString();
+        result.disableSendState();
+        if (state.isEmpty()) {
+          result.setStateToDefault();
+        } else {
+          result.setStateFromString(state);
+        }
+        result.enableSendState();
+      }
+      return result;
+    }
+  }
+
+  // PlaceHolders
+  // ItemPlaceHolder
+  syn Item Item.realItem() = this;
+  eq ItemPlaceHolder.realItem() {
+    return containingSmartHomeEntityModel().resolveItem(getID()).orElseThrow(() -> new RuntimeException("Item '" + getID() + "' not found!"));
+  }
+  eq ItemPlaceHolder.prettyPrintType() = "<placeholder>";
+  syn boolean Item.isItemPlaceHolder() = false;
+  eq ItemPlaceHolder.isItemPlaceHolder() = true;
+
+  // GroupPlaceHolder
+  syn Group Group.realGroup() = this;
+  eq GroupPlaceHolder.realGroup() {
+    return containingSmartHomeEntityModel().resolveGroup(getID()).orElseThrow(() -> new RuntimeException("Group '" + getID() + "' not found!"));
+  }
+
+  public ItemCategory SmartHomeEntityModel.getOrCreateCategoryByName(String categoryName) {
+    return this.resolveItemCategory(categoryName).orElseGet(() -> {
+      ItemCategory result = new ItemCategory();
+      result.setName(categoryName);
+      addItemCategory(result);
+      return result;
+    });
   }
 
 }
diff --git a/eraser-base/src/main/jastadd/shem.relast b/eraser-base/src/main/jastadd/shem.relast
index 62cb25506245a5f7d04dcea483cfa11c07d1d179..165666746b6cd509b5046eeab257cd15289c5602 100644
--- a/eraser-base/src/main/jastadd/shem.relast
+++ b/eraser-base/src/main/jastadd/shem.relast
@@ -1,23 +1,26 @@
 // ----------------    openHAB    ------------------------------
-SmartHomeEntityModel ::= Thing* Group* ThingType* ChannelType* ChannelCategory* ItemCategory* /ActivityItem:Item/ ;
+SmartHomeEntityModel ::= Thing* Group* ThingType* Parameter* ChannelType* Channel* ItemCategory* /ActivityItem:Item/ ;
 
 
 abstract ModelElement ::= <ID:String> ;
 abstract LabelledModelElement : ModelElement ::= <Label:String> ;
 abstract DescribableModelElement : LabelledModelElement ::= <Description:String> ;
 
-ThingType : DescribableModelElement ::= Parameter* ;
+ThingType : DescribableModelElement ::= ;
+rel ThingType.Parameter* -> Parameter ;
 rel ThingType.ChannelType* -> ChannelType ;
 
-Thing : LabelledModelElement ::= Channel* ;
+Thing : LabelledModelElement ::= ;
+rel Thing.Channel* <-> Channel.Thing ;
 rel Thing.Type -> ThingType ;
 
-ChannelType : DescribableModelElement ::= <ItemType:ItemType> <ReadOnly:boolean> ;
-rel ChannelType.ChannelCategory -> ChannelCategory ;
+ChannelType : DescribableModelElement ::= <ItemType:ItemType> <ReadOnly:boolean> ChannelCategory ;
 
 abstract ChannelCategory ;
-DefaultChannelCategory : ChannelCategory ::= <Value:DefaultChannelCategoryValue> ;
+ReferringChannelCategory : ChannelCategory ;
+rel ReferringChannelCategory.ChannelCategory -> DefaultChannelCategory ;
 SimpleChannelCategory : ChannelCategory ::= <Value:String> ;
+DefaultChannelCategory ::= <Value:DefaultChannelCategoryValue> ;
 
 Channel : ModelElement ::= ;
 rel Channel.Type -> ChannelType ;
@@ -26,8 +29,8 @@ rel Channel.LinkedItem* <-> Item.Channel? ;
 Parameter : DescribableModelElement ::= <Type:ParameterValueType> [DefaultValue:ParameterDefaultValue] <Context:String> <Required:boolean> ;
 ParameterDefaultValue ::= <Value:String> ;
 
-abstract Item : LabelledModelElement ::= <_fetched_data:boolean> MetaData:ItemMetaData* /ItemObserver/ /LastChanged/;
-rel Item.Category? -> ItemCategory ;
+abstract Item : LabelledModelElement ::= <_fetched_data:boolean> [MetaData] /ItemObserver/ /LastChanged/;
+rel Item.Category? <-> ItemCategory.Items* ;
 rel Item.FrequencySetting? -> FrequencySetting ;
 
 abstract ItemWithBooleanState : Item ::= <_state:boolean> ;
@@ -46,16 +49,19 @@ StringItem : ItemWithStringState ;
 SwitchItem : ItemWithBooleanState ;
 DefaultItem : ItemWithStringState ;
 ActivityItem : ItemWithDoubleState ;
+ItemPrototype : ItemWithStringState ::= ItemWithCorrectType:Item ;  // only used for parsing
+ItemPlaceHolder : ItemWithStringState ;  // only used for parsing
 
-ItemMetaData ::= <Key:String> <Value:String> ;
+MetaData ::= KeyValuePair* ;
+KeyValuePair ::= <Key:String> <Value:String> ;
 
 ItemCategory ::= <Name:String> ;
 
 LastChanged ::= <Value:Instant> ;
 
-
 Group : LabelledModelElement ::= Group* Item* [AggregationFunction:GroupAggregationFunction] ;
 rel Group.FrequencySetting? -> FrequencySetting ;
+GroupPlaceHolder : Group ;  // only used for parsing
 
 abstract GroupAggregationFunction ;
 SimpleGroupAggregationFunction : GroupAggregationFunction ::= <FunctionName:SimpleGroupAggregationFunctionName> ;
diff --git a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/openhab2/OpenHab2Importer.java b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/openhab2/OpenHab2Importer.java
index d3fcaefa4cf16a7114406ac69f399dc029e1c7a7..bc3dab425186024202d91c3ad659898f6d06b3e0 100644
--- a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/openhab2/OpenHab2Importer.java
+++ b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/openhab2/OpenHab2Importer.java
@@ -6,6 +6,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.module.SimpleModule;
 import de.tudresden.inf.st.eraser.jastadd.model.*;
 import de.tudresden.inf.st.eraser.openhab2.data.*;
+import de.tudresden.inf.st.eraser.util.JavaUtils;
 import de.tudresden.inf.st.eraser.util.ParserUtils;
 import de.tudresden.inf.st.eraser.util.Tuple;
 import org.apache.logging.log4j.LogManager;
@@ -76,6 +77,9 @@ public class OpenHab2Importer {
       logger.info("Read a total of {} item(s) including groups.", itemList.length);
       update(model, itemList);
 
+      root.treeResolveAll();
+      root.doSafeFullTraversal();
+
       LinkData[] linkList = mapper.readValue(makeURL(linksUrl, hostAndPort), LinkData[].class);
       logger.info("Read a total of {} link(s).", linkList.length);
       update(model, linkList);
@@ -113,13 +117,13 @@ public class OpenHab2Importer {
   private void update(SmartHomeEntityModel model, ChannelTypeData[] channelTypeList) {
     for (ChannelTypeData channelTypeData : channelTypeList) {
       ChannelType channelType = new ChannelType();
+      model.addChannelType(channelType);
       channelType.setID(channelTypeData.UID);
       channelType.setLabel(channelTypeData.label);
       channelType.setDescription(channelTypeData.description);
       maybeSetItemType(channelType, channelTypeData.itemType);
       maybeSetChannelCategory(channelType, channelTypeData.category);
       maybeSetReadOnly(channelType, channelTypeData.stateDescription);
-      model.addChannelType(channelType);
     }
   }
 
@@ -144,17 +148,16 @@ public class OpenHab2Importer {
     if (category == null) {
       return;
     }
-    try {
-      DefaultChannelCategoryValue dccv = DefaultChannelCategoryValue.valueOf(category);
-      channelType.setChannelCategory(new DefaultChannelCategory(dccv));
-    } catch (IllegalArgumentException e) {
-      // channel category was not found
-      // store in unresolved and only warn once about it
-      if (nonDefaultChannelCategories.add(category)) {
-        logger.warn("Could not find ChannelCategory for '{}'", category);
-      }
-      channelType.setChannelCategory(new SimpleChannelCategory(category));
-    }
+    JavaUtils.ifPresentOrElse(channelType.getRoot().getSmartHomeEntityModel().resolveDefaultChannelCategory(category),
+        dcc -> channelType.setChannelCategory(new ReferringChannelCategory(dcc)),
+        () -> {
+          // channel category was not found
+          // store in unresolved and only warn once about it
+          if (nonDefaultChannelCategories.add(category)) {
+            logger.warn("Could not find ChannelCategory for '{}'", category);
+          }
+          channelType.setChannelCategory(new SimpleChannelCategory(category));
+        });
   }
 
   private void maybeSetReadOnly(ChannelType channelType, StateDescriptionData stateDescription) {
@@ -177,6 +180,8 @@ public class OpenHab2Importer {
         channel.setID(channelData.uid);
         ifPresent(model.resolveChannelType(channelData.channelTypeUID),
             "channelType", channelData, channel::setType);
+        model.addChannel(channel);
+        // now set relation
         thing.addChannel(channel);
       }
     }
@@ -243,10 +248,12 @@ public class OpenHab2Importer {
         }
         item.enableSendState();
         if (itemData.metadata != null) {
+          MetaData metaData = new MetaData();
+          item.setMetaData(metaData);
           for (Map.Entry<String, MetaDataData> entry : itemData.metadata.entrySet()) {
             logger.debug("Add metadata for namespace {}", entry.getKey());
             for (Map.Entry<String, String> metaDataEntry : entry.getValue().config.entrySet()) {
-              item.addMetaData(new ItemMetaData(metaDataEntry.getKey(), metaDataEntry.getValue()));
+              metaData.add(metaDataEntry.getKey(), metaDataEntry.getValue());
             }
           }
         }
@@ -278,7 +285,7 @@ public class OpenHab2Importer {
       }
     }
     if (!itemsWithoutGroup.isEmpty()) {
-      ParserUtils.createUnknownGroup(model, itemsWithoutGroup);
+      ParserUtils.addToUnknownGroup(model, itemsWithoutGroup);
     }
   }
 
diff --git a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/parser/EraserParserHelper.java b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/parser/EraserParserHelper.java
index 32be358baf48a7cd2af133d0fe3e1a3dca97aa02..abb789b66118b14c609711de134de169e9cde565 100644
--- a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/parser/EraserParserHelper.java
+++ b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/parser/EraserParserHelper.java
@@ -14,7 +14,8 @@ import java.util.function.BiConsumer;
  *
  * @author rschoene - Initial contribution
  */
-public class EraserParserHelper {
+@Deprecated
+class EraserParserHelper {
   private Logger logger = LogManager.getLogger(EraserParserHelper.class);
 
   private Map<String, ThingType> thingTypeMap = new HashMap<>();
@@ -89,7 +90,7 @@ public class EraserParserHelper {
     } else {
       resolve(itemMap, missingItemForDesignator, Designator::setItem);
     }
-    missingTopicMap.forEach((topic, parts) -> ParserUtils.createMqttTopic(topic, parts, this.root));
+//    missingTopicMap.forEach((item, s) -> item.setMqttTopic(s));
     this.root.getMqttRoot().ensureCorrectPrefixes();
 
     resolveList(channelMap, missingChannelListMap, Thing::addChannel);
@@ -101,7 +102,7 @@ public class EraserParserHelper {
 
 
     createUnknownGroupIfNecessary();
-    createChannelCategories();
+//    createChannelCategories();
     createItemCategories();
 
     if (checkUnusedElements) {
@@ -139,16 +140,10 @@ public class EraserParserHelper {
           sortedDanglingItems.add(item);
         }
       }
-      ParserUtils.createUnknownGroup(this.root.getSmartHomeEntityModel(), sortedDanglingItems);
+      ParserUtils.addToUnknownGroup(this.root.getSmartHomeEntityModel(), sortedDanglingItems);
     }
   }
 
-  private void createChannelCategories() {
-    channelCategoryMap.values().stream().sorted(Comparator.comparing(this::ident)).forEach(
-        cc -> root.getSmartHomeEntityModel().addChannelCategory(cc));
-    channelCategoryMap.clear();
-  }
-
   private void createItemCategories() {
     Map<String, ItemCategory> newCategories = new HashMap<>();
     missingItemCategoryMap.forEach((item, category) ->
@@ -248,21 +243,6 @@ public class EraserParserHelper {
     return ct;
   }
 
-  public ChannelType setChannelCategory(ChannelType ct, String name) {
-    ChannelCategory result = channelCategoryMap.get(name);
-    if (result == null) {
-      try {
-        DefaultChannelCategoryValue dccv = DefaultChannelCategoryValue.valueOf(name);
-        result = new DefaultChannelCategory(dccv);
-      } catch (IllegalArgumentException e) {
-        result = new SimpleChannelCategory(name);
-      }
-      channelCategoryMap.put(name, result);
-    }
-    ct.setChannelCategory(result);
-    return ct;
-  }
-
   public Channel setChannelType(Channel c, String channelTypeName) {
     missingChannelTypeMap.put(c, channelTypeName);
     return c;
@@ -299,17 +279,10 @@ public class EraserParserHelper {
     return item;
   }
 
-  public Item setMetaData(Item item, StringKeyMap metaData) {
-    for (AbstractMap.SimpleEntry<String, String> entry : metaData) {
-      item.addMetaData(new ItemMetaData(entry.getKey(), entry.getValue()));
-    }
-    return item;
-  }
-
   public Item retype(Item itemWithCorrectType, Item prototype) {
     itemWithCorrectType.setID(prototype.getID());
     itemWithCorrectType.setLabel(prototype.getLabel());
-    itemWithCorrectType.setMetaDataList(prototype.getMetaDataList());
+    itemWithCorrectType.setMetaData(prototype.getMetaData());
     itemWithCorrectType.setFrequencySetting(prototype.getFrequencySetting());
     if (!(itemWithCorrectType instanceof ActivityItem)) {
       String state = prototype.getStateAsString();
diff --git a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/util/ParserUtils.java b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/util/ParserUtils.java
index c1b75ba26b03db32a52fadec3436dc5fdbcbeff0..5b365efb7b3e184a76396411a659ce624c76a779 100644
--- a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/util/ParserUtils.java
+++ b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/util/ParserUtils.java
@@ -8,7 +8,6 @@ import com.fasterxml.jackson.databind.SerializationFeature;
 import de.tudresden.inf.st.eraser.jastadd.model.*;
 import de.tudresden.inf.st.eraser.jastadd.parser.EraserParser;
 import de.tudresden.inf.st.eraser.jastadd.scanner.EraserScanner;
-import de.tudresden.inf.st.eraser.parser.EraserParserHelper;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
@@ -26,7 +25,6 @@ import java.util.Objects;
  */
 public class ParserUtils {
 
-  public static final String UNKNOWN_GROUP_NAME = "Unknown";
   private static boolean verboseLoading = false;
   private static final Logger logger = LogManager.getLogger(ParserUtils.class);
   private static final ObjectMapper OBJECT_MAPPER_INSTANCE = new ObjectMapper()
@@ -145,8 +143,7 @@ public class ParserUtils {
 
     EraserScanner scanner = new EraserScanner(reader);
     EraserParser parser = new EraserParser();
-    Root result = (Root) parser.parse(scanner);
-    parser.resolveReferences();
+    Root result = parser.parseRoot(scanner);
     reader.close();
     return result;
   }
@@ -172,31 +169,14 @@ public class ParserUtils {
   }
 
   /**
-   * Create well-known group call "Unknown" and add all dangling items to it.
+   * Add all dangling items to unknown group.
    * @param model         The model to operate on
    * @param danglingItems A list of items to add to the new group
    */
-  public static void createUnknownGroup(SmartHomeEntityModel model, Collection<Item> danglingItems) {
-    Group unknownGroup = new Group();
-    unknownGroup.setID(UNKNOWN_GROUP_NAME);
-    model.addGroup(unknownGroup);
+  public static void addToUnknownGroup(SmartHomeEntityModel model, Collection<Item> danglingItems) {
+    Group unknownGroup = model.unknownGroup();
     danglingItems.forEach(unknownGroup::addItem);
-    logger.info("Created new {}", unknownGroup.prettyPrint().trim());
-  }
-
-  /**
-   * Create a topic for the given topic name and assign it to the given item.
-   * @param item        The item to which the topic will be assigned to
-   * @param topicSuffix The full topic name
-   * @param root        The model to operate on
-   */
-  public static void createMqttTopic(Item item, String topicSuffix, Root root) {
-    item.setTopic(root.getMqttRoot().resolveTopicSuffix(topicSuffix).orElseGet(() -> {
-      MqttTopic result = new MqttTopic();
-      result.setTopicString(topicSuffix);
-      root.getMqttRoot().addTopic(result);
-      return result;
-    }));
+    logger.info("Updated unknown group {}", unknownGroup.prettyPrint().trim());
   }
 
   public static NumberExpression parseNumberExpression(String expression_string) throws IOException, Parser.Exception {
@@ -216,7 +196,6 @@ public class ParserUtils {
   }
 
   private static Expression parseExpression(String expression_string, short alt_goal, Root root) throws IOException, Parser.Exception {
-    EraserParserHelper.setInitialRoot(root);
     StringReader reader = new StringReader(expression_string);
     if (verboseLoading) {
       EraserScanner scanner = new EraserScanner(reader);
@@ -230,13 +209,12 @@ public class ParserUtils {
         e.printStackTrace();
       }
     }
+    EraserParser.setNextInitialRoot(root);
     reader = new StringReader(expression_string);
     EraserScanner scanner = new EraserScanner(reader);
     EraserParser parser = new EraserParser();
-    Expression result = (Expression) parser.parse(scanner, alt_goal);
-    parser.resolveReferences();
+    Expression result = parser.parseExpression(scanner, alt_goal);
     reader.close();
-    EraserParserHelper.setInitialRoot(null);
     return result;
   }
 
@@ -245,8 +223,7 @@ public class ParserUtils {
     StringReader reader = new StringReader(definition);
     EraserScanner scanner = new EraserScanner(reader);
     EraserParser parser = new EraserParser();
-    Root root = (Root) parser.parse(scanner);
-    parser.resolveReferences();
+    Root root = parser.parseRoot(scanner);
     reader.close();
     int size = root.getSmartHomeEntityModel().items().size();
     if (size == 0) {
diff --git a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/util/TestUtils.java b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/util/TestUtils.java
index 3654470a6285e3424d750043b0df66cec295361b..1e215384019efafe9139a8139d5034e9cc10f864 100644
--- a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/util/TestUtils.java
+++ b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/util/TestUtils.java
@@ -39,7 +39,9 @@ public class TestUtils {
 
   public static SmartHomeEntityModel createModelWithGroup() {
     Root root = Root.createEmptyRoot();
-    ParserUtils.createUnknownGroup(root.getSmartHomeEntityModel(),new ArrayList<>());
+    Group group0 = new Group();
+    group0.setID("Group0");
+    root.getSmartHomeEntityModel().addGroup(group0);
 
     return root.getSmartHomeEntityModel();
   }
diff --git a/eraser-base/src/test/java/de/tudresden/inf/st/eraser/MarshallingTests.java b/eraser-base/src/test/java/de/tudresden/inf/st/eraser/MarshallingTests.java
index 60bfd0a759a03ce485bc8aa158b2ac8770340d47..2184860192791bc358fdc2987679098366b930db 100644
--- a/eraser-base/src/test/java/de/tudresden/inf/st/eraser/MarshallingTests.java
+++ b/eraser-base/src/test/java/de/tudresden/inf/st/eraser/MarshallingTests.java
@@ -1,10 +1,10 @@
 package de.tudresden.inf.st.eraser;
 
+import de.tudresden.inf.st.eraser.jastadd.parser.EraserParser;
 import de.tudresden.inf.st.eraser.jastadd_test.core.TestConfiguration;
 import de.tudresden.inf.st.eraser.jastadd_test.core.TestProperties;
 import de.tudresden.inf.st.eraser.jastadd_test.core.TestRunner;
 import de.tudresden.inf.st.eraser.jastadd_test.core.Util;
-import de.tudresden.inf.st.eraser.parser.EraserParserHelper;
 import de.tudresden.inf.st.eraser.util.ParserUtils;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.MethodSource;
@@ -25,7 +25,7 @@ public class MarshallingTests {
   @ParameterizedTest
   @MethodSource("getTests")
   public void runTest(TestConfiguration unitTest) throws Exception {
-    EraserParserHelper.setCheckUnusedElements(false);
+    EraserParser.setCheckUnusedElements(false);
     ParserUtils.setVerboseLoading(false);
     TestRunner.runTest(unitTest);
   }
diff --git a/eraser-base/src/test/java/de/tudresden/inf/st/eraser/OpenHabImporterTest.java b/eraser-base/src/test/java/de/tudresden/inf/st/eraser/OpenHabImporterTest.java
index c6ebe6ce0e35e17fc157bd25ba5c9d2289635c71..6daa513b7d87fdae87d514c586da61b9ad8993e7 100644
--- a/eraser-base/src/test/java/de/tudresden/inf/st/eraser/OpenHabImporterTest.java
+++ b/eraser-base/src/test/java/de/tudresden/inf/st/eraser/OpenHabImporterTest.java
@@ -8,11 +8,12 @@ import org.junit.jupiter.params.provider.MethodSource;
 
 import java.net.MalformedURLException;
 import java.net.URL;
+import java.nio.file.InvalidPathException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.List;
 
-import static org.junit.Assert.fail;
+import static org.junit.jupiter.api.Assertions.fail;
 
 /**
  * Testing import from openHAB using {@link OpenHab2Importer}.
@@ -49,8 +50,13 @@ public class OpenHabImporterTest {
     }
 
     private URL makeFileUrl(String fileName, String emptyFileName) throws MalformedURLException {
-      Path path = Paths.get(directory, fileName);
-      if (!path.toFile().exists() || !path.toFile().isFile()) {
+      Path path = null;
+      try {
+        path = Paths.get(directory, fileName);
+      } catch (InvalidPathException e) {
+        // use empty file
+      }
+      if (path == null || !path.toFile().exists() || !path.toFile().isFile()) {
         path = Paths.get(directory, "..", emptyFileName);
       }
       return path.toUri().toURL();
diff --git a/eraser-base/src/test/java/de/tudresden/inf/st/eraser/RulesTest.java b/eraser-base/src/test/java/de/tudresden/inf/st/eraser/RulesTest.java
index f4da998f546f33e1ad976b2d9aef08b329185966..b056ba020adc39a9d6bcf5c17a7563acf5da6184 100644
--- a/eraser-base/src/test/java/de/tudresden/inf/st/eraser/RulesTest.java
+++ b/eraser-base/src/test/java/de/tudresden/inf/st/eraser/RulesTest.java
@@ -324,7 +324,7 @@ public class RulesTest {
 
     // add StateSyncGroup as rule to root and trigger rewrite
     model.getRoot().addRule(group);
-    model.getRoot().doFullTraversal();
+    model.getRoot().doSafeFullTraversal();
 
 
     colorItem1.setState(TupleHSB.parse("0,0,100"));
@@ -362,7 +362,7 @@ public class RulesTest {
 
     // add StateSyncGroup as rule to root and trigger rewrite
     model.getRoot().addRule(group);
-    model.getRoot().doFullTraversal();
+    model.getRoot().doSafeFullTraversal();
 
     Instant i1 = Instant.now();
     dateTimeItem1.setState(i1);
@@ -402,7 +402,7 @@ public class RulesTest {
 
     // add StateSyncGroup as rule to root and trigger rewrite
     model.getRoot().addRule(group);
-    model.getRoot().doFullTraversal();
+    model.getRoot().doSafeFullTraversal();
 
 
     numberItem1.setState(123);
@@ -441,7 +441,7 @@ public class RulesTest {
 
     // add StateSyncGroup as rule to root and trigger rewrite
     model.getRoot().addRule(group);
-    model.getRoot().doFullTraversal();
+    model.getRoot().doSafeFullTraversal();
 
 
     stringItem1.setState("123");
@@ -483,7 +483,7 @@ public class RulesTest {
 
     // add StateSyncGroup as rule to root and trigger rewrite
     model.getRoot().addRule(group);
-    model.getRoot().doFullTraversal();
+    model.getRoot().doSafeFullTraversal();
 
 
     switchItem3.setState(false);
diff --git a/eraser-base/src/test/java/de/tudresden/inf/st/eraser/jastadd_test/core/TestConfiguration.java b/eraser-base/src/test/java/de/tudresden/inf/st/eraser/jastadd_test/core/TestConfiguration.java
index df9495aaaddac0ca310b319c07a8b614c067d493..860efad6e30f530a7e23dacc4c8c43cc7c3ab864 100644
--- a/eraser-base/src/test/java/de/tudresden/inf/st/eraser/jastadd_test/core/TestConfiguration.java
+++ b/eraser-base/src/test/java/de/tudresden/inf/st/eraser/jastadd_test/core/TestConfiguration.java
@@ -135,12 +135,10 @@ public class TestConfiguration {
     File[] files = dir.listFiles();
     if (files == null) return;
     for (File file: files) {
-      if (!file.isDirectory()) {
-        file.delete();
-      } else {
+      if (file.isDirectory()) {
         cleanDirectory(file);
-        file.delete();
       }
+      file.delete();
     }
   }
 
diff --git a/eraser-base/src/test/java/de/tudresden/inf/st/eraser/jastadd_test/core/TestProperties.java b/eraser-base/src/test/java/de/tudresden/inf/st/eraser/jastadd_test/core/TestProperties.java
index 1b8099c99948940e0c376e59b843221005471b53..5ee91322f00e961a46d4e77955c08cde8d76a638 100644
--- a/eraser-base/src/test/java/de/tudresden/inf/st/eraser/jastadd_test/core/TestProperties.java
+++ b/eraser-base/src/test/java/de/tudresden/inf/st/eraser/jastadd_test/core/TestProperties.java
@@ -136,7 +136,7 @@ public class TestProperties extends Properties {
   private static void addPaths(Collection<String> list, String pathList) {
     String[] items = pathList.split(",");
     for (String item : items) {
-      item = item.trim().replace('\\', '/');
+      item = Util.platformIndependentPath(item.trim());
       if (!item.isEmpty()) {
         list.add(item);
       }
diff --git a/eraser-base/src/test/java/de/tudresden/inf/st/eraser/jastadd_test/core/Util.java b/eraser-base/src/test/java/de/tudresden/inf/st/eraser/jastadd_test/core/Util.java
index 019e9da2b3c94da40e8c7b5b52c4bc16c755de3b..7fb7320f9127648a1ee2526ea5878b11360cc603 100644
--- a/eraser-base/src/test/java/de/tudresden/inf/st/eraser/jastadd_test/core/Util.java
+++ b/eraser-base/src/test/java/de/tudresden/inf/st/eraser/jastadd_test/core/Util.java
@@ -38,14 +38,18 @@ import java.util.*;
  */
 public class Util {
 
+  public static String platformIndependentPath(String regularPath) {
+    return regularPath.replace('\\', '/');
+  }
+
 //  public static final String TEST_ROOT = "src/test/resources/tests";
 //  public static final String TEST_ROOT = "tests";
 
   /**
    * Find all test directories
-   * @param testRoot
-   * @param tests
-   * @param excludes
+   * @param testRoot  root directory of tests
+   * @param tests     list of test configurations to add
+   * @param excludes  paths to exclude
    */
   private static void addChildTestDirs(File testRoot, List<TestConfiguration> tests,
                                        Collection<String> excludes) {
@@ -58,7 +62,7 @@ public class Util {
       return;
     }
     for (File child: files) {
-      addTestDir(testRoot.getPath(), child, tests, excludes);
+      addTestDir(platformIndependentPath(testRoot.getPath()), child, tests, excludes);
     }
   }
 
@@ -178,7 +182,7 @@ public class Util {
       addTestDir(properties.getTestRoot(), new File(properties.getTestRoot()), tests, excludes);
     } else {
       for (String include: includes) {
-        addByPattern(new File(properties.getTestRoot()), include.replace('\\', '/'), tests, excludes);
+        addByPattern(new File(properties.getTestRoot()), platformIndependentPath(include), tests, excludes);
       }
     }
 
diff --git a/eraser.spark/src/main/java/de/tudresden/inf/st/eraser/spark/Application.java b/eraser.spark/src/main/java/de/tudresden/inf/st/eraser/spark/Application.java
index 3427804342d059baa96f933f9781d7bdf356501a..1e793fb89c91b7b5b496fa027825ae2a634d7602 100644
--- a/eraser.spark/src/main/java/de/tudresden/inf/st/eraser/spark/Application.java
+++ b/eraser.spark/src/main/java/de/tudresden/inf/st/eraser/spark/Application.java
@@ -114,7 +114,7 @@ public class Application {
 
           //--- PUT /model/items/:identifier ---
           Spark.put("", (request, response) -> {
-            SmartHomeEntityModel SmartHomeEntityModel = root.getSmartHomeEntityModel();
+            SmartHomeEntityModel smartHomeEntityModel = root.getSmartHomeEntityModel();
             Item item;
             try {
               item = ParserUtils.parseItem(request.body());
@@ -122,7 +122,7 @@ public class Application {
               logger.catching(e);
               return makeError(response, 400, "Could not create item. Error message: " + e.getMessage());
             }
-            if (!SmartHomeEntityModel.resolveItem(item.getID()).isPresent()) {
+            if (!smartHomeEntityModel.resolveItem(item.getID()).isPresent()) {
               root.getSmartHomeEntityModel().addNewItem(item);
               response.status(201);
               return "OK";
@@ -242,13 +242,13 @@ public class Application {
         item.getLabel(),
         item.getTopic() != null ? item.getTopic().getTopicString() : null,
         item.isSendState(),
-        wrapMetaData(item.getMetaDataList()));
+        wrapMetaData(item.getMetaData()));
   }
 
-  private Map<String, String> wrapMetaData(JastAddList<ItemMetaData> itemMetaDataList) {
+  private Map<String, String> wrapMetaData(MetaData metaData) {
     Map<String, String> result = new HashMap<>();
-    for (ItemMetaData metaData : itemMetaDataList) {
-      result.put(metaData.getKey(), metaData.getValue());
+    for (KeyValuePair keyValuePair : metaData.getKeyValuePairList()) {
+      result.put(keyValuePair.getKey(), keyValuePair.getValue());
     }
     return result;
   }
diff --git a/feedbackloop.api/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/api/Execute.java b/feedbackloop.api/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/api/Execute.java
index 3b3da24d3cf88899fec03a538198e2472f72ac1a..8e824e83a0f9b73331d9629a7aa06bb97114ea98 100644
--- a/feedbackloop.api/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/api/Execute.java
+++ b/feedbackloop.api/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/api/Execute.java
@@ -20,13 +20,6 @@ public interface Execute {
    */
   void setKnowledgeBase(Root knowledgeBase);
 
-  /**
-   * <b>Deprecated</b>: Use {@link #updateItems(Iterable)} instead.
-   * @param brightnessAndRgbForItems Map, keys are item names, values are RGB and brightness values
-   */
-  @Deprecated
-  void updateItems(Map<String, Tuple<Integer, ValuesRGB>> brightnessAndRgbForItems);
-
   /**
    * Updates items according to given updates
    * @param updates tuples containing item and its new HSB value
diff --git a/feedbackloop.api/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/api/Learner.java b/feedbackloop.api/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/api/Learner.java
index a08a6fc2a444b4a30fd4f234acfd4449c075d444..0d92fbbd0ef22abb47cc16c2ba568a0ca02116ec 100644
--- a/feedbackloop.api/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/api/Learner.java
+++ b/feedbackloop.api/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/api/Learner.java
@@ -118,9 +118,6 @@ public interface Learner {
 	 * */
 	EncogModel getTrainedModel(int modelID);
 
-	@Deprecated
-	EncogModel getTrainedModel(URL url, int modelID);
-	
 	/**
 	 * 
 	 * Method for getting normalizer of a model for a specific column/input.
diff --git a/feedbackloop.execute/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/execute/ExecuteImpl.java b/feedbackloop.execute/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/execute/ExecuteImpl.java
index 2b305dfd4ffc42996e2d3f157fe24bc1b0d3bc62..fd2ab95d6792ea57ccae539c2ff6cc6f5d081d97 100644
--- a/feedbackloop.execute/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/execute/ExecuteImpl.java
+++ b/feedbackloop.execute/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/execute/ExecuteImpl.java
@@ -1,16 +1,8 @@
 package de.tudresden.inf.st.eraser.feedbackloop.execute;
 
-import de.tudresden.inf.st.eraser.commons.color.ColorUtils;
-import de.tudresden.inf.st.eraser.commons.color.ColorUtils.ValuesIntegralHSB;
-import de.tudresden.inf.st.eraser.commons.color.ColorUtils.ValuesRGB;
 import de.tudresden.inf.st.eraser.feedbackloop.api.Execute;
-import de.tudresden.inf.st.eraser.jastadd.model.*;
-import de.tudresden.inf.st.eraser.util.Tuple;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
-import java.util.*;
-import java.util.function.Consumer;
+import de.tudresden.inf.st.eraser.jastadd.model.ItemUpdate;
+import de.tudresden.inf.st.eraser.jastadd.model.Root;
 
 /**
  * Reference implementation for Execute.
@@ -19,38 +11,11 @@ import java.util.function.Consumer;
  */
 public class ExecuteImpl implements Execute {
 
-  private Logger logger = LogManager.getLogger(ExecuteImpl.class);
-  private Root knowledgeBase;
+//  private Root knowledgeBase;
 
   @Override
   public void setKnowledgeBase(Root knowledgeBase) {
-    this.knowledgeBase = knowledgeBase;
-  }
-
-  @Override
-  public void updateItems(Map<String, Tuple<Integer, ValuesRGB>> brightnessAndRgbForItems) {
-    List<ItemUpdate> updates = new ArrayList<>();
-    for (Map.Entry<String, Tuple<Integer, ValuesRGB>> entry : brightnessAndRgbForItems.entrySet()) {
-      String itemId = entry.getKey();
-      resolveOrLogError(itemId, item -> {
-        if (entry.getValue() == null) {
-          return;
-        }
-        Integer brightness = entry.getValue().x;
-        ValuesRGB rgb = entry.getValue().y;
-        ValuesIntegralHSB hsb;
-        if (rgb != null) {
-          // also set rgb values
-          hsb = ColorUtils.convertRGBtoHSB(rgb).toIntegral();
-          hsb.brightness = brightness;
-        } else {
-          hsb = ValuesIntegralHSB.of(0, 100, brightness);
-        }
-        hsb.ensureBounds();
-        updates.add(new ItemUpdateColor(item, TupleHSB.of(hsb.hue, hsb.saturation, hsb.brightness)));
-      });
-    }
-    updateItems(updates);
+//    this.knowledgeBase = knowledgeBase;
   }
 
   @Override
@@ -59,12 +24,4 @@ public class ExecuteImpl implements Execute {
       preference.apply();
     }
   }
-
-  private void resolveOrLogError(String itemId, Consumer<? super Item> consumer) {
-    Optional<Item> optionalItem = knowledgeBase.getSmartHomeEntityModel().resolveItem(itemId);
-    if (!optionalItem.isPresent()) {
-      logger.warn("Could not resolve '{}' as an item.", itemId);
-    }
-    optionalItem.ifPresent(consumer);
-  }
 }
diff --git a/feedbackloop.learner/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/learner/LearnerImpl.java b/feedbackloop.learner/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/learner/LearnerImpl.java
index 83edf6b7f195d9be6420369d4590955c42449735..5c2489a393a7aa4cefa55c1383b69327059ead75 100644
--- a/feedbackloop.learner/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/learner/LearnerImpl.java
+++ b/feedbackloop.learner/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/learner/LearnerImpl.java
@@ -174,11 +174,6 @@ public class LearnerImpl implements Learner {
     return fillModel(modelID);
   }
 
-  @Override
-  public EncogModel getTrainedModel(URL url, int modelID) {
-    return fillModel(modelID);
-  }
-
   private EncogModel fillModel(int modelID) {
     EncogModel encogModel = new EncogModel("NN");
     BasicNetwork nn = models.get(modelID).getNetwork();
diff --git a/feedbackloop.learner_backup/src/main/java/de/tudresden/inf/st/eraser/feedbackloop.learner_backup/Main.java b/feedbackloop.learner_backup/src/main/java/de/tudresden/inf/st/eraser/feedbackloop.learner_backup/Main.java
index d8ff6ed7248d6e48107a5c3ecafc523cffcb984c..c1958a1255ef28b67d0ae925abea7deb73b5fae0 100644
--- a/feedbackloop.learner_backup/src/main/java/de/tudresden/inf/st/eraser/feedbackloop.learner_backup/Main.java
+++ b/feedbackloop.learner_backup/src/main/java/de/tudresden/inf/st/eraser/feedbackloop.learner_backup/Main.java
@@ -59,7 +59,7 @@ public class Main {
 
   private static Root createRootWithItemsFrom(LearnerScenarioDefinition scenarioDefinition) {
     Root result = Root.createEmptyRoot();
-    ParserUtils.createUnknownGroup(
+    ParserUtils.addToUnknownGroup(
         result.getSmartHomeEntityModel(),
         scenarioDefinition.relevantItemNames.stream().map(Main::createItem).collect(Collectors.toList()));
     return result;
diff --git a/feedbackloop.learner_backup/src/test/java/de/tudresden/inf/st/eraser/feedbackloop/learner_backup/LearnerTestUtils.java b/feedbackloop.learner_backup/src/test/java/de/tudresden/inf/st/eraser/feedbackloop/learner_backup/LearnerTestUtils.java
index fe32a87d0339b3461a2c268711a22554c75b6545..50d235f4b736ea0cb2d29f951004a977f61b449f 100644
--- a/feedbackloop.learner_backup/src/test/java/de/tudresden/inf/st/eraser/feedbackloop/learner_backup/LearnerTestUtils.java
+++ b/feedbackloop.learner_backup/src/test/java/de/tudresden/inf/st/eraser/feedbackloop/learner_backup/LearnerTestUtils.java
@@ -46,8 +46,8 @@ public class LearnerTestUtils {
         itemName -> {
           if (itemName.equals("activity")) return;
           Item item = createItem(itemName);
-          ParserUtils.createMqttTopic(item, itemName, result);
           group.addItem(item);
+          item.setTopic(result.getMqttRoot().getOrCreateMqttTopic(itemName));
         }
     );
     // init activities