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 e834eb516e514289652ae0f35820303597a821ae..e7b6bfb808eb4f1c40ce86442f85340ae8f1ebf7 100644 --- a/eraser-base/src/main/jastadd/Item.jrag +++ b/eraser-base/src/main/jastadd/Item.jrag @@ -283,6 +283,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: @@ -302,7 +304,7 @@ aspect ItemHandling { logger.catching(e); } } - if (hasItemObserver() && this.getStateData().checkStateProcessingTime(getRelevantItemPerformance())) { + if (this.getStateData().checkStateProcessingTime(getRelevantItemPerformance())) { this.getStateData().afterStateChangeProcessed(); getItemObserver().apply(); } @@ -411,6 +413,7 @@ aspect ItemHandling { SetStateFromTriggeringItemAction action = new SetStateFromTriggeringItemAction(); action.setAffectedItem(controlledItem); rule.addAction(action); + rule.activateFor(controllerItem); return rule; @@ -473,6 +476,7 @@ aspect ItemHandling { //--- copyStateTo --- protected abstract void Item.copyStateTo(Item stateReceiver); + protected void ItemWithBooleanState.copyStateTo(Item stateReceiver) { stateReceiver.setStateFromBoolean(this.getState()); } @@ -489,6 +493,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 ec95c8564fdc2f29d1d664bb42d722d80e78f994..1e73d8b151a03e792af75458369000f40cdca1eb 100644 --- a/eraser-base/src/main/jastadd/Navigation.jrag +++ b/eraser-base/src/main/jastadd/Navigation.jrag @@ -13,10 +13,14 @@ aspect Navigation { protected ItemPerformance GroupItemComposite.getRelevantItemPerformance() { ASTNode parent = getParent(); - Group g = this.getEnclosingGroup(); - //Group g1 = g.getEnclosingGroup(); - if (this.getItemPerformance()==null && g!=null) { - return g.getRelevantItemPerformance(); + //TODO: Remove try catch + try { + Group g = this.getEnclosingGroup(); + if (this.getItemPerformance()==null && g!=null) { + return g.getRelevantItemPerformance(); + } + } catch(Exception e) { + } return this.getItemPerformance(); } @@ -41,104 +45,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 1ca7d69d3ebfb1f850d00e95ae484cc0c4aa6156..8746ae62081ec92c6d203253032acc831cad06d8 100644 --- a/eraser-base/src/main/jastadd/Rules.relast +++ b/eraser-base/src/main/jastadd/Rules.relast @@ -30,3 +30,5 @@ ItemObserver ::= ; rel ItemObserver.TriggeredRule* <-> Rule.Observer* ; ItemPerformance : LabelledModelElement ::= <EventProcessingFrequency:double> ; +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 aa5d6f5c6c63141d0d6d808e0de8d05c48c71fd9..9429a1d71e0fe280348664115bbcfa2103cb61b8 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 6c37562bac17aac3c9b3d320164d6569322f07dc..63ad20d42f9edc3424b472bccf4e072d78b6e0a1 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); :} @@ -139,6 +142,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; :} ; @@ -157,6 +164,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; :} ; @@ -287,6 +309,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 978a9088f32f0b448e6c648c91ae6db71f22c58b..ee3f42d0cf3f61691ee16f1936f6e8547911ed41 100644 --- a/eraser-base/src/main/jastadd/shem.relast +++ b/eraser-base/src/main/jastadd/shem.relast @@ -28,7 +28,7 @@ ParameterDefaultValue ::= <Value:String> ; abstract GroupItemComposite : LabelledModelElement ; rel GroupItemComposite.ItemPerformance -> ItemPerformance ; -abstract Item : GroupItemComposite ::= <_fetched_data:boolean> MetaData:ItemMetaData* [ItemObserver] /StateData/; +abstract Item : GroupItemComposite ::= <_fetched_data:boolean> MetaData:ItemMetaData* /ItemObserver/ /StateData/; rel Item.Category? -> ItemCategory ; 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 ffb5ee052b545e6a5c145eb27d186440cb57a499..3f6b1df53f0709793100d72b619896ead298a3d4 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 @@ -76,6 +76,7 @@ public class EraserParserHelper { // when parsing expressions this.root = EraserParserHelper.initialRoot != null ? EraserParserHelper.initialRoot : createRoot(); } + if (checkUnusedElements) { fillUnused(); } @@ -108,6 +109,8 @@ public class EraserParserHelper { if (checkUnusedElements) { checkUnusedElements(); } + + this.root.treeResolveAll(); } private void addItemToGroup(Group group, Item item) { @@ -315,11 +318,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/test/java/de/tudresden/inf/st/eraser/RulesTest.java b/eraser-base/src/test/java/de/tudresden/inf/st/eraser/RulesTest.java index 16815c5f6bceef351dfa2dc14b8d66dfe0b4a6c2..023400348da8cdf3599ea4e0c749caf0fca4c62e 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; @@ -17,8 +22,7 @@ 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. @@ -206,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"); @@ -247,6 +251,254 @@ public class RulesTest { } + @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);