diff --git a/eraser-base/src/main/jastadd/AdditionalTypes.jadd b/eraser-base/src/main/jastadd/AdditionalTypes.jadd index 32651117408970af5b3a57f4ddc48e2f7f2a0d8c..8abddb34f36d87d7f889dfd245fe83b017ae4069 100644 --- a/eraser-base/src/main/jastadd/AdditionalTypes.jadd +++ b/eraser-base/src/main/jastadd/AdditionalTypes.jadd @@ -1,16 +1,19 @@ aspect AdditionalTypes { - public class StringList extends beaver.Symbol implements Iterable<String> { - private java.util.Deque<String> delegatee = new java.util.ArrayDeque<>(); + public class ReversedList<T> extends beaver.Symbol implements Iterable<T> { + private java.util.Deque<T> delegatee = new java.util.ArrayDeque<>(); - public java.util.Iterator<String> iterator() { + public java.util.Iterator<T> iterator() { return delegatee.descendingIterator(); } - public void add(String s) { - delegatee.add(s); + public void add(T t) { + delegatee.add(t); } } + public class StringList extends ReversedList<String> {} + public class ItemList extends ReversedList<Item> {} + public class TypedKeyMap<T> extends beaver.Symbol implements Iterable<AbstractMap.SimpleEntry<T, String>> { private java.util.Deque<AbstractMap.SimpleEntry<T, String>> delegatee = new java.util.ArrayDeque<>(); diff --git a/eraser-base/src/main/jastadd/Item.jrag b/eraser-base/src/main/jastadd/Item.jrag index 5c03824f96b9de7db5a5ddeb0c4a330b516db4f9..ece1fd875c26b2d6f4bc94aa712f9bfe56a8884b 100644 --- a/eraser-base/src/main/jastadd/Item.jrag +++ b/eraser-base/src/main/jastadd/Item.jrag @@ -281,6 +281,8 @@ aspect ItemHandling { stateUpdated(shouldSendState); } + syn ItemObserver Item.getItemObserver() = new ItemObserver(); + //--- stateUpdated --- /** * Called, whenever the state of an item is updated. Does various things including: @@ -300,9 +302,7 @@ aspect ItemHandling { logger.catching(e); } } - if (hasItemObserver()) { - getItemObserver().apply(); - } + getItemObserver().apply(); } //--- sendState --- @@ -407,6 +407,7 @@ aspect ItemHandling { SetStateFromTriggeringItemAction action = new SetStateFromTriggeringItemAction(); action.setAffectedItem(controlledItem); rule.addAction(action); + rule.activateFor(controllerItem); return rule; @@ -464,6 +465,7 @@ aspect ItemHandling { //--- copyStateTo --- protected abstract void Item.copyStateTo(Item stateReceiver); + protected void ItemWithBooleanState.copyStateTo(Item stateReceiver) { stateReceiver.setStateFromBoolean(this.getState()); } @@ -480,6 +482,9 @@ aspect ItemHandling { stateReceiver.setStateFromInstant(this.getState()); } + + + private void ColorItem.setBrightness(int value) { setState(getState().withDifferentBrightness(value)); } diff --git a/eraser-base/src/main/jastadd/Navigation.jrag b/eraser-base/src/main/jastadd/Navigation.jrag index 3e34287f52b221ad4525c3c0766db2f8b8ae697a..dd4e283a155b68dd432303debd93dd094aee06eb 100644 --- a/eraser-base/src/main/jastadd/Navigation.jrag +++ b/eraser-base/src/main/jastadd/Navigation.jrag @@ -29,104 +29,6 @@ aspect Navigation { return result; } - //--- resolveThingType --- - syn java.util.Optional<ThingType> SmartHomeEntityModel.resolveThingType(String thingTypeId) { - for (ThingType thingType : this.getThingTypeList()) { - if (thingType.getID().equals(thingTypeId)) { - return java.util.Optional.of(thingType); - } - } - return java.util.Optional.empty(); - } - - //--- 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); - } - } - } - return java.util.Optional.empty(); - } - - //--- resolveChannelType --- - syn java.util.Optional<ChannelType> SmartHomeEntityModel.resolveChannelType(String channelTypeId) { - for (ChannelType channelType : this.getChannelTypeList()) { - if (channelType.getID().equals(channelTypeId)) { - return java.util.Optional.of(channelType); - } - } - 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)) { - return java.util.Optional.of(item); - } - } - return java.util.Optional.empty(); - } - - //--- resolveGroup --- - syn java.util.Optional<Group> SmartHomeEntityModel.resolveGroup(String groupId) { - for (Group group : this.getGroupList()) { - if (group.getID().equals(groupId)) { - return java.util.Optional.of(group); - } - } - return java.util.Optional.empty(); - } - - //--- resolveMqttTopic --- - syn java.util.Optional<MqttTopic> Root.resolveMqttTopic(String mqttTopicId) { - return this.getMqttRoot().resolveTopic(mqttTopicId); - } - - //--- resolveItemCategory --- - syn java.util.Optional<ItemCategory> SmartHomeEntityModel.resolveItemCategory(String categoryName) { - for (ItemCategory category : getItemCategoryList()) { - if (category.getName().equals(categoryName)) { - return java.util.Optional.of(category); - } - } - return java.util.Optional.empty(); - } - - //--- resolveActivity --- - syn java.util.Optional<Activity> Root.resolveActivity(int identifier) { - for (Activity activity : getMachineLearningRoot().getActivityList()) { - if (activity.getIdentifier() == identifier) { - return java.util.Optional.of(activity); - } - } - return java.util.Optional.empty(); - } - syn java.util.Optional<Activity> Root.resolveActivity(String label) { - for (Activity activity : getMachineLearningRoot().getActivityList()) { - if (activity.getLabel().equals(label)) { - return java.util.Optional.of(activity); - } - } - return java.util.Optional.empty(); - } - - //--- resolveChangeEvent --- - syn java.util.Optional<ChangeEvent> Root.resolveChangeEvent(int identifier) { - for (ChangeEvent changeEvent : getMachineLearningRoot().getChangeEventList()) { - if (changeEvent.getIdentifier() == identifier) { - return java.util.Optional.of(changeEvent); - } - } - return java.util.Optional.empty(); - } - //--- containingThing --- inh Thing Channel.containingThing(); eq Thing.getChannel().containingThing() = this; diff --git a/eraser-base/src/main/jastadd/Printing.jrag b/eraser-base/src/main/jastadd/Printing.jrag index 0b454039a9b84c02c68f2cef4593c0a334b99b37..80661e43e7f221d38f8c5e242abe0bd3753259ec 100644 --- a/eraser-base/src/main/jastadd/Printing.jrag +++ b/eraser-base/src/main/jastadd/Printing.jrag @@ -3,7 +3,7 @@ aspect Printing { String ASTNode.safeID(ModelElement elem) { return elem == null ? "NULL" : elem.getID(); } - syn String Root.prettyPrint() { + eq Root.prettyPrint() { StringBuilder sb = new StringBuilder(); sb.append(getSmartHomeEntityModel().prettyPrint()); sb.append(getMqttRoot().prettyPrint()); @@ -13,7 +13,7 @@ aspect Printing { } //--- SmartHomeEntityModel.prettyPrint() --- - syn String SmartHomeEntityModel.prettyPrint() { + eq SmartHomeEntityModel.prettyPrint() { StringBuilder sb = new StringBuilder(); for (Thing t : getThingList()) { sb.append(t.prettyPrint()); @@ -40,7 +40,7 @@ aspect Printing { } //Thing: id="" label="" type="" channels=["CHANNEL_ID", "CHANNEL_ID"] ; - syn String Thing.prettyPrint() { + eq Thing.prettyPrint() { return new MemberPrinter("Thing") .addRequired("id", getID()) .addNonDefault("label", getLabel()) @@ -50,7 +50,7 @@ aspect Printing { } //ITEM_TYPE Item: id="" label="" state="" category="" topic=""; - syn String Item.prettyPrint() { + eq Item.prettyPrint() { return new MemberPrinter(prettyPrintType()) .addRequired("id", getID()) .addNonDefault("label", getLabel()) @@ -79,7 +79,7 @@ aspect Printing { eq DefaultItem.prettyPrintType() = "Item" ; // special ActivityItem printing. Always omit state. - syn String ActivityItem.prettyPrint() { + eq ActivityItem.prettyPrint() { return new MemberPrinter(prettyPrintType()) .addRequired("id", getID()) .addNonDefault("label", getLabel()) @@ -94,7 +94,7 @@ aspect Printing { //Group: id="" groups=["GROUP_ID", "GROUP_ID"] items=["ITEM_ID", "ITEM_ID"] aggregation=AGG; // AGG either '"agg-name"', or '"agg-name" ("param1", "param2")' - syn String Group.prettyPrint() { + eq Group.prettyPrint() { return new MemberPrinter("Group") .addRequired("id", getID()) .addNonDefault("label", getLabel()) @@ -104,7 +104,6 @@ aspect Printing { .build(); } - syn String GroupAggregationFunction.prettyPrint(); eq SimpleGroupAggregationFunction.prettyPrint() { if (getFunctionName() == SimpleGroupAggregationFunctionName.EQUALITY) { return ""; @@ -120,7 +119,7 @@ aspect Printing { } //ThingType: id="" label="" description="" parameters=["PARAM_ID", "PARAM_ID"] channelTypes=["CHANNEL_TYPE_ID", "CHANNEL_TYPE_ID"]; - syn String ThingType.prettyPrint() { + eq ThingType.prettyPrint() { return new MemberPrinter("ThingType") .addRequired("id", getID()) .addNonDefault("label", getLabel()) @@ -131,7 +130,7 @@ aspect Printing { } //Parameter: id="" label="" description="" type="" default="" required; - syn String Parameter.prettyPrint() { + eq Parameter.prettyPrint() { return new MemberPrinter("Parameter") .addRequired("id", getID()) .addNonDefault("label", getLabel()) @@ -144,7 +143,7 @@ aspect Printing { } //ChannelType: id="" label="" description="" itemType="" category="" readyOnly; - syn String ChannelType.prettyPrint() { + eq ChannelType.prettyPrint() { return new MemberPrinter("ChannelType") .addRequired("id", getID()) .addNonDefault("label", getLabel()) @@ -160,7 +159,7 @@ aspect Printing { syn String SimpleChannelCategory.prettyPrint() = getValue(); //Channel: id="" type="" links=["ITEM_ID", "ITEM_ID"]; - syn String Channel.prettyPrint() { + eq Channel.prettyPrint() { return new MemberPrinter("Channel") .addRequired("id", getID()) .addRequired("type", getType(), ChannelType::getID) @@ -178,7 +177,7 @@ aspect Printing { } //Mqtt: incoming="" outgoing="" host=""; - syn String MqttRoot.prettyPrint() { + eq MqttRoot.prettyPrint() { return new MemberPrinter("Mqtt") .addNonDefault("incoming", getIncomingPrefix()) .addNonDefault("outgoing", getOutgoingPrefix()) @@ -187,7 +186,7 @@ aspect Printing { } //Influx: user="" password="" dbName="" host="" ; - syn String InfluxRoot.prettyPrint() { + eq InfluxRoot.prettyPrint() { return new MemberPrinter("Influx") .addNonDefault("user", getUser(), DEFAULT_USER) .addNonDefault("password", getPassword(), DEFAULT_PASSWORD) @@ -197,7 +196,7 @@ aspect Printing { } // Activities: { index: "name" } - syn String MachineLearningRoot.prettyPrint() { + eq MachineLearningRoot.prettyPrint() { return new MemberPrinter("ML") .addNodes("activities", getNumActivity(), getActivityList(), activity -> activity.getIdentifier() + ":\"" + activity.getLabel() + "\"", @@ -215,7 +214,7 @@ aspect Printing { syn String PowerExpression.prettyPrint() = "(" + getLeftOperand().prettyPrint() + " ^ " + getRightOperand().prettyPrint() + ")"; syn String ParenthesizedLogicalExpression.prettyPrint() = "(" + getOperand().prettyPrint() + ")"; syn String NotExpression.prettyPrint() = "!" + getOperand().prettyPrint(); - syn String ComparingExpression.prettyPrint() { + eq ComparingExpression.prettyPrint() { switch (getComparator()) { case NotEquals: return "(" + getLeftOperand().prettyPrint() + " != " + getRightOperand().prettyPrint() + ")"; case Equals: return "(" + getLeftOperand().prettyPrint() + " == " + getRightOperand().prettyPrint() + ")"; @@ -230,4 +229,16 @@ aspect Printing { syn String OrExpression.prettyPrint() = "(" + getLeftOperand().prettyPrint() + " | " + getRightOperand().prettyPrint() + ")"; syn String Designator.prettyPrint() = getItem().getID(); + // Rules + eq Rule.prettyPrint() { + return new MemberPrinter("Rule") + .addIds("TriggeringItems", getObserverList().size(), getObserverList(), + io -> io.observedItem().getID()) + .addNodes("Condition", getNumCondition(), getConditionList(), + Condition::toString) + .addNodes("Action", getNumAction(), getActionList(), + Action::toString) + .build(); + } + } diff --git a/eraser-base/src/main/jastadd/Resolving.jrag b/eraser-base/src/main/jastadd/Resolving.jrag new file mode 100644 index 0000000000000000000000000000000000000000..e1d793c1587afcd16b232197a2e87b21c9d6f08b --- /dev/null +++ b/eraser-base/src/main/jastadd/Resolving.jrag @@ -0,0 +1,106 @@ +aspect Resolving { + + //--- resolveThingType --- + syn java.util.Optional<ThingType> SmartHomeEntityModel.resolveThingType(String thingTypeId) { + for (ThingType thingType : this.getThingTypeList()) { + if (thingType.getID().equals(thingTypeId)) { + return java.util.Optional.of(thingType); + } + } + return java.util.Optional.empty(); + } + + //--- 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); + } + } + } + return java.util.Optional.empty(); + } + + //--- resolveChannelType --- + syn java.util.Optional<ChannelType> SmartHomeEntityModel.resolveChannelType(String channelTypeId) { + for (ChannelType channelType : this.getChannelTypeList()) { + if (channelType.getID().equals(channelTypeId)) { + return java.util.Optional.of(channelType); + } + } + 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)) { + return java.util.Optional.of(item); + } + } + return java.util.Optional.empty(); + } + + //--- resolveGroup --- + syn java.util.Optional<Group> SmartHomeEntityModel.resolveGroup(String groupId) { + for (Group group : this.getGroupList()) { + if (group.getID().equals(groupId)) { + return java.util.Optional.of(group); + } + } + return java.util.Optional.empty(); + } + + //--- resolveMqttTopic --- + syn java.util.Optional<MqttTopic> Root.resolveMqttTopic(String mqttTopicId) { + return this.getMqttRoot().resolveTopic(mqttTopicId); + } + + //--- resolveItemCategory --- + syn java.util.Optional<ItemCategory> SmartHomeEntityModel.resolveItemCategory(String categoryName) { + for (ItemCategory category : getItemCategoryList()) { + if (category.getName().equals(categoryName)) { + return java.util.Optional.of(category); + } + } + return java.util.Optional.empty(); + } + + //--- resolveActivity --- + syn java.util.Optional<Activity> Root.resolveActivity(int identifier) { + for (Activity activity : getMachineLearningRoot().getActivityList()) { + if (activity.getIdentifier() == identifier) { + return java.util.Optional.of(activity); + } + } + return java.util.Optional.empty(); + } + syn java.util.Optional<Activity> Root.resolveActivity(String label) { + for (Activity activity : getMachineLearningRoot().getActivityList()) { + if (activity.getLabel().equals(label)) { + return java.util.Optional.of(activity); + } + } + return java.util.Optional.empty(); + } + + //--- resolveChangeEvent --- + syn java.util.Optional<ChangeEvent> Root.resolveChangeEvent(int identifier) { + for (ChangeEvent changeEvent : getMachineLearningRoot().getChangeEventList()) { + if (changeEvent.getIdentifier() == identifier) { + return java.util.Optional.of(changeEvent); + } + } + return java.util.Optional.empty(); + } + + // implementing resolving for relations + refine RefResolverStubs eq StateSyncGroup.resolveTargetItemByToken(String id, int position) { + return getRoot().getSmartHomeEntityModel().resolveItem(id).orElseThrow(() -> new RuntimeException("Item '" + id + "' not found!")); + } + +} diff --git a/eraser-base/src/main/jastadd/Rules.jrag b/eraser-base/src/main/jastadd/Rules.jrag index 26c18ae2f1faba258a2db6aa60d5b0c6cf974575..e2acfca41c585dedfc71bd56c0772abcef36d247 100644 --- a/eraser-base/src/main/jastadd/Rules.jrag +++ b/eraser-base/src/main/jastadd/Rules.jrag @@ -24,29 +24,20 @@ aspect Rules { eq Item.getItemObserver().observedItem() = this; public void Rule.activateFor(Item item) { - // 1) Get or create new ItemObserver, and add it to Root - ItemObserver itemObserver; - if (item.hasItemObserver()) { - itemObserver = item.getItemObserver(); - // 1.a) Check if observer already triggers this rule - for (Rule rule : itemObserver.getTriggeredRules()) { - if (rule.equals(this)) { - logger.warn("Rule already activated for item {}. Ignoring.", item); - return; - } + // 1) Get ItemObserver + ItemObserver itemObserver = item.getItemObserver(); + // 1.a) Check if observer already triggers this rule + for (Rule rule : itemObserver.getTriggeredRules()) { + if (rule.equals(this)) { + logger.warn("Rule already activated for item {}. Ignoring.", item); + return; } - } else { - itemObserver = new ItemObserver(); - item.setItemObserver(itemObserver); } - // 2) Link event and itemObserver itemObserver.addTriggeredRule(this); } public void Rule.deactivateFor(Item item) { - if (item.hasItemObserver()) { - item.getItemObserver().removeTriggeredRule(this); - } + item.getItemObserver().removeTriggeredRule(this); } @@ -73,14 +64,6 @@ aspect Rules { return executor.scheduleAtFixedRate(() -> trigger(null), initialDelay, period, unit); } - public void Rule.removeActivationOf(Item item) { - if (item.hasItemObserver()) { - item.getItemObserver().removeTriggeredRule(this); - } else { - // there is no observer yet - logger.warn("Item {} was never activated before.", item); - } - } // --- Condition.holdsFor --- syn boolean Condition.holdsFor(Item item); @@ -109,7 +92,11 @@ aspect Rules { getAffectedItem().setStateFromString(getNewStateProvider().get()); } public void SetStateFromTriggeringItemAction.applyFor(Item item) { - item.copyStateTo(getAffectedItem()); + Item target = getAffectedItem(); + if (target==item) { + return; + } + item.copyStateTo(target); } public void SetStateFromItemsAction.applyFor(Item item) { getAffectedItem().setStateFromString(getCombinator().apply(getSourceItems())); @@ -122,3 +109,18 @@ aspect Rules { } } + +aspect StateSyncGroup { + rewrite StateSyncGroup { + to Rule { + Rule rule = new Rule(); + + for (Item item : getTargetItemList()) { + rule.addAction(new SetStateFromTriggeringItemAction(item)); + rule.addObserver(item.getItemObserver()); + } + + return rule; + } + } +} diff --git a/eraser-base/src/main/jastadd/Rules.relast b/eraser-base/src/main/jastadd/Rules.relast index 25a0fd2b41f3f1051e03d06ace4f41b6907030e6..22176613130d4fd2802379fffb6d406819a2cd04 100644 --- a/eraser-base/src/main/jastadd/Rules.relast +++ b/eraser-base/src/main/jastadd/Rules.relast @@ -28,3 +28,6 @@ MultiplyDoubleToStateAction : SetStateAction ::= <Multiplier:double> ; ItemObserver ::= ; rel ItemObserver.TriggeredRule* <-> Rule.Observer* ; + +StateSyncGroup : Rule ; +rel StateSyncGroup.TargetItem* -> Item; diff --git a/eraser-base/src/main/jastadd/eraser.flex b/eraser-base/src/main/jastadd/eraser.flex index 5f70414fa4005ad1baf1b0623459ce7c5f2f69f2..21987f14a9762c3f7c9de8dadd405ad5431ad7b5 100644 --- a/eraser-base/src/main/jastadd/eraser.flex +++ b/eraser-base/src/main/jastadd/eraser.flex @@ -57,6 +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); } // 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 68967a5655bf12cab2e2127db29e1f0ac5ebd808..1a055ec64b0166fa0bea7882e6e0921e7d280976 100644 --- a/eraser-base/src/main/jastadd/eraser.parser +++ b/eraser-base/src/main/jastadd/eraser.parser @@ -3,6 +3,7 @@ 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 java.util.Map; import java.util.HashMap; :} ; @@ -31,6 +32,7 @@ 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; :} @@ -42,6 +44,7 @@ Root goal = | 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); :} @@ -137,6 +140,10 @@ Item item_body = | {: return eph.createItem(); :} ; +Item item_ref = + TEXT.n {: return Item.createRef(n); :} + ; + Group group = GROUP COLON group_body.gb SEMICOLON {: return gb; :} ; @@ -154,6 +161,21 @@ Group group_body = | {: return new Group(); :} ; + +StateSyncGroup state_sync_group = SYNCSTATE COLON syncstate_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(); :} + ; + ThingType thing_type = THING_TYPE COLON thing_type_body.ttb SEMICOLON {: return ttb; :} ; @@ -284,6 +306,21 @@ 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(); :} diff --git a/eraser-base/src/main/jastadd/shem.relast b/eraser-base/src/main/jastadd/shem.relast index ca1878ac6d2722a3e2a1671e066b19c5175c7be6..a71a0ede3307b937a77798d9713ddbae197d323e 100644 --- a/eraser-base/src/main/jastadd/shem.relast +++ b/eraser-base/src/main/jastadd/shem.relast @@ -25,7 +25,9 @@ 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] ; +abstract Item : LabelledModelElement ::= <_fetched_data:boolean> MetaData:ItemMetaData* /ItemObserver/; + + rel Item.Category? -> ItemCategory ; abstract ItemWithBooleanState : Item ::= <_state:boolean> ; 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 795d469bc786cfde73b12f8329956cbb79bb953a..01b91770dc1d4a58dfd4f03033ea1068c9282a98 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 @@ -73,6 +73,7 @@ public class EraserParserHelper { // when parsing expressions this.root = EraserParserHelper.initialRoot != null ? EraserParserHelper.initialRoot : createRoot(); } + if (checkUnusedElements) { fillUnused(); } @@ -93,6 +94,7 @@ public class EraserParserHelper { resolveList(itemMap, missingItemLinkListMap, Channel::addLinkedItem); resolveList(groupMap, missingSubGroupListMap, Group::addGroup); resolveList(itemMap, missingItemListMap, this::addItemToGroup); + resolveList(channelTypeMap, missingChannelTypeListMap, ThingType::addChannelType); resolveList(parameterMap, missingParameterListMap, ThingType::addParameter); resolveList(itemMap, missingControllingListMap, Item::addControlling); @@ -103,6 +105,8 @@ public class EraserParserHelper { if (checkUnusedElements) { checkUnusedElements(); } + + this.root.treeResolveAll(); } private void addItemToGroup(Group group, Item item) { @@ -292,11 +296,6 @@ public class EraserParserHelper { return item; } - public Item setControlling(Item item, StringList controlling) { - missingControllingListMap.put(item, controlling); - return item; - } - public Item retype(Item itemWithCorrectType, Item prototype) { itemWithCorrectType.setID(prototype.getID()); itemWithCorrectType.setLabel(prototype.getLabel()); diff --git a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/util/MemberPrinter.java b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/util/MemberPrinter.java index a6c89a42bca8e978f837ad805ec4278634f3afa4..53acf15253fcfc3a91067ca332ea8e2f2cdf7642 100644 --- a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/util/MemberPrinter.java +++ b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/util/MemberPrinter.java @@ -80,9 +80,9 @@ public class MemberPrinter { * @param listOfNodes the list of nodes * @param mapping a function to map a node to a ModelElement */ - private <T extends ASTNode> void concatIds(Iterable<T> listOfNodes, - Function<T, ? extends ModelElement> mapping) { - concatNodes(listOfNodes, t -> mapping.apply(t).getID(), true); + private <T extends ASTNode<?>> void concatIds(Iterable<T> listOfNodes, + Function<T, String> mapping) { + concatNodes(listOfNodes, mapping, true); } /** @@ -91,7 +91,7 @@ public class MemberPrinter { * @param listOfNodes the list of nodes * @param mapping a function to map a node to a String */ - private <T extends ASTNode> void concatNodes(Iterable<T> listOfNodes, + private <T extends ASTNode<?>> void concatNodes(Iterable<T> listOfNodes, Function<T, String> mapping, boolean quote) { boolean first = true; @@ -146,9 +146,9 @@ public class MemberPrinter { * @param mapping A function to map a node to a ModelElement * @return this */ - public <T extends ASTNode> MemberPrinter addIds( + public <T extends ASTNode<?>> MemberPrinter addIds( String name, int count, Iterable<T> listOfNodes, - Function<T, ? extends ModelElement> mapping) { + Function<T, String> mapping) { if (count > 0) { sb.append(' ').append(name).append("=["); concatIds(listOfNodes, mapping); @@ -168,7 +168,7 @@ public class MemberPrinter { * @param <T> The type of all nodes * @return this */ - public <T extends ASTNode> MemberPrinter addNodes(String name, int count, Iterable<T> listOfNodes, + public <T extends ASTNode<?>> MemberPrinter addNodes(String name, int count, Iterable<T> listOfNodes, Function<T, String> mapping) { return addNodes(name, count, listOfNodes, mapping, ListBracketType.SQUARE); } @@ -183,7 +183,7 @@ public class MemberPrinter { * @param bracketType The type of brackets to enclose the list with * @return this */ - public <T extends ASTNode> MemberPrinter addNodes(String name, int count, Iterable<T> listOfNodes, + public <T extends ASTNode<?>> MemberPrinter addNodes(String name, int count, Iterable<T> listOfNodes, Function<T, String> mapping, ListBracketType bracketType) { if (count > 0) { sb.append(' ').append(name).append("=").append(bracketType.begin); @@ -214,7 +214,7 @@ public class MemberPrinter { * @param child The child to append * @return this */ - public MemberPrinter addOptionalPrettyPrint(ASTNode child) { + public MemberPrinter addOptionalPrettyPrint(ASTNode<?> child) { if (child != null) { this.empty = false; sb.append(child.prettyPrint()); 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 fd6024bdfd45e2652eb4eaa118eada3dbfc75f23..3654470a6285e3424d750043b0df66cec295361b 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 @@ -2,6 +2,8 @@ package de.tudresden.inf.st.eraser.util; import de.tudresden.inf.st.eraser.jastadd.model.*; +import java.util.ArrayList; + /** * Helper class to create models used in tests. * @@ -35,6 +37,13 @@ public class TestUtils { return ModelAndItem.of(root.getSmartHomeEntityModel(), item); } + public static SmartHomeEntityModel createModelWithGroup() { + Root root = Root.createEmptyRoot(); + ParserUtils.createUnknownGroup(root.getSmartHomeEntityModel(),new ArrayList<>()); + + return root.getSmartHomeEntityModel(); + } + public static NumberItem addItemTo(SmartHomeEntityModel model, double initialValue) { return addItemTo(model, initialValue, false); } 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 1a1ea62a2d4611fa00da7252510a325fe40cf41f..633627c6bf85e4ddd363865b01bb62155919ae1e 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 @@ -2,12 +2,17 @@ package de.tudresden.inf.st.eraser; import beaver.Parser; import de.tudresden.inf.st.eraser.jastadd.model.*; +import de.tudresden.inf.st.eraser.jastadd.model.Action; import de.tudresden.inf.st.eraser.util.ParserUtils; import de.tudresden.inf.st.eraser.util.TestUtils; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Disabled; +import org.testcontainers.shaded.com.google.common.collect.ImmutableList; import java.io.IOException; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ScheduledFuture; @@ -15,9 +20,9 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.StreamSupport; +import static de.tudresden.inf.st.eraser.util.TestUtils.getDefaultGroup; import static org.assertj.core.api.Assertions.fail; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; /** * Testing the simple rule engine. @@ -205,7 +210,7 @@ public class RulesTest { setState(item, 5); assertEquals(1, counter.get(item), "Change of item state should trigger the rule"); - rule.removeActivationOf(item); + rule.deactivateFor(item); setState(item, 3); assertEquals(1, counter.get(item), "Change of item state should not change the counter anymore"); @@ -245,6 +250,254 @@ public class RulesTest { assertEquals(2, counter.get(item), "Change of item to 7 should not trigger the rule, check2 violated"); } + @Test + public void testStateSyncGroupRewriteStructure() { + // init StateSyncGroup + StateSyncGroup group = new StateSyncGroup(); + + ArrayList<Item> items = new ArrayList<>(); + + // create model and an item + TestUtils.ModelAndItem mai = TestUtils.createModelAndItem(0, true); + NumberItem item0 = mai.item; + group.addTargetItem(item0); + items.add(item0); + + // init more items + for (int i=1;i<=4;i++) { + NumberItem item = TestUtils.addItemTo(mai.model, i, true); + item.setID("item"+i); + group.addTargetItem(item); + items.add(item); + } + + // add StateSyncGroup as rule to root + mai.model.getRoot().addRule(group); + + + + // trace rewritten rule and its actions + Rule rewrittenRule = mai.model.getRoot().getRules().getChild(0); + + + ImmutableList<Action> actions = ImmutableList.copyOf(rewrittenRule.getActionList().iterator()); + ImmutableList<ItemObserver> observers = ImmutableList.copyOf(rewrittenRule.getObservers().iterator()); + + // check general structure + assertEquals(ImmutableList.copyOf(rewrittenRule.getConditionList().iterator()).size(), 0); + assertEquals(actions.size(), 5); + + // check actions and observers + for (int i=0;i<items.size();i++) { + assertTrue(actions.get(i) instanceof SetStateFromTriggeringItemAction); + assertTrue(observers.contains(items.get(i).getItemObserver())); + } + + + + } + + private static void addItemToModel(SmartHomeEntityModel model, Item item) { + getDefaultGroup(model).addItem(item); + } + + @Test + public void testColorItemStateSyncGroup() { + StateSyncGroup group = new StateSyncGroup(); + + //init model and 3 items + SmartHomeEntityModel model = TestUtils.createModelWithGroup(); + + ColorItem colorItem1 = new ColorItem(); + addItemToModel(model,colorItem1); + group.addTargetItem(colorItem1); + + ColorItem colorItem2 = new ColorItem(); + addItemToModel(model,colorItem2); + group.addTargetItem(colorItem2); + + ColorItem colorItem3 = new ColorItem(); + addItemToModel(model,colorItem3); + group.addTargetItem(colorItem3); + + + // add StateSyncGroup as rule to root and trigger rewrite + model.getRoot().addRule(group); + model.getRoot().doFullTraversal(); + + + colorItem1.setState(TupleHSB.parse("0,0,100")); + assertEquals(colorItem1.getState(),TupleHSB.parse("0,0,100")); + assertEquals(colorItem2.getState(),TupleHSB.parse("0,0,100")); + assertEquals(colorItem3.getState(),TupleHSB.parse("0,0,100")); + + colorItem3.setState(TupleHSB.parse("0,0,7")); + assertEquals(colorItem1.getState(),TupleHSB.parse("0,0,7")); + assertEquals(colorItem2.getState(),TupleHSB.parse("0,0,7")); + assertEquals(colorItem3.getState(),TupleHSB.parse("0,0,7")); + + + } + + @Test + public void testDateTimeItemStateSyncGroup() { + StateSyncGroup group = new StateSyncGroup(); + + //init model and 3 items + SmartHomeEntityModel model = TestUtils.createModelWithGroup(); + + DateTimeItem dateTimeItem1 = new DateTimeItem(); + addItemToModel(model,dateTimeItem1); + group.addTargetItem(dateTimeItem1); + + DateTimeItem dateTimeItem2 = new DateTimeItem(); + addItemToModel(model,dateTimeItem2); + group.addTargetItem(dateTimeItem2); + + DateTimeItem dateTimeItem3 = new DateTimeItem(); + addItemToModel(model,dateTimeItem3); + group.addTargetItem(dateTimeItem3); + + + // add StateSyncGroup as rule to root and trigger rewrite + model.getRoot().addRule(group); + model.getRoot().doFullTraversal(); + + Instant i1 = Instant.now(); + dateTimeItem1.setState(i1); + assertEquals(dateTimeItem1.getState(),i1); + assertEquals(dateTimeItem2.getState(),i1); + assertEquals(dateTimeItem3.getState(),i1); + + Instant i2 = Instant.now(); + dateTimeItem3.setState(i2); + assertEquals(dateTimeItem1.getState(),i2); + assertEquals(dateTimeItem2.getState(),i2); + assertEquals(dateTimeItem3.getState(),i2); + } + + /** + * Also for DimmerItem, RollerShutterItem, ActivityItem + */ + @Test + public void testDoubleStateItemStateSyncGroup() { + StateSyncGroup group = new StateSyncGroup(); + + //init model and 3 items + SmartHomeEntityModel model = TestUtils.createModelWithGroup(); + + NumberItem numberItem1 = new NumberItem(); + addItemToModel(model,numberItem1); + group.addTargetItem(numberItem1); + + NumberItem numberItem2 = new NumberItem(); + addItemToModel(model,numberItem2); + group.addTargetItem(numberItem2); + + NumberItem numberItem3 = new NumberItem(); + addItemToModel(model,numberItem3); + group.addTargetItem(numberItem3); + + + // add StateSyncGroup as rule to root and trigger rewrite + model.getRoot().addRule(group); + model.getRoot().doFullTraversal(); + + + numberItem1.setState(123); + assertEquals(numberItem1.getState(),123); + assertEquals(numberItem2.getState(),123); + assertEquals(numberItem3.getState(),123); + + numberItem2.setState(42); + assertEquals(numberItem1.getState(),42); + assertEquals(numberItem2.getState(),42); + assertEquals(numberItem3.getState(),42); + } + + /** + * Also for ImageItem, LocationItem, PlayerItem, DefaultItem + */ + @Test + public void testStringStateItemStateSyncGroup() { + StateSyncGroup group = new StateSyncGroup(); + + //init model and 3 items + SmartHomeEntityModel model = TestUtils.createModelWithGroup(); + + StringItem stringItem1 = new StringItem(); + addItemToModel(model,stringItem1); + group.addTargetItem(stringItem1); + + StringItem stringItem2 = new StringItem(); + addItemToModel(model,stringItem2); + group.addTargetItem(stringItem2); + + StringItem stringItem3 = new StringItem(); + addItemToModel(model,stringItem3); + group.addTargetItem(stringItem3); + + + // add StateSyncGroup as rule to root and trigger rewrite + model.getRoot().addRule(group); + model.getRoot().doFullTraversal(); + + + stringItem1.setState("123"); + assertEquals(stringItem1.getState(),"123"); + assertEquals(stringItem2.getState(),"123"); + assertEquals(stringItem3.getState(),"123"); + + stringItem2.setState("Hermes"); + assertEquals(stringItem1.getState(),"Hermes"); + assertEquals(stringItem2.getState(),"Hermes"); + assertEquals(stringItem3.getState(),"Hermes"); + + + } + + + /** + * Also for ContactItem + */ + @Test + public void testBooleanStateItemStateSyncGroup() { + StateSyncGroup group = new StateSyncGroup(); + + //init model and 3 items + SmartHomeEntityModel model = TestUtils.createModelWithGroup(); + + SwitchItem switchItem1 = new SwitchItem(); + addItemToModel(model,switchItem1); + group.addTargetItem(switchItem1); + + SwitchItem switchItem2 = new SwitchItem(); + addItemToModel(model,switchItem2); + group.addTargetItem(switchItem2); + + SwitchItem switchItem3 = new SwitchItem(); + addItemToModel(model,switchItem3); + group.addTargetItem(switchItem3); + + + // add StateSyncGroup as rule to root and trigger rewrite + model.getRoot().addRule(group); + model.getRoot().doFullTraversal(); + + + switchItem3.setState(false); + assertFalse(switchItem1.getState()); + assertFalse(switchItem2.getState()); + assertFalse(switchItem3.getState()); + + switchItem1.setState(true); + assertTrue(switchItem1.getState()); + assertTrue(switchItem2.getState()); + assertTrue(switchItem3.getState()); + + } + + @Test public void testTwoActions() { TestUtils.ModelAndItem modelAndItem = createModelAndItem(2); @@ -673,7 +926,7 @@ public class RulesTest { private StringItem addStringItem(SmartHomeEntityModel model, String initialValue) { StringItem item = new StringItem(); - Group group = TestUtils.getDefaultGroup(model); + Group group = getDefaultGroup(model); item.setID("item" + group.getNumItem()); item.setState(initialValue, false); group.addItem(item);