From 2e0b5b388343fe850e75d3f0bdb8436548e42b47 Mon Sep 17 00:00:00 2001 From: rschoene <rene.schoene@tu-dresden.de> Date: Thu, 20 Jan 2022 17:28:07 +0100 Subject: [PATCH 1/6] WIP: begin with attribute as endpoint target --- .../src/main/jastadd/Analysis.jrag | 1 + .../src/main/jastadd/Intermediate.jadd | 8 ++-- .../src/main/jastadd/Mappings.jrag | 3 ++ .../src/main/jastadd/Navigation.jrag | 1 + .../src/main/jastadd/RagConnect.relast | 5 +- .../main/jastadd/parser/ParserRewrites.jrag | 13 +++++ .../src/main/jastadd/parser/RagConnect.parser | 6 ++- .../src/main/jastadd/scanner/Keywords.flex | 2 + ragconnect.tests/build.gradle | 26 ++++++++++ .../src/test/01-input/attribute/README.md | 3 ++ .../src/test/01-input/attribute/Test.connect | 31 ++++++++++++ .../src/test/01-input/attribute/Test.jadd | 39 +++++++++++++++ .../src/test/01-input/attribute/Test.relast | 14 ++++++ .../ragconnect/tests/AttributeTest.java | 48 +++++++++++++++++++ 14 files changed, 193 insertions(+), 7 deletions(-) create mode 100644 ragconnect.tests/src/test/01-input/attribute/README.md create mode 100644 ragconnect.tests/src/test/01-input/attribute/Test.connect create mode 100644 ragconnect.tests/src/test/01-input/attribute/Test.jadd create mode 100644 ragconnect.tests/src/test/01-input/attribute/Test.relast create mode 100644 ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AttributeTest.java diff --git a/ragconnect.base/src/main/jastadd/Analysis.jrag b/ragconnect.base/src/main/jastadd/Analysis.jrag index 85ff38d..b48146f 100644 --- a/ragconnect.base/src/main/jastadd/Analysis.jrag +++ b/ragconnect.base/src/main/jastadd/Analysis.jrag @@ -64,6 +64,7 @@ aspect Analysis { } syn boolean EndpointTarget.entityIsNormalAttribute(); + // TODO AttributeEndpointTarget.entityIsNormalAttribute eq TokenEndpointTarget.entityIsNormalAttribute() = !getToken().getNTA(); eq TypeEndpointTarget.entityIsNormalAttribute() = !getType().getNTA(); eq ContextFreeTypeEndpointTarget.entityIsNormalAttribute() = false; diff --git a/ragconnect.base/src/main/jastadd/Intermediate.jadd b/ragconnect.base/src/main/jastadd/Intermediate.jadd index bcd4cc4..b215f18 100644 --- a/ragconnect.base/src/main/jastadd/Intermediate.jadd +++ b/ragconnect.base/src/main/jastadd/Intermediate.jadd @@ -371,6 +371,10 @@ aspect MustacheReceiveAndSendAndHandleUri { syn String EndpointTarget.parentTypeName(); syn String EndpointTarget.entityName(); + // TODO AttributeEndpointTarget.getterName + eq AttributeEndpointTarget.parentTypeName() = getParentTypeDecl().getName(); + eq AttributeEndpointTarget.entityName() = getName(); + eq TokenEndpointTarget.getterMethodName() = "get" + getToken().getName(); eq TokenEndpointTarget.parentTypeName() = getToken().containingTypeDecl().getName(); eq TokenEndpointTarget.entityName() = getToken().getName(); @@ -456,9 +460,7 @@ aspect MustacheSendDefinition { getTypeDecl().getName() : ragconnect().configJastAddList() + "<" + getTypeDecl().getName() + ">"; - syn String EndpointTarget.senderName(); - eq TokenEndpointTarget.senderName() = ragconnect().internalRagConnectPrefix() + "_sender_" + getToken().getName(); - eq TypeEndpointTarget.senderName() = ragconnect().internalRagConnectPrefix() + "_sender_" + getType().getName(); + syn String EndpointTarget.senderName() = ragconnect().internalRagConnectPrefix() + "_sender_" + entityName(); eq ContextFreeTypeEndpointTarget.senderName() = null; syn String MEndpointDefinition.updateMethodName(); diff --git a/ragconnect.base/src/main/jastadd/Mappings.jrag b/ragconnect.base/src/main/jastadd/Mappings.jrag index 738028b..fb7c296 100644 --- a/ragconnect.base/src/main/jastadd/Mappings.jrag +++ b/ragconnect.base/src/main/jastadd/Mappings.jrag @@ -191,6 +191,7 @@ aspect Mappings { // --- suitableReceiveDefaultMapping --- syn DefaultMappingDefinition EndpointDefinition.suitableReceiveDefaultMapping() { + // TODO might be expanded to AttributeEndpointTarget if (getEndpointTarget().isTypeEndpointTarget()) { try { TypeDecl typeDecl = program().resolveTypeDecl(targetTypeName()); @@ -236,6 +237,7 @@ aspect Mappings { // --- suitableSendDefaultMapping --- syn DefaultMappingDefinition EndpointDefinition.suitableSendDefaultMapping() { + // TODO might be expanded to AttributeEndpointTarget if (getEndpointTarget().isTypeEndpointTarget() && typeIsList() && !getIndexBasedListAccess()) { return ragconnect().defaultListTreeToBytesMapping(); } @@ -288,6 +290,7 @@ aspect Mappings { } } syn String EndpointTarget.targetTypeName(); + eq AttributeEndpointTarget.targetTypeName() = getTypeName(); eq TokenEndpointTarget.targetTypeName() = getToken().effectiveJavaTypeUse().getName(); eq TypeEndpointTarget.targetTypeName() = getType().getTypeDecl().getName(); eq ContextFreeTypeEndpointTarget.targetTypeName() = getTypeDecl().getName(); diff --git a/ragconnect.base/src/main/jastadd/Navigation.jrag b/ragconnect.base/src/main/jastadd/Navigation.jrag index a65ab72..260ed55 100644 --- a/ragconnect.base/src/main/jastadd/Navigation.jrag +++ b/ragconnect.base/src/main/jastadd/Navigation.jrag @@ -1,5 +1,6 @@ aspect NewStuff { + // TODO regenerate for AttributeEndpointTarget /** Tests if EndpointTarget is a TokenEndpointTarget. * @return 'true' if this is a TokenEndpointTarget, otherwise 'false' */ diff --git a/ragconnect.base/src/main/jastadd/RagConnect.relast b/ragconnect.base/src/main/jastadd/RagConnect.relast index 01a0f2f..f6cf0dd 100644 --- a/ragconnect.base/src/main/jastadd/RagConnect.relast +++ b/ragconnect.base/src/main/jastadd/RagConnect.relast @@ -13,9 +13,10 @@ TypeEndpointTarget : EndpointTarget; rel TypeEndpointTarget.Type <-> TypeComponent.TypeEndpointTarget*; ContextFreeTypeEndpointTarget : EndpointTarget; rel ContextFreeTypeEndpointTarget.TypeDecl <-> TypeDecl.ContextFreeTypeEndpointTarget*; -UntypedEndpointTarget : EndpointTarget ::= <TypeName> <ChildName>; // only used by parser +UntypedEndpointTarget : EndpointTarget ::= <TypeName> <ChildName> <IsAttribute:boolean>; // only used by parser // to be integrated: -//AttributeEndpointTarget : EndpointTarget ::= <Name> ; +AttributeEndpointTarget : EndpointTarget ::= <Name> <TypeName> ; +rel AttributeEndpointTarget.ParentTypeDecl <-> TypeDecl.AttributeEndpointTarget*; //RelationEndpointTarget : EndpointTarget ; //rel RelationEndpointTarget.Role <-> Role.RelationEndpointTarget* ; diff --git a/ragconnect.base/src/main/jastadd/parser/ParserRewrites.jrag b/ragconnect.base/src/main/jastadd/parser/ParserRewrites.jrag index f4a8912..3de9341 100644 --- a/ragconnect.base/src/main/jastadd/parser/ParserRewrites.jrag +++ b/ragconnect.base/src/main/jastadd/parser/ParserRewrites.jrag @@ -23,6 +23,19 @@ aspect ParserRewrites { result.setTypeDecl(TypeDecl.createRef(getTypeName())); return result; } + + when (getIsAttribute()) + to AttributeEndpointTarget { + AttributeEndpointTarget result = new AttributeEndpointTarget(); + String[] tokens = this.getChildName().split(":"); + String attributeName = tokens[0]; + String attributeTypeName = tokens[1]; + result.copyOtherValuesFrom(this); + result.setName(attributeName); + result.setTypeName(attributeTypeName); + result.setParentTypeDecl(TypeDecl.createRef(getTypeName())); + return result; + } } syn String UntypedEndpointTarget.combinedName() = getTypeName() + "." + getChildName(); diff --git a/ragconnect.base/src/main/jastadd/parser/RagConnect.parser b/ragconnect.base/src/main/jastadd/parser/RagConnect.parser index 54a388e..dcd55d2 100644 --- a/ragconnect.base/src/main/jastadd/parser/RagConnect.parser +++ b/ragconnect.base/src/main/jastadd/parser/RagConnect.parser @@ -62,8 +62,10 @@ EndpointDefinition endpoint_definition_type ; EndpointTarget endpoint_target - = ID.type_name DOT ID.child_name {: return new UntypedEndpointTarget(type_name, child_name); :} - | ID.type_name {: return new UntypedEndpointTarget(type_name, ""); :} + = ID.type_name DOT ID.child_name {: return new UntypedEndpointTarget(type_name, child_name, false); :} + | ID.type_name DOT ID.child_name BRACKETS COLON ID.attribute_type_name + {: return new UntypedEndpointTarget(type_name, child_name + ":" + attribute_type_name, true); :} + | ID.type_name {: return new UntypedEndpointTarget(type_name, "", false); :} ; ArrayList string_list diff --git a/ragconnect.base/src/main/jastadd/scanner/Keywords.flex b/ragconnect.base/src/main/jastadd/scanner/Keywords.flex index 8a1eaec..ffa057b 100644 --- a/ragconnect.base/src/main/jastadd/scanner/Keywords.flex +++ b/ragconnect.base/src/main/jastadd/scanner/Keywords.flex @@ -8,3 +8,5 @@ "with" { return sym(Terminals.WITH); } "indexed" { return sym(Terminals.INDEXED); } "add" { return sym(Terminals.ADD); } +"()" { return sym(Terminals.BRACKETS); } +":" { return sym(Terminals.COLON); } diff --git a/ragconnect.tests/build.gradle b/ragconnect.tests/build.gradle index 8d073b5..b545b19 100644 --- a/ragconnect.tests/build.gradle +++ b/ragconnect.tests/build.gradle @@ -621,3 +621,29 @@ task compileIndexedSendIncremental(type: RagConnectTest, dependsOn: ':ragconnect extraOptions = JASTADD_INCREMENTAL_OPTIONS_TRACING_FULL } } + +// --- Test: attribute-incremental --- +task compileAttributeIncremental(type: RagConnectTest, dependsOn: ':ragconnect.base:jar') { + ragconnect { + outputDir = file('src/test/02-after-ragconnect/attributeInc') + inputFiles = [file('src/test/01-input/attribute/Test.relast'), + file('src/test/01-input/attribute/Test.connect')] + rootNode = 'Root' + logWrites = true + logReads = true + logIncremental = true + extraOptions = ['--experimental-jastadd-329'] + } + relast { + useJastAddNames = true + grammarName = 'src/test/03-after-relast/attributeInc/attributeInc' + serializer = 'jackson' + } + jastadd { + jastAddList = 'JastAddList' + packageName = 'attributeInc.ast' + inputFiles = [file('src/test/01-input/attribute/Test.jadd')] + extraOptions = JASTADD_INCREMENTAL_OPTIONS_TRACING_FULL + } +} +compileAttributeIncremental.outputs.upToDateWhen { false } diff --git a/ragconnect.tests/src/test/01-input/attribute/README.md b/ragconnect.tests/src/test/01-input/attribute/README.md new file mode 100644 index 0000000..08481b7 --- /dev/null +++ b/ragconnect.tests/src/test/01-input/attribute/README.md @@ -0,0 +1,3 @@ +# Attribute + +Idea: Use send definitions for attributes. diff --git a/ragconnect.tests/src/test/01-input/attribute/Test.connect b/ragconnect.tests/src/test/01-input/attribute/Test.connect new file mode 100644 index 0000000..3e6a66e --- /dev/null +++ b/ragconnect.tests/src/test/01-input/attribute/Test.connect @@ -0,0 +1,31 @@ +send SenderRoot.basic():String ; +send SenderRoot.simple():String ; +send SenderRoot.transformed():int ; +send SenderRoot.toReferenceType():A ; +send SenderRoot.toNTA():A ; + +AddSuffix maps A a to A {: + A result = new A(); + String changedValue = a.getValue() + "post"; + result.setValue(changedValue); + result.setInner(new Inner("inner" + changedValue)); + return result; +:} + +AddStringSuffix maps String s to String {: + return s + "post"; +:} + +AddPlusOne maps int i to int {: + return i + 1; +:} + +receive ReceiverRoot.FromBasic; +receive ReceiverRoot.FromSimpleNoMapping; +receive ReceiverRoot.FromSimpleWithMapping using AddStringSuffix; +receive ReceiverRoot.FromTransformedNoMapping; +receive ReceiverRoot.FromTransformedWithMapping using AddPlusOne; +receive ReceiverRoot.FromReferenceTypeNoMapping; +receive ReceiverRoot.FromReferenceTypeWithMapping using AddSuffix; +receive ReceiverRoot.FromNTANoMapping; +receive ReceiverRoot.FromNTAWithMapping using AddSuffix; diff --git a/ragconnect.tests/src/test/01-input/attribute/Test.jadd b/ragconnect.tests/src/test/01-input/attribute/Test.jadd new file mode 100644 index 0000000..c5274c6 --- /dev/null +++ b/ragconnect.tests/src/test/01-input/attribute/Test.jadd @@ -0,0 +1,39 @@ +aspect Computation { + syn String SenderRoot.basic() = getInput(); + syn String SenderRoot.simple() = getInput() + "Post"; + syn int SenderRoot.transformed() = getInput().length(); + syn A SenderRoot.toReferenceType() { + A result = new A(); + result.setValue(getInput()); + Inner inner = new Inner(); + inner.setInnerValue("1"); + result.setInner(inner); + return result; + } + syn nta A SenderRoot.toNTA() { + A result = new A(); + result.setValue(getInput()); + Inner inner = new Inner(); + inner.setInnerValue("2"); + result.setInner(inner); + return result; + } +} +aspect MakeCodeCompile { + +} +aspect MakeCodeWork { + +} +aspect NameResolution { + // overriding customID guarantees to produce the same JSON representation for equal lists + // otherwise, the value for id is different each time + @Override + protected String A.customID() { + return getClass().getSimpleName() + getValue(); + } + @Override + protected String Inner.customID() { + return getClass().getSimpleName() + getInnerValue(); + } +} diff --git a/ragconnect.tests/src/test/01-input/attribute/Test.relast b/ragconnect.tests/src/test/01-input/attribute/Test.relast new file mode 100644 index 0000000..d7edb5c --- /dev/null +++ b/ragconnect.tests/src/test/01-input/attribute/Test.relast @@ -0,0 +1,14 @@ +Root ::= SenderRoot ReceiverRoot; +SenderRoot ::= <Input> ; +ReceiverRoot ::= + <FromBasic> + <FromSimpleNoMapping> + <FromSimpleWithMapping> + <FromTransformedNoMapping:int> + <FromTransformedWithMapping:int> + FromReferenceTypeNoMapping:A + FromReferenceTypeWithMapping:A + FromNTANoMapping:A + FromNTAWithMapping:A ; +A ::= <Value> Inner ; +Inner ::= <InnerValue> ; diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AttributeTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AttributeTest.java new file mode 100644 index 0000000..6b87744 --- /dev/null +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AttributeTest.java @@ -0,0 +1,48 @@ +package org.jastadd.ragconnect.tests; + +import attribute.ast.*; +import org.junit.jupiter.api.Tag; + +import java.io.IOException; + +/** + * Test case "attribute". + * + * @author rschoene - Initial contribution + */ +@Tag("Incremental") +@Tag("New") +public class AttributeTest extends AbstractMqttTest { + Root model; + MqttHandler handler; + + @Override + protected void createModel() { + + } + + @Override + protected void setupReceiverAndConnect() throws IOException, InterruptedException { + + } + + @Override + protected void communicateSendInitialValue() throws IOException, InterruptedException { + + } + + @Override + protected void communicateOnlyUpdatedValue() throws IOException, InterruptedException { + + } + + @Override + protected void closeConnections() { + if (handler != null) { + handler.close(); + } + if (model != null) { + model.ragconnectCloseConnections(); + } + } +} -- GitLab From 2a281c3ea3e3734f6c079f43d5048c58315824f2 Mon Sep 17 00:00:00 2001 From: rschoene <rene.schoene@tu-dresden.de> Date: Thu, 3 Feb 2022 10:36:37 +0100 Subject: [PATCH 2/6] working on attributes as endpoint target - change syntax in connect file, avoid colon - begin with AttributeTest - discovered error when endpoint is connected twice --- .../src/main/jastadd/Analysis.jrag | 16 +- .../src/main/jastadd/Intermediate.jadd | 22 ++- .../src/main/jastadd/Intermediate.relast | 1 + .../src/main/jastadd/Navigation.jrag | 13 ++ .../src/main/jastadd/parser/RagConnect.parser | 2 +- .../src/main/jastadd/scanner/Keywords.flex | 4 +- .../src/main/resources/handler.mustache | 8 + .../main/resources/sendDefinition.mustache | 8 +- ragconnect.tests/build.gradle | 1 + .../src/test/01-input/attribute/Test.connect | 12 +- .../src/test/01-input/attribute/Test.relast | 2 +- .../ragconnect/tests/AttributeTest.java | 148 +++++++++++++++++- 12 files changed, 215 insertions(+), 22 deletions(-) diff --git a/ragconnect.base/src/main/jastadd/Analysis.jrag b/ragconnect.base/src/main/jastadd/Analysis.jrag index b48146f..b5fb2ce 100644 --- a/ragconnect.base/src/main/jastadd/Analysis.jrag +++ b/ragconnect.base/src/main/jastadd/Analysis.jrag @@ -2,6 +2,19 @@ aspect Analysis { // --- isAlreadyDefined --- syn boolean EndpointDefinition.isAlreadyDefined() = getEndpointTarget().isAlreadyDefined(); syn boolean EndpointTarget.isAlreadyDefined(); + eq AttributeEndpointTarget.isAlreadyDefined() { + // define lookup here, as not used elsewhere + int numberOfSameDefs = 0; + for (EndpointTarget target : ragconnect().givenEndpointTargetList()) { + if (target.isAttributeEndpointTarget()) { + AttributeEndpointTarget other = target.asAttributeEndpointTarget(); + if (other.getParentTypeDecl().equals(this.getParentTypeDecl()) && other.getName().equals(this.getName())) { + numberOfSameDefs += 1; + } + } + } + return numberOfSameDefs > 1; + } eq TokenEndpointTarget.isAlreadyDefined() { return lookupTokenEndpointDefinitions(getToken()).stream() .filter(containingEndpointDefinition()::matchesType) @@ -63,8 +76,9 @@ aspect Analysis { } } + // TODO rename entityIsNormalAttribute to actual meaning syn boolean EndpointTarget.entityIsNormalAttribute(); - // TODO AttributeEndpointTarget.entityIsNormalAttribute + eq AttributeEndpointTarget.entityIsNormalAttribute() = true; eq TokenEndpointTarget.entityIsNormalAttribute() = !getToken().getNTA(); eq TypeEndpointTarget.entityIsNormalAttribute() = !getType().getNTA(); eq ContextFreeTypeEndpointTarget.entityIsNormalAttribute() = false; diff --git a/ragconnect.base/src/main/jastadd/Intermediate.jadd b/ragconnect.base/src/main/jastadd/Intermediate.jadd index b215f18..e26b797 100644 --- a/ragconnect.base/src/main/jastadd/Intermediate.jadd +++ b/ragconnect.base/src/main/jastadd/Intermediate.jadd @@ -168,6 +168,11 @@ aspect MustacheMappingApplicationAndDefinition { syn String MEndpointDefinition.preemptiveReturn(); syn String MEndpointDefinition.firstInputVarName(); + // TODO check MAttributeSendDefinition + eq MAttributeSendDefinition.firstInputVarName() = getterMethodCall(); + eq MAttributeSendDefinition.preemptiveExpectedValue() = lastValueGetterCall(); + eq MAttributeSendDefinition.preemptiveReturn() = "return false;"; + eq MTokenReceiveDefinition.firstInputVarName() = "message"; eq MTokenReceiveDefinition.preemptiveExpectedValue() = getterMethodCall(); eq MTokenReceiveDefinition.preemptiveReturn() = "return;"; @@ -323,7 +328,7 @@ aspect MustacheRagConnect { aspect MustacheReceiveAndSendAndHandleUri { // === EndpointDefinition === - syn String EndpointDefinition.connectMethodName() = "connect" + entityName(); + syn String EndpointDefinition.connectMethodName() = "connect" + capitalize(entityName()); syn String EndpointDefinition.connectParameterName() = "uriString"; @@ -348,7 +353,7 @@ aspect MustacheReceiveAndSendAndHandleUri { } else { extra = ""; } - return "disconnect" + extra + entityName(); + return "disconnect" + extra + capitalize(entityName()); } syn String EndpointDefinition.entityName() = getEndpointTarget().entityName(); @@ -371,7 +376,7 @@ aspect MustacheReceiveAndSendAndHandleUri { syn String EndpointTarget.parentTypeName(); syn String EndpointTarget.entityName(); - // TODO AttributeEndpointTarget.getterName + eq AttributeEndpointTarget.getterMethodName() = getName(); eq AttributeEndpointTarget.parentTypeName() = getParentTypeDecl().getName(); eq AttributeEndpointTarget.entityName() = getName(); @@ -463,9 +468,14 @@ aspect MustacheSendDefinition { syn String EndpointTarget.senderName() = ragconnect().internalRagConnectPrefix() + "_sender_" + entityName(); eq ContextFreeTypeEndpointTarget.senderName() = null; + // TOOO both updateMethodName and writeMethodName could be defined for MEndpointDefinition using getEndpointDefinition().enitityName() syn String MEndpointDefinition.updateMethodName(); syn String MEndpointDefinition.writeMethodName(); + // TODO check MAttributeSendDefinition. maybe name-clash possible if there is an attribute with same name as a child + eq MAttributeSendDefinition.updateMethodName() = ragconnect().internalRagConnectPrefix() + "_update_" + getEndpointDefinition().entityName(); + eq MAttributeSendDefinition.writeMethodName() = ragconnect().internalRagConnectPrefix() + "_writeLastValue_" + getEndpointDefinition().entityName(); + eq MTokenReceiveDefinition.updateMethodName() = null; eq MTokenReceiveDefinition.writeMethodName() = null; @@ -582,6 +592,12 @@ aspect AttributesForMustache { return result; } abstract MEndpointDefinition EndpointTarget.createMEndpointDefinition(boolean isSend); + MEndpointDefinition AttributeEndpointTarget.createMEndpointDefinition(boolean isSend) { + if (!isSend) { + throw new IllegalArgumentException("AttributeEndpointTarget can only be sent!"); + } + return new MAttributeSendDefinition(); + } MEndpointDefinition TokenEndpointTarget.createMEndpointDefinition(boolean isSend) { return isSend ? new MTokenSendDefinition() : new MTokenReceiveDefinition(); } diff --git a/ragconnect.base/src/main/jastadd/Intermediate.relast b/ragconnect.base/src/main/jastadd/Intermediate.relast index 7909750..486158a 100644 --- a/ragconnect.base/src/main/jastadd/Intermediate.relast +++ b/ragconnect.base/src/main/jastadd/Intermediate.relast @@ -1,6 +1,7 @@ abstract MEndpointDefinition ::= InnerMappingDefinition:MInnerMappingDefinition*; rel MEndpointDefinition.EndpointDefinition -> EndpointDefinition; +MAttributeSendDefinition : MEndpointDefinition; abstract MTokenEndpointDefinition : MEndpointDefinition; MTokenReceiveDefinition : MTokenEndpointDefinition; MTokenSendDefinition : MTokenEndpointDefinition; diff --git a/ragconnect.base/src/main/jastadd/Navigation.jrag b/ragconnect.base/src/main/jastadd/Navigation.jrag index 260ed55..cb89e31 100644 --- a/ragconnect.base/src/main/jastadd/Navigation.jrag +++ b/ragconnect.base/src/main/jastadd/Navigation.jrag @@ -25,6 +25,12 @@ aspect NewStuff { syn boolean EndpointTarget.isUntypedEndpointTarget() = false; eq UntypedEndpointTarget.isUntypedEndpointTarget() = true; + /** Tests if EndpointTarget is a AttributeEndpointTarget. + * @return 'true' if this is a AttributeEndpointTarget, otherwise 'false' + */ + syn boolean EndpointTarget.isAttributeEndpointTarget() = false; + eq AttributeEndpointTarget.isAttributeEndpointTarget() = true; + /** casts a EndpointTarget into a TokenEndpointTarget if possible. * @return 'this' cast to a TokenEndpointTarget or 'null' */ @@ -52,6 +58,13 @@ aspect NewStuff { syn UntypedEndpointTarget EndpointTarget.asUntypedEndpointTarget(); eq EndpointTarget.asUntypedEndpointTarget() = null; eq UntypedEndpointTarget.asUntypedEndpointTarget() = this; + + /** casts a EndpointTarget into a AttributeEndpointTarget if possible. + * @return 'this' cast to a AttributeEndpointTarget or 'null' + */ + syn AttributeEndpointTarget EndpointTarget.asAttributeEndpointTarget(); + eq EndpointTarget.asAttributeEndpointTarget() = null; + eq AttributeEndpointTarget.asAttributeEndpointTarget() = this; } aspect RagConnectNavigation { diff --git a/ragconnect.base/src/main/jastadd/parser/RagConnect.parser b/ragconnect.base/src/main/jastadd/parser/RagConnect.parser index dcd55d2..f822bf0 100644 --- a/ragconnect.base/src/main/jastadd/parser/RagConnect.parser +++ b/ragconnect.base/src/main/jastadd/parser/RagConnect.parser @@ -63,7 +63,7 @@ EndpointDefinition endpoint_definition_type EndpointTarget endpoint_target = ID.type_name DOT ID.child_name {: return new UntypedEndpointTarget(type_name, child_name, false); :} - | ID.type_name DOT ID.child_name BRACKETS COLON ID.attribute_type_name + | ID.type_name DOT ID.child_name BRACKET_LEFT ID.attribute_type_name BRACKET_RIGHT {: return new UntypedEndpointTarget(type_name, child_name + ":" + attribute_type_name, true); :} | ID.type_name {: return new UntypedEndpointTarget(type_name, "", false); :} ; diff --git a/ragconnect.base/src/main/jastadd/scanner/Keywords.flex b/ragconnect.base/src/main/jastadd/scanner/Keywords.flex index ffa057b..cda59f5 100644 --- a/ragconnect.base/src/main/jastadd/scanner/Keywords.flex +++ b/ragconnect.base/src/main/jastadd/scanner/Keywords.flex @@ -8,5 +8,5 @@ "with" { return sym(Terminals.WITH); } "indexed" { return sym(Terminals.INDEXED); } "add" { return sym(Terminals.ADD); } -"()" { return sym(Terminals.BRACKETS); } -":" { return sym(Terminals.COLON); } +"(" { return sym(Terminals.BRACKET_LEFT); } +")" { return sym(Terminals.BRACKET_RIGHT); } diff --git a/ragconnect.base/src/main/resources/handler.mustache b/ragconnect.base/src/main/resources/handler.mustache index a7ef597..0d36873 100644 --- a/ragconnect.base/src/main/resources/handler.mustache +++ b/ragconnect.base/src/main/resources/handler.mustache @@ -110,6 +110,10 @@ aspect RagConnectHandler { senders.forEach(Runnable::run); } + void run(RagConnectToken token) { + tokenToSender.get(token).run(); + } + byte[] getLastValue() { return lastValue; } @@ -150,6 +154,10 @@ aspect RagConnectHandler { java.util.Optional.ofNullable(publishers.get(index)).ifPresent(RagConnectPublisher::run); } + void run(int index, RagConnectToken token) { + java.util.Optional.ofNullable(publishers.get(index)).ifPresent(publisher -> publisher.run(token)); + } + byte[] getLastValue(int index) { RagConnectPublisher publisher = publishers.get(index); if (publisher == null) { diff --git a/ragconnect.base/src/main/resources/sendDefinition.mustache b/ragconnect.base/src/main/resources/sendDefinition.mustache index 293b37c..499116e 100644 --- a/ragconnect.base/src/main/resources/sendDefinition.mustache +++ b/ragconnect.base/src/main/resources/sendDefinition.mustache @@ -18,7 +18,7 @@ public boolean {{parentTypeName}}.{{connectMethodName}}(String {{connectParamete }{{#IndexBasedListAccess}}, index{{/IndexBasedListAccess}}, connectToken); {{updateMethodName}}({{#IndexBasedListAccess}}index{{/IndexBasedListAccess}}); if (writeCurrentValue) { - {{writeMethodName}}({{#IndexBasedListAccess}}index{{/IndexBasedListAccess}}); + {{writeMethodName}}({{#IndexBasedListAccess}}index, {{/IndexBasedListAccess}}connectToken); } success = true; break; @@ -49,7 +49,7 @@ public boolean {{parentTypeName}}.{{connectMethodName}}(String {{connectParamete {{#IndexBasedListAccess}}index,{{/IndexBasedListAccess}} () -> { if (this.{{updateMethodName}}({{#IndexBasedListAccess}}index{{/IndexBasedListAccess}})) { - this.{{writeMethodName}}({{#IndexBasedListAccess}}index{{/IndexBasedListAccess}}); + this.{{writeMethodName}}({{#IndexBasedListAccess}}index, {{/IndexBasedListAccess}}connectToken); } } ); @@ -106,8 +106,8 @@ protected boolean {{parentTypeName}}.{{updateMethodName}}({{#IndexBasedListAcces return {{senderName}} != null; } -protected void {{parentTypeName}}.{{writeMethodName}}({{#IndexBasedListAccess}}int index{{/IndexBasedListAccess}}) { - {{senderName}}.run({{#IndexBasedListAccess}}index{{/IndexBasedListAccess}}); +protected void {{parentTypeName}}.{{writeMethodName}}({{#IndexBasedListAccess}}int index, {{/IndexBasedListAccess}}RagConnectToken token) { + {{senderName}}.run({{#IndexBasedListAccess}}index, {{/IndexBasedListAccess}}token); } {{#needForwardingNTA}} diff --git a/ragconnect.tests/build.gradle b/ragconnect.tests/build.gradle index b545b19..f57f477 100644 --- a/ragconnect.tests/build.gradle +++ b/ragconnect.tests/build.gradle @@ -50,6 +50,7 @@ dependencies { testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.4.0' testImplementation group: 'org.assertj', name: 'assertj-core', version: '3.12.1' testImplementation group: 'org.awaitility', name: 'awaitility', version: '4.1.1' + testImplementation group: 'de.tudresden.inf.st', name: 'dumpAst', version: '0.3.5' // jackson (for serialization of types) implementation group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.12.1' diff --git a/ragconnect.tests/src/test/01-input/attribute/Test.connect b/ragconnect.tests/src/test/01-input/attribute/Test.connect index 3e6a66e..03688a9 100644 --- a/ragconnect.tests/src/test/01-input/attribute/Test.connect +++ b/ragconnect.tests/src/test/01-input/attribute/Test.connect @@ -1,14 +1,14 @@ -send SenderRoot.basic():String ; -send SenderRoot.simple():String ; -send SenderRoot.transformed():int ; -send SenderRoot.toReferenceType():A ; -send SenderRoot.toNTA():A ; +send SenderRoot.basic(String) ; +send SenderRoot.simple(String) ; +send SenderRoot.transformed(int) ; +send SenderRoot.toReferenceType(A) ; +send SenderRoot.toNTA(A) ; AddSuffix maps A a to A {: A result = new A(); String changedValue = a.getValue() + "post"; result.setValue(changedValue); - result.setInner(new Inner("inner" + changedValue)); + result.setInner(new Inner("inner" + a.getInner().getInnerValue())); return result; :} diff --git a/ragconnect.tests/src/test/01-input/attribute/Test.relast b/ragconnect.tests/src/test/01-input/attribute/Test.relast index d7edb5c..7afc9b7 100644 --- a/ragconnect.tests/src/test/01-input/attribute/Test.relast +++ b/ragconnect.tests/src/test/01-input/attribute/Test.relast @@ -1,4 +1,4 @@ -Root ::= SenderRoot ReceiverRoot; +Root ::= SenderRoot* ReceiverRoot; SenderRoot ::= <Input> ; ReceiverRoot ::= <FromBasic> diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AttributeTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AttributeTest.java index 6b87744..ed6ef0c 100644 --- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AttributeTest.java +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AttributeTest.java @@ -1,9 +1,22 @@ package org.jastadd.ragconnect.tests; -import attribute.ast.*; +import attributeInc.ast.*; +import de.tudresden.inf.st.jastadd.dumpAst.ast.Dumper; import org.junit.jupiter.api.Tag; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; + +import static java.util.function.Predicate.isEqual; +import static org.awaitility.Awaitility.await; +import static org.jastadd.ragconnect.tests.TestUtils.mqttUri; +import static org.jastadd.ragconnect.tests.TestUtils.waitForMqtt; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Test case "attribute". @@ -13,27 +26,150 @@ import java.io.IOException; @Tag("Incremental") @Tag("New") public class AttributeTest extends AbstractMqttTest { - Root model; - MqttHandler handler; + + private static final String TOPIC_WILDCARD = "attr/#"; + private static final String TOPIC_BASIC = "attr/string/basic"; + private static final String TOPIC_SIMPLE_NO_MAPPING = "attr/string/simple/plain"; + private static final String TOPIC_SIMPLE_WITH_MAPPING = "attr/string/simple/mapped"; + private static final String TOPIC_TRANSFORMED_NO_MAPPING = "attr/int/transformed/plain"; + private static final String TOPIC_TRANSFORMED_WITH_MAPPING = "attr/int/transformed/mapped"; + private static final String TOPIC_REFERENCE_TYPE_NO_MAPPING = "attr/a/ref/plain"; + private static final String TOPIC_REFERENCE_TYPE_WITH_MAPPING = "attr/a/ref/mapped"; + private static final String TOPIC_NTA_NO_MAPPING = "attr/a/nta/plain"; + private static final String TOPIC_NTA_WITH_MAPPING = "attr/a/nta/mapped"; + + private static final String INITIAL_STRING = "initial"; + + private MqttHandler handler; + private ReceiverData data; + + private Root model; + private SenderRoot senderString; + private SenderRoot senderInt; + private SenderRoot senderA; + private ReceiverRoot receiverRoot; @Override protected void createModel() { - + model = new Root(); + // model.trace().setReceiver(TestUtils::logEvent); + senderString = new SenderRoot().setInput(INITIAL_STRING); + senderInt = new SenderRoot().setInput(INITIAL_STRING); + senderA = new SenderRoot().setInput(INITIAL_STRING); + receiverRoot = new ReceiverRoot(); + model.addSenderRoot(senderString); + model.addSenderRoot(senderInt); + model.addSenderRoot(senderA); + model.setReceiverRoot(receiverRoot); } @Override protected void setupReceiverAndConnect() throws IOException, InterruptedException { + model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS); + handler = new MqttHandler().setHost(TestUtils.getMqttHost()).dontSendWelcomeMessage(); + assertTrue(handler.waitUntilReady(2, TimeUnit.SECONDS)); + + data = new ReceiverData(); + assertTrue(handler.newConnection(TOPIC_WILDCARD, bytes -> data.numberOfValues += 1)); + + // connect receive + assertTrue(receiverRoot.connectFromBasic(mqttUri(TOPIC_BASIC))); + assertTrue(receiverRoot.connectFromSimpleNoMapping(mqttUri(TOPIC_SIMPLE_NO_MAPPING))); + assertTrue(receiverRoot.connectFromSimpleWithMapping(mqttUri(TOPIC_SIMPLE_WITH_MAPPING))); + assertTrue(receiverRoot.connectFromTransformedNoMapping(mqttUri(TOPIC_TRANSFORMED_NO_MAPPING))); + assertTrue(receiverRoot.connectFromTransformedWithMapping(mqttUri(TOPIC_TRANSFORMED_WITH_MAPPING))); + assertTrue(receiverRoot.connectFromReferenceTypeNoMapping(mqttUri(TOPIC_REFERENCE_TYPE_NO_MAPPING))); + assertTrue(receiverRoot.connectFromReferenceTypeWithMapping(mqttUri(TOPIC_REFERENCE_TYPE_WITH_MAPPING))); + assertTrue(receiverRoot.connectFromNTANoMapping(mqttUri(TOPIC_NTA_NO_MAPPING))); + assertTrue(receiverRoot.connectFromNTAWithMapping(mqttUri(TOPIC_NTA_WITH_MAPPING))); + // connect send, and wait to receive (if writeCurrentValue is set) + assertTrue(senderString.connectBasic(mqttUri(TOPIC_BASIC), isWriteCurrentValue())); + assertTrue(senderString.connectSimple(mqttUri(TOPIC_SIMPLE_NO_MAPPING), isWriteCurrentValue())); + assertTrue(senderString.connectSimple(mqttUri(TOPIC_SIMPLE_WITH_MAPPING), isWriteCurrentValue())); + + assertTrue(senderInt.connectTransformed(mqttUri(TOPIC_TRANSFORMED_NO_MAPPING), isWriteCurrentValue())); + assertTrue(senderInt.connectTransformed(mqttUri(TOPIC_TRANSFORMED_WITH_MAPPING), isWriteCurrentValue())); + + assertTrue(senderA.connectToReferenceType(mqttUri(TOPIC_REFERENCE_TYPE_NO_MAPPING), isWriteCurrentValue())); + assertTrue(senderA.connectToReferenceType(mqttUri(TOPIC_REFERENCE_TYPE_WITH_MAPPING), isWriteCurrentValue())); + assertTrue(senderA.connectToNTA(mqttUri(TOPIC_NTA_NO_MAPPING), isWriteCurrentValue())); + assertTrue(senderA.connectToNTA(mqttUri(TOPIC_NTA_WITH_MAPPING), isWriteCurrentValue())); + + waitForValue(senderString.basic(), receiverRoot::getFromBasic); + waitForValue(senderString.simple(), receiverRoot::getFromSimpleNoMapping); + waitForNonNull(receiverRoot::getFromReferenceTypeNoMapping); + waitForNonNull(receiverRoot::getFromNTANoMapping); + } + + private <T> void waitForValue(T expectedValue, Callable<T> callable) { + if (isWriteCurrentValue()) { + await().until(callable, isEqual(expectedValue)); + } + } + + private <T> void waitForNonNull(Callable<T> callable) { + if (isWriteCurrentValue()) { + await().until(callable, Predicate.not(isEqual(null))); + } } @Override protected void communicateSendInitialValue() throws IOException, InterruptedException { + // basic, simple <-- senderString + // transformed <-- senderInt + // ref-type, nta <-- senderA + check(9, INITIAL_STRING, INITIAL_STRING + "Post", INITIAL_STRING, INITIAL_STRING, INITIAL_STRING); + senderString.setInput("test-01"); + check(12, "test-01", "test-01Post", INITIAL_STRING, INITIAL_STRING, INITIAL_STRING); } @Override protected void communicateOnlyUpdatedValue() throws IOException, InterruptedException { + waitForMqtt(); + } + + private void check(int numberOfValues, String basic, String simple, String transformed, + String refType, String nta) { + awaitEquals(numberOfValues, () -> data.numberOfValues, "numberOfValues"); + + try { + Path tempFile = Files.createTempFile("receiverRoot", "yml"); + Dumper.read(receiverRoot).dumpAsYaml(tempFile, false); + String content = Files.readString(tempFile); + logger.debug("receiverRoot\n" + content); + } catch (IOException e) { + e.printStackTrace(); + } + + awaitEquals(basic, receiverRoot::getFromBasic, "basic"); + + awaitEquals(simple, receiverRoot::getFromSimpleNoMapping, "simple"); + awaitEquals(simple + "post", receiverRoot::getFromSimpleWithMapping, "simple mapped"); + + int transformedLength = transformed.length(); + awaitEquals(transformedLength, receiverRoot::getFromTransformedNoMapping, "transformed"); + awaitEquals(transformedLength + 1, receiverRoot::getFromTransformedWithMapping, "transformed mapped"); + checkA(refType, "1", + receiverRoot.getFromReferenceTypeNoMapping(), "ref-type"); + checkA(refType + "post", "inner1", + receiverRoot.getFromReferenceTypeWithMapping(), "ref-type mapped"); + + checkA(nta, "2", + receiverRoot.getFromNTANoMapping(), "nta"); + checkA(nta + "post", "inner2", + receiverRoot.getFromNTAWithMapping(), "nta mapped"); + } + + private void checkA(String expectedValue, String expectedInner, A actual, String message) { + awaitEquals(expectedValue, actual::getValue, message + " value"); + awaitEquals(expectedInner, actual.getInner()::getInnerValue, message + " inner"); + } + + private <T> void awaitEquals(T expected, Callable<T> actual, String alias) { + await(alias).atMost(1500, TimeUnit.MILLISECONDS).until(actual, isEqual(expected)); } @Override @@ -45,4 +181,8 @@ public class AttributeTest extends AbstractMqttTest { model.ragconnectCloseConnections(); } } + + private static class ReceiverData { + int numberOfValues = 0; + } } -- GitLab From 27cf18207fde4c2013f474688a5e7eca8184df1c Mon Sep 17 00:00:00 2001 From: rschoene <rene.schoene@tu-dresden.de> Date: Thu, 3 Feb 2022 17:48:11 +0100 Subject: [PATCH 3/6] working on attributes as endpoint target - observer-entry now has list of connect-tokens and attributeCall is invoked once when attribute is flushed - INC_FLUSH_START and INC_FLUSH_END can now be nested - finished AttributeTest --- .../src/main/resources/ragconnect.mustache | 83 +++++++--- .../main/resources/sendDefinition.mustache | 6 +- ragconnect.tests/build.gradle | 10 -- .../src/test/01-input/attribute/Test.jadd | 2 +- .../ragconnect/tests/AttributeTest.java | 143 +++++++++++++----- 5 files changed, 175 insertions(+), 69 deletions(-) diff --git a/ragconnect.base/src/main/resources/ragconnect.mustache b/ragconnect.base/src/main/resources/ragconnect.mustache index fbe3dd5..088d2d3 100644 --- a/ragconnect.base/src/main/resources/ragconnect.mustache +++ b/ragconnect.base/src/main/resources/ragconnect.mustache @@ -87,22 +87,33 @@ aspect RagConnectObserver { class RagConnectObserver implements ASTState.Trace.Receiver { class RagConnectObserverEntry { - final RagConnectToken connectToken; final ASTNode node; final String attributeString; final boolean compareParams; final Object params; final Runnable attributeCall; + final java.util.List<RagConnectToken> connectList = new java.util.ArrayList<>(); - RagConnectObserverEntry(RagConnectToken connectToken, ASTNode node, String attributeString, + RagConnectObserverEntry(ASTNode node, String attributeString, boolean compareParams, Object params, Runnable attributeCall) { - this.connectToken = connectToken; this.node = node; this.attributeString = attributeString; this.compareParams = compareParams; this.params = params; this.attributeCall = attributeCall; } + + boolean baseMembersEqualTo(RagConnectObserverEntry other) { + return baseMembersEqualTo(other.node, other.attributeString, other.compareParams, other.params); + } + + boolean baseMembersEqualTo(ASTNode otherNode, String otherAttributeString, + boolean otherCompareParams, Object otherParams) { + return this.node.equals(otherNode) && + this.attributeString.equals(otherAttributeString) && + this.compareParams == otherCompareParams && + (!this.compareParams || java.util.Objects.equals(this.params, otherParams)); + } } {{#configExperimentalJastAdd329}} @@ -124,7 +135,7 @@ aspect RagConnectObserver { {{#configExperimentalJastAdd329}} java.util.Set<RagConnectObserverEntry> entryQueue = new java.util.HashSet<>(); - RagConnectObserverStartEntry startEntry = null; + java.util.Deque<RagConnectObserverStartEntry> startEntries = new java.util.LinkedList<>(); {{/configExperimentalJastAdd329}} RagConnectObserver(ASTNode node) { @@ -145,42 +156,72 @@ aspect RagConnectObserver { {{#configLoggingEnabledForIncremental}} System.out.println("** observer add: " + node + " on " + attributeString + (compareParams ? " (parameterized)" : "")); {{/configLoggingEnabledForIncremental}} - observedNodes.add(new RagConnectObserverEntry(connectToken, node, attributeString, - compareParams, params, attributeCall)); + // either add to an existing entry (with same node, attribute) or create new entry + boolean needNewEntry = true; + for (RagConnectObserverEntry entry : observedNodes) { + if (entry.baseMembersEqualTo(node, attributeString, compareParams, params)) { + entry.connectList.add(connectToken); + needNewEntry = false; + break; + } + } + if (needNewEntry) { + RagConnectObserverEntry newEntry = new RagConnectObserverEntry(node, attributeString, + compareParams, params, attributeCall); + newEntry.connectList.add(connectToken); + observedNodes.add(newEntry); + } } void remove(RagConnectToken connectToken) { - observedNodes.removeIf(entry -> entry.connectToken.equals(connectToken)); + RagConnectObserverEntry entryToDelete = null; + for (RagConnectObserverEntry entry : observedNodes) { + entry.connectList.remove(connectToken); + if (entry.connectList.isEmpty()) { + entryToDelete = entry; + } + } + if (entryToDelete != null) { + observedNodes.remove(entryToDelete); + } } + @Override public void accept(ASTState.Trace.Event event, ASTNode node, String attribute, Object params, Object value) { oldReceiver.accept(event, node, attribute, params, value); {{#configExperimentalJastAdd329}} // react to INC_FLUSH_START and remember entry - if (event == ASTState.Trace.Event.INC_FLUSH_START && startEntry == null) { + if (event == ASTState.Trace.Event.INC_FLUSH_START && startEntries.isEmpty()) { {{#configLoggingEnabledForIncremental}} System.out.println("** observer start: " + node + " on " + attribute); {{/configLoggingEnabledForIncremental}} - startEntry = new RagConnectObserverStartEntry(node, attribute, value); + startEntries.addFirst(new RagConnectObserverStartEntry(node, attribute, value)); return; } // react to INC_FLUSH_END and process queued entries, if it matches start entry - if (event == ASTState.Trace.Event.INC_FLUSH_END && - node == startEntry.node && + if (event == ASTState.Trace.Event.INC_FLUSH_END) { + if (startEntries.isEmpty()) { + {{#configLoggingEnabledForIncremental}} + System.out.println("** observer end without start! for " + node + " on " + attribute); + {{/configLoggingEnabledForIncremental}} + return; + } + RagConnectObserverStartEntry startEntry = startEntries.removeFirst(); + if (node == startEntry.node && attribute == startEntry.attributeString && value == startEntry.flushIncToken) { - // create a copy of the queue to avoid entering this again causing an endless recursion - RagConnectObserverEntry[] entriesToProcess = entryQueue.toArray(new RagConnectObserverEntry[entryQueue.size()]); - entryQueue.clear(); - startEntry = null; - {{#configLoggingEnabledForIncremental}} - System.out.println("** observer process (entries: " + entriesToProcess.length + "): " + node + " on " + attribute); - {{/configLoggingEnabledForIncremental}} - for (RagConnectObserverEntry entry : entriesToProcess) { - entry.attributeCall.run(); + // create a copy of the queue to avoid entering this again causing an endless recursion + RagConnectObserverEntry[] entriesToProcess = entryQueue.toArray(new RagConnectObserverEntry[entryQueue.size()]); + entryQueue.clear(); + {{#configLoggingEnabledForIncremental}} + System.out.println("** observer process (entries: " + entriesToProcess.length + "): " + node + " on " + attribute); + {{/configLoggingEnabledForIncremental}} + for (RagConnectObserverEntry entry : entriesToProcess) { + entry.attributeCall.run(); + } + return; } - return; } {{/configExperimentalJastAdd329}} diff --git a/ragconnect.base/src/main/resources/sendDefinition.mustache b/ragconnect.base/src/main/resources/sendDefinition.mustache index 499116e..2805097 100644 --- a/ragconnect.base/src/main/resources/sendDefinition.mustache +++ b/ragconnect.base/src/main/resources/sendDefinition.mustache @@ -49,7 +49,7 @@ public boolean {{parentTypeName}}.{{connectMethodName}}(String {{connectParamete {{#IndexBasedListAccess}}index,{{/IndexBasedListAccess}} () -> { if (this.{{updateMethodName}}({{#IndexBasedListAccess}}index{{/IndexBasedListAccess}})) { - this.{{writeMethodName}}({{#IndexBasedListAccess}}index, {{/IndexBasedListAccess}}connectToken); + this.{{writeMethodName}}({{#IndexBasedListAccess}}index{{/IndexBasedListAccess}}); } } ); @@ -106,6 +106,10 @@ protected boolean {{parentTypeName}}.{{updateMethodName}}({{#IndexBasedListAcces return {{senderName}} != null; } +protected void {{parentTypeName}}.{{writeMethodName}}({{#IndexBasedListAccess}}int index{{/IndexBasedListAccess}}) { + {{senderName}}.run({{#IndexBasedListAccess}}index{{/IndexBasedListAccess}}); +} + protected void {{parentTypeName}}.{{writeMethodName}}({{#IndexBasedListAccess}}int index, {{/IndexBasedListAccess}}RagConnectToken token) { {{senderName}}.run({{#IndexBasedListAccess}}index, {{/IndexBasedListAccess}}token); } diff --git a/ragconnect.tests/build.gradle b/ragconnect.tests/build.gradle index f57f477..8bc120a 100644 --- a/ragconnect.tests/build.gradle +++ b/ragconnect.tests/build.gradle @@ -50,7 +50,6 @@ dependencies { testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.4.0' testImplementation group: 'org.assertj', name: 'assertj-core', version: '3.12.1' testImplementation group: 'org.awaitility', name: 'awaitility', version: '4.1.1' - testImplementation group: 'de.tudresden.inf.st', name: 'dumpAst', version: '0.3.5' // jackson (for serialization of types) implementation group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.12.1' @@ -371,7 +370,6 @@ task compileTreeIncremental(type: RagConnectTest) { inputFiles = [file('src/test/01-input/tree/Test.relast'), file('src/test/01-input/tree/Test.connect')] rootNode = 'Root' - logWrites = true } relast { useJastAddNames = true @@ -414,7 +412,6 @@ task compileTreeAllowedTokensIncremental(type: RagConnectTest) { inputFiles = [file('src/test/01-input/treeAllowedTokens/Test.relast'), file('src/test/01-input/treeAllowedTokens/Test.connect')] rootNode = 'Root' - logWrites = true } relast { useJastAddNames = true @@ -605,9 +602,6 @@ task compileIndexedSendIncremental(type: RagConnectTest, dependsOn: ':ragconnect inputFiles = [file('src/test/01-input/indexedSend/Test.relast'), file('src/test/01-input/indexedSend/Test.connect')] rootNode = 'Root' - logWrites = true - logReads = true - logIncremental = true extraOptions = ['--experimental-jastadd-329'] } relast { @@ -630,9 +624,6 @@ task compileAttributeIncremental(type: RagConnectTest, dependsOn: ':ragconnect.b inputFiles = [file('src/test/01-input/attribute/Test.relast'), file('src/test/01-input/attribute/Test.connect')] rootNode = 'Root' - logWrites = true - logReads = true - logIncremental = true extraOptions = ['--experimental-jastadd-329'] } relast { @@ -647,4 +638,3 @@ task compileAttributeIncremental(type: RagConnectTest, dependsOn: ':ragconnect.b extraOptions = JASTADD_INCREMENTAL_OPTIONS_TRACING_FULL } } -compileAttributeIncremental.outputs.upToDateWhen { false } diff --git a/ragconnect.tests/src/test/01-input/attribute/Test.jadd b/ragconnect.tests/src/test/01-input/attribute/Test.jadd index c5274c6..34b1b45 100644 --- a/ragconnect.tests/src/test/01-input/attribute/Test.jadd +++ b/ragconnect.tests/src/test/01-input/attribute/Test.jadd @@ -1,7 +1,7 @@ aspect Computation { syn String SenderRoot.basic() = getInput(); syn String SenderRoot.simple() = getInput() + "Post"; - syn int SenderRoot.transformed() = getInput().length(); + syn int SenderRoot.transformed() = Integer.parseInt(getInput()); syn A SenderRoot.toReferenceType() { A result = new A(); result.setValue(getInput()); diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AttributeTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AttributeTest.java index ed6ef0c..54f0bca 100644 --- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AttributeTest.java +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AttributeTest.java @@ -1,21 +1,20 @@ package org.jastadd.ragconnect.tests; import attributeInc.ast.*; -import de.tudresden.inf.st.jastadd.dumpAst.ast.Dumper; +import org.awaitility.core.ConditionFactory; import org.junit.jupiter.api.Tag; import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; +import java.util.Objects; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; import java.util.function.Predicate; +import java.util.function.Supplier; import static java.util.function.Predicate.isEqual; import static org.awaitility.Awaitility.await; import static org.jastadd.ragconnect.tests.TestUtils.mqttUri; import static org.jastadd.ragconnect.tests.TestUtils.waitForMqtt; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; /** @@ -39,6 +38,7 @@ public class AttributeTest extends AbstractMqttTest { private static final String TOPIC_NTA_WITH_MAPPING = "attr/a/nta/mapped"; private static final String INITIAL_STRING = "initial"; + private static final String INITIAL_STRING_FOR_INT = "1"; private MqttHandler handler; private ReceiverData data; @@ -52,9 +52,9 @@ public class AttributeTest extends AbstractMqttTest { @Override protected void createModel() { model = new Root(); - // model.trace().setReceiver(TestUtils::logEvent); +// model.trace().setReceiver(TestUtils::logEvent); senderString = new SenderRoot().setInput(INITIAL_STRING); - senderInt = new SenderRoot().setInput(INITIAL_STRING); + senderInt = new SenderRoot().setInput(INITIAL_STRING_FOR_INT); senderA = new SenderRoot().setInput(INITIAL_STRING); receiverRoot = new ReceiverRoot(); model.addSenderRoot(senderString); @@ -98,6 +98,7 @@ public class AttributeTest extends AbstractMqttTest { waitForValue(senderString.basic(), receiverRoot::getFromBasic); waitForValue(senderString.simple(), receiverRoot::getFromSimpleNoMapping); + waitForValue(senderInt.transformed(), receiverRoot::getFromTransformedNoMapping); waitForNonNull(receiverRoot::getFromReferenceTypeNoMapping); waitForNonNull(receiverRoot::getFromNTANoMapping); } @@ -116,60 +117,130 @@ public class AttributeTest extends AbstractMqttTest { @Override protected void communicateSendInitialValue() throws IOException, InterruptedException { - // basic, simple <-- senderString - // transformed <-- senderInt - // ref-type, nta <-- senderA - check(9, INITIAL_STRING, INITIAL_STRING + "Post", INITIAL_STRING, INITIAL_STRING, INITIAL_STRING); + // basic, simple(2) <-- senderString + // transformed(2) <-- senderInt + // ref-type(2), nta(2) <-- senderA + check(9, INITIAL_STRING, INITIAL_STRING + "Post", INITIAL_STRING_FOR_INT, INITIAL_STRING, INITIAL_STRING); senderString.setInput("test-01"); - check(12, "test-01", "test-01Post", INITIAL_STRING, INITIAL_STRING, INITIAL_STRING); + check(12, "test-01", "test-01Post", INITIAL_STRING_FOR_INT, INITIAL_STRING, INITIAL_STRING); + + senderString.setInput("test-01"); + check(12, "test-01", "test-01Post", INITIAL_STRING_FOR_INT, INITIAL_STRING, INITIAL_STRING); + + senderInt.setInput("20"); + check(14, "test-01", "test-01Post", "20", INITIAL_STRING, INITIAL_STRING); + + senderA.setInput("test-03"); + check(18, "test-01", "test-01Post", "20", "test-03", "test-03"); + + assertTrue(senderString.disconnectSimple(mqttUri(TOPIC_SIMPLE_NO_MAPPING))); + assertTrue(senderString.disconnectSimple(mqttUri(TOPIC_SIMPLE_WITH_MAPPING))); + senderString.setInput("test-04"); + check(19, "test-04", "test-01Post", "20", "test-03", "test-03"); + + assertTrue(senderA.disconnectToNTA(mqttUri(TOPIC_NTA_NO_MAPPING))); + senderA.setInput("test-05"); + check(22, "test-04", "test-01Post", "20", "test-05", "test-03"); } @Override protected void communicateOnlyUpdatedValue() throws IOException, InterruptedException { waitForMqtt(); + // basic, simple(2) <-- senderString + // transformed(2) <-- senderInt + // ref-type(2), nta(2) <-- senderA + check(0, null, null, null, null, null); + + senderString.setInput("test-01"); + check(3, "test-01", "test-01Post", null, null, null); + + senderString.setInput("test-01"); + check(3, "test-01", "test-01Post", null, null, null); + + senderInt.setInput("20"); + check(5, "test-01", "test-01Post", "20", null, null); + + senderA.setInput("test-03"); + check(9, "test-01", "test-01Post", "20", "test-03", "test-03"); + + assertTrue(senderString.disconnectSimple(mqttUri(TOPIC_SIMPLE_NO_MAPPING))); + assertTrue(senderString.disconnectSimple(mqttUri(TOPIC_SIMPLE_WITH_MAPPING))); + senderString.setInput("test-04"); + check(10, "test-04", "test-01Post", "20", "test-03", "test-03"); + + assertTrue(senderA.disconnectToNTA(mqttUri(TOPIC_NTA_NO_MAPPING))); + senderA.setInput("test-05"); + check(13, "test-04", "test-01Post", "20", "test-05", "test-03"); } private void check(int numberOfValues, String basic, String simple, String transformed, - String refType, String nta) { + String a, String ntaNoMapping) { awaitEquals(numberOfValues, () -> data.numberOfValues, "numberOfValues"); - try { - Path tempFile = Files.createTempFile("receiverRoot", "yml"); - Dumper.read(receiverRoot).dumpAsYaml(tempFile, false); - String content = Files.readString(tempFile); - logger.debug("receiverRoot\n" + content); - } catch (IOException e) { - e.printStackTrace(); + awaitEquals(Objects.requireNonNullElse(basic, ""), + receiverRoot::getFromBasic, "basic"); + + if (simple != null) { + awaitEquals(simple, + receiverRoot::getFromSimpleNoMapping, "simple"); + awaitEquals(simple + "post", + receiverRoot::getFromSimpleWithMapping, "simple mapped"); + } else { + awaitEquals("", + receiverRoot::getFromSimpleNoMapping, "simple null"); + awaitEquals("", + receiverRoot::getFromSimpleWithMapping, "simple mapped null"); } - awaitEquals(basic, receiverRoot::getFromBasic, "basic"); + if (transformed != null) { + awaitEquals(Integer.parseInt(transformed), + receiverRoot::getFromTransformedNoMapping, "transformed"); + awaitEquals(Integer.parseInt(transformed) + 1, + receiverRoot::getFromTransformedWithMapping, "transformed mapped"); + } else { + awaitEquals(0, + receiverRoot::getFromTransformedNoMapping, "transformed null"); + awaitEquals(0, + receiverRoot::getFromTransformedWithMapping, "transformed mapped null"); + } - awaitEquals(simple, receiverRoot::getFromSimpleNoMapping, "simple"); - awaitEquals(simple + "post", receiverRoot::getFromSimpleWithMapping, "simple mapped"); + if (a != null) { + awaitA(a, "1", + receiverRoot.getFromReferenceTypeNoMapping(), "ref-type"); + awaitA(a + "post", "inner1", + receiverRoot.getFromReferenceTypeWithMapping(), "ref-type mapped"); + awaitA(a + "post", "inner2", + receiverRoot.getFromNTAWithMapping(), "nta mapped"); + } else { + awaitNull(receiverRoot::getFromReferenceTypeNoMapping, "manual ref-type null"); + awaitNull(receiverRoot::getFromReferenceTypeWithMapping, "ref-type mapped null"); + awaitNull(receiverRoot::getFromNTAWithMapping, "nta mapped null"); + } - int transformedLength = transformed.length(); - awaitEquals(transformedLength, receiverRoot::getFromTransformedNoMapping, "transformed"); - awaitEquals(transformedLength + 1, receiverRoot::getFromTransformedWithMapping, "transformed mapped"); + if (ntaNoMapping != null) { + awaitA(ntaNoMapping, "2", + receiverRoot.getFromNTANoMapping(), "nta"); + } else { + awaitNull(receiverRoot::getFromNTANoMapping, "nta null"); + } + } - checkA(refType, "1", - receiverRoot.getFromReferenceTypeNoMapping(), "ref-type"); - checkA(refType + "post", "inner1", - receiverRoot.getFromReferenceTypeWithMapping(), "ref-type mapped"); + private void awaitNull(Supplier<A> actual, String alias) { + internalAwait(alias).until(() -> actual.get() == null); + } - checkA(nta, "2", - receiverRoot.getFromNTANoMapping(), "nta"); - checkA(nta + "post", "inner2", - receiverRoot.getFromNTAWithMapping(), "nta mapped"); + private <T> void awaitEquals(T expected, Callable<T> actual, String alias) { + internalAwait(alias).until(actual, isEqual(expected)); } - private void checkA(String expectedValue, String expectedInner, A actual, String message) { + private void awaitA(String expectedValue, String expectedInner, A actual, String message) { awaitEquals(expectedValue, actual::getValue, message + " value"); awaitEquals(expectedInner, actual.getInner()::getInnerValue, message + " inner"); } - private <T> void awaitEquals(T expected, Callable<T> actual, String alias) { - await(alias).atMost(1500, TimeUnit.MILLISECONDS).until(actual, isEqual(expected)); + private ConditionFactory internalAwait(String alias) { + return await(alias).atMost(1500, TimeUnit.MILLISECONDS); } @Override -- GitLab From c3dcd4c4555a14da0bef787565e921a45b1c8f95 Mon Sep 17 00:00:00 2001 From: rschoene <rene.schoene@tu-dresden.de> Date: Fri, 4 Feb 2022 10:28:57 +0100 Subject: [PATCH 4/6] working on attributes as endpoint target - fix capitalize for null and empty strings, caused problems when used for empty entityName of context-free-endpoints --- ragconnect.base/src/main/jastadd/Util.jadd | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ragconnect.base/src/main/jastadd/Util.jadd b/ragconnect.base/src/main/jastadd/Util.jadd index 81b820c..31b15ba 100644 --- a/ragconnect.base/src/main/jastadd/Util.jadd +++ b/ragconnect.base/src/main/jastadd/Util.jadd @@ -1,5 +1,7 @@ aspect Util { static String ASTNode.capitalize(String s) { + if (s == null) return null; + if (s.isEmpty()) return ""; return Character.toUpperCase(s.charAt(0)) + s.substring(1); } protected T JastAddList.firstChild() { return getChild(0); } -- GitLab From 2c76008acc5b36bde5f718aa821c56692244ea8e Mon Sep 17 00:00:00 2001 From: rschoene <rene.schoene@tu-dresden.de> Date: Fri, 4 Feb 2022 13:56:39 +0100 Subject: [PATCH 5/6] working on attributes as endpoint target - add documentation on finding bugs - fix bug for nested flush events - consolidated use of awaitility --- pages/docs/inner-workings.md | 32 +++++++++++++++++-- .../src/main/resources/ragconnect.mustache | 5 +-- .../ragconnect/tests/AttributeTest.java | 18 +++-------- .../ragconnect/tests/ForwardingTest.java | 14 +++----- .../ragconnect/tests/IndexedSendTest.java | 6 ++-- .../jastadd/ragconnect/tests/TestUtils.java | 9 ++++-- .../singleList/AbstractSingleListTest.java | 6 ++-- 7 files changed, 52 insertions(+), 38 deletions(-) diff --git a/pages/docs/inner-workings.md b/pages/docs/inner-workings.md index bd9986a..1cb22a5 100644 --- a/pages/docs/inner-workings.md +++ b/pages/docs/inner-workings.md @@ -1,4 +1,4 @@ -# Inner workings of `RagConnect` +# Inner Workings of `RagConnect` Please see [API documentation](ragdoc/index.html) for more details. @@ -20,11 +20,11 @@ The other main aspect (which is currently not really used) is `IntermediateToYAM This is used to generate a YAML file containing the data used by mustache. It can be used by the default mustache implementation together with the templates. -# Implementation details +# Implementation Details In the following, details for special implementation topics are discussed. -## forwarding +## Forwarding When a nonterminal is used in a send endpoints, it needs an implicit forwarding attribute to work, because only _computed elements_ can be sent. Since the nonterminal itself should be sent, the generated attribute simply returns this nonterminal. @@ -33,3 +33,29 @@ However, changing any token within the whole subtree or changing the structure o This way, the dependency tracking registers a dependency between structure and tokens to the attribute. The attribute (as well as any other generated element) is prefixed with `_ragconnect_` to avoid potential name conflicts with user-specified elements. + +# Implementation Hints + +## Debugging Tests and Finding Bugs + +To help with finding errors/bugs when tests fail, there are several things to find the correct spot. + +- **Look closely**. Analyze the error message closely, and possible any previous error message(s) that could have caused the test to fail. +- **Focus on single error** + - To only inspect one test, mark them with `@Tag("New")` and use the gradle task "newTests". + - Use `Assumptions.assumeTrue(false);` to abort unneeded test cases early. + - When editing RagConnect itself and force recreating source for the affected test, e.g., `compileForwardingIncremental.outputs.upToDateWhen { false }` + - _Remember to undo all changes, once the bug is fixed._ +- **Activate logs**. Add the following to the `ragconnect` specification of the compile-task of the affected test: + ``` + logReads = true + logWrites = true + logIncremental = true + ``` + _Remember to remove those lines, once the bug is fixed._ +- **Trace incremental events**. Add the following right after create the root node (named `model` here): + ```java + model.trace().setReceiver(TestUtils::logEvent); + ``` + This will output every event fired by the incremental evaluation engine. _Remember to remove this line, once the bug is fixed._ +- **Add log statements**. As there will be quite some log output, add some identifying log statement (i.e., using `logger.fatal("---")`) right before the suspicious statement to inspect only the relevant log message after that. diff --git a/ragconnect.base/src/main/resources/ragconnect.mustache b/ragconnect.base/src/main/resources/ragconnect.mustache index 088d2d3..dc6b839 100644 --- a/ragconnect.base/src/main/resources/ragconnect.mustache +++ b/ragconnect.base/src/main/resources/ragconnect.mustache @@ -191,7 +191,7 @@ aspect RagConnectObserver { oldReceiver.accept(event, node, attribute, params, value); {{#configExperimentalJastAdd329}} // react to INC_FLUSH_START and remember entry - if (event == ASTState.Trace.Event.INC_FLUSH_START && startEntries.isEmpty()) { + if (event == ASTState.Trace.Event.INC_FLUSH_START) { {{#configLoggingEnabledForIncremental}} System.out.println("** observer start: " + node + " on " + attribute); {{/configLoggingEnabledForIncremental}} @@ -207,13 +207,14 @@ aspect RagConnectObserver { {{/configLoggingEnabledForIncremental}} return; } - RagConnectObserverStartEntry startEntry = startEntries.removeFirst(); + RagConnectObserverStartEntry startEntry = startEntries.peekFirst(); if (node == startEntry.node && attribute == startEntry.attributeString && value == startEntry.flushIncToken) { // create a copy of the queue to avoid entering this again causing an endless recursion RagConnectObserverEntry[] entriesToProcess = entryQueue.toArray(new RagConnectObserverEntry[entryQueue.size()]); entryQueue.clear(); + startEntries.removeFirst(); {{#configLoggingEnabledForIncremental}} System.out.println("** observer process (entries: " + entriesToProcess.length + "): " + node + " on " + attribute); {{/configLoggingEnabledForIncremental}} diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AttributeTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AttributeTest.java index 54f0bca..2e7e0c7 100644 --- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AttributeTest.java +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AttributeTest.java @@ -1,7 +1,6 @@ package org.jastadd.ragconnect.tests; import attributeInc.ast.*; -import org.awaitility.core.ConditionFactory; import org.junit.jupiter.api.Tag; import java.io.IOException; @@ -12,9 +11,7 @@ import java.util.function.Predicate; import java.util.function.Supplier; import static java.util.function.Predicate.isEqual; -import static org.awaitility.Awaitility.await; -import static org.jastadd.ragconnect.tests.TestUtils.mqttUri; -import static org.jastadd.ragconnect.tests.TestUtils.waitForMqtt; +import static org.jastadd.ragconnect.tests.TestUtils.*; import static org.junit.jupiter.api.Assertions.assertTrue; /** @@ -23,7 +20,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue; * @author rschoene - Initial contribution */ @Tag("Incremental") -@Tag("New") public class AttributeTest extends AbstractMqttTest { private static final String TOPIC_WILDCARD = "attr/#"; @@ -105,13 +101,13 @@ public class AttributeTest extends AbstractMqttTest { private <T> void waitForValue(T expectedValue, Callable<T> callable) { if (isWriteCurrentValue()) { - await().until(callable, isEqual(expectedValue)); + awaitMqtt().until(callable, isEqual(expectedValue)); } } private <T> void waitForNonNull(Callable<T> callable) { if (isWriteCurrentValue()) { - await().until(callable, Predicate.not(isEqual(null))); + awaitMqtt().until(callable, Predicate.not(isEqual(null))); } } @@ -227,11 +223,11 @@ public class AttributeTest extends AbstractMqttTest { } private void awaitNull(Supplier<A> actual, String alias) { - internalAwait(alias).until(() -> actual.get() == null); + awaitMqtt().alias(alias).until(() -> actual.get() == null); } private <T> void awaitEquals(T expected, Callable<T> actual, String alias) { - internalAwait(alias).until(actual, isEqual(expected)); + awaitMqtt().alias(alias).until(actual, isEqual(expected)); } private void awaitA(String expectedValue, String expectedInner, A actual, String message) { @@ -239,10 +235,6 @@ public class AttributeTest extends AbstractMqttTest { awaitEquals(expectedInner, actual.getInner()::getInnerValue, message + " inner"); } - private ConditionFactory internalAwait(String alias) { - return await(alias).atMost(1500, TimeUnit.MILLISECONDS); - } - @Override protected void closeConnections() { if (handler != null) { diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ForwardingTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ForwardingTest.java index 64430eb..e73f769 100644 --- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ForwardingTest.java +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ForwardingTest.java @@ -1,8 +1,6 @@ package org.jastadd.ragconnect.tests; import forwardingInc.ast.*; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import org.assertj.core.groups.Tuple; import org.junit.jupiter.api.Tag; @@ -14,9 +12,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.groups.Tuple.tuple; -import static org.awaitility.Awaitility.await; -import static org.jastadd.ragconnect.tests.TestUtils.mqttUri; -import static org.jastadd.ragconnect.tests.TestUtils.waitForMqtt; +import static org.jastadd.ragconnect.tests.TestUtils.*; import static org.junit.jupiter.api.Assertions.*; /** @@ -55,7 +51,7 @@ public class ForwardingTest extends AbstractMqttTest { @Override protected void createModel() { model = new Root(); -// model.trace().setReceiver(TestUtils::logEvent); + model.trace().setReceiver(TestUtils::logEvent); senderRoot = new SenderRoot(); model.setSenderRoot(senderRoot); @@ -183,7 +179,7 @@ public class ForwardingTest extends AbstractMqttTest { private void waitForValue() { if (isWriteCurrentValue()) { - await().until(() -> data.valueSentSinceLastCheck.getAndSet(false)); + awaitMqtt().until(() -> data.valueSentSinceLastCheck.getAndSet(false)); } } @@ -524,9 +520,7 @@ public class ForwardingTest extends AbstractMqttTest { if (model != null) { model.ragconnectCloseConnections(); } - if (observer != null) { - observer.init(); - } + observer.init(); } static class Values<T> { diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/IndexedSendTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/IndexedSendTest.java index a4575fe..75d7d16 100644 --- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/IndexedSendTest.java +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/IndexedSendTest.java @@ -11,9 +11,7 @@ import java.util.concurrent.TimeUnit; import java.util.function.Predicate; import static org.assertj.core.groups.Tuple.tuple; -import static org.awaitility.Awaitility.await; -import static org.jastadd.ragconnect.tests.TestUtils.mqttUri; -import static org.jastadd.ragconnect.tests.TestUtils.waitForMqtt; +import static org.jastadd.ragconnect.tests.TestUtils.*; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -99,7 +97,7 @@ public class IndexedSendTest extends AbstractMqttTest { private void waitForValue(Callable<Integer> callable, int expectedValue) { if (isWriteCurrentValue()) { - await().until(callable, Predicate.isEqual(expectedValue)); + awaitMqtt().until(callable, Predicate.isEqual(expectedValue)); } } diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TestUtils.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TestUtils.java index 937c930..9a6c22b 100644 --- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TestUtils.java +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TestUtils.java @@ -5,6 +5,8 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.awaitility.Awaitility; +import org.awaitility.core.ConditionFactory; import org.jastadd.ragconnect.compiler.Compiler; import org.junit.jupiter.api.Assertions; @@ -24,7 +26,6 @@ import java.util.stream.Collectors; import static java.util.Collections.addAll; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.util.Lists.newArrayList; -import static org.awaitility.Awaitility.await; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -145,6 +146,10 @@ public class TestUtils { TimeUnit.MILLISECONDS.sleep(1500); } + public static ConditionFactory awaitMqtt() { + return Awaitility.await().atMost(1500, TimeUnit.MILLISECONDS); + } + static <T_Event, T_ASTNode> void logEvent(T_Event event, T_ASTNode node, String attribute, Object params, Object value) { logger.info("event: {}, node: {}, attribute: {}, params: {}, value: {}", event, node, attribute, params, value); @@ -388,7 +393,7 @@ public class TestUtils { } void awaitChange() { - await().until(hasChanged); + awaitMqtt().until(hasChanged); updatePrevious(); } diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleList/AbstractSingleListTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleList/AbstractSingleListTest.java index 832ba76..cdcb41c 100644 --- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleList/AbstractSingleListTest.java +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleList/AbstractSingleListTest.java @@ -14,10 +14,8 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Function; -import static org.awaitility.Awaitility.await; +import static org.jastadd.ragconnect.tests.TestUtils.*; import static org.jastadd.ragconnect.tests.TestUtils.IntList.list; -import static org.jastadd.ragconnect.tests.TestUtils.mqttUri; -import static org.jastadd.ragconnect.tests.TestUtils.testJaddContainReferenceToJackson; import static org.junit.jupiter.api.Assertions.*; /** @@ -167,7 +165,7 @@ public abstract class AbstractSingleListTest extends AbstractMqttTest { private void waitForValue() { if (isWriteCurrentValue()) { - await().until(() -> data.valueSentSinceLastCheck.getAndSet(false)); + awaitMqtt().until(() -> data.valueSentSinceLastCheck.getAndSet(false)); } } -- GitLab From 9268a5f9ff7820bc45183bbf753e01635dbdaf37 Mon Sep 17 00:00:00 2001 From: rschoene <rene.schoene@tu-dresden.de> Date: Fri, 4 Feb 2022 15:37:40 +0100 Subject: [PATCH 6/6] working on attributes as endpoint target - add documentation for attributes and their types - rename entityIsNormalAttribute to hasAttributeResetMethod (and flip boolean value) --- pages/docs/dsl.md | 8 +++++--- ragconnect.base/src/main/jastadd/Analysis.jrag | 16 +++++++++------- .../src/main/jastadd/Intermediate.jadd | 11 ++++------- .../src/main/jastadd/IntermediateToYAML.jrag | 2 +- ragconnect.base/src/main/jastadd/Mappings.jrag | 18 ++++-------------- .../src/main/jastadd/Navigation.jrag | 1 - .../main/jastadd/parser/ParserRewrites.jrag | 2 +- .../src/main/resources/sendDefinition.mustache | 4 ++-- .../src/test/01-input/errors/Standard.connect | 9 +++++++++ .../src/test/01-input/errors/Standard.expected | 2 ++ .../ragconnect/tests/ForwardingTest.java | 2 +- 11 files changed, 38 insertions(+), 37 deletions(-) diff --git a/pages/docs/dsl.md b/pages/docs/dsl.md index 19125a8..e8b6b2b 100644 --- a/pages/docs/dsl.md +++ b/pages/docs/dsl.md @@ -13,7 +13,7 @@ The kind of the element determines, whether an endpoint for it can be receiving, To declare a new endpoints, use the following syntax: ``` -("send"|"receive") ["indexed"] ["with add"] <Non-Terminal>[.<Target>["()"]] ["using" <Mapping-Name> (, <Mapping-Name>)*] ";" +("send"|"receive") ["indexed"] ["with add"] <Non-Terminal>[.<Target>["(<AttributeType>)"]] ["using" <Mapping-Name> (, <Mapping-Name>)*] ";" ``` A breakdown of the parts of that syntax: @@ -28,10 +28,12 @@ A breakdown of the parts of that syntax: - The second optional keyword `with add` can also be used only for receiving endpoints targeting a list children. As described above, it can be combined with `indexed`. If used on its own, the incoming data is interpreted as a complete list and its elements will be appended to the current list. -- The `<Non-Terminal>[.<Target>["()"]]` notation describes the actual affected node. +- The `<Non-Terminal>[.<Target>["(<AttributeType>)"]]` notation describes the actual affected node. - If the target is omitted, all nodes of that non-terminal type can be connected, irrespective of their context. This is a context-free endpoint definition. - The target can be any child on the right-hand side of a production rule, a role of a relation, or an attribute. - The brackets `()` after the target must be used in case of an attribute, and only then. + The brackets `(<AttributeType>)` after the target must be used in case of an attribute, and only then. + Here, the return type of the attribute has to be specified, as aspect files are not parsed by RagConnect. + Hence, RagConnect can not and will not verify the existence of the attribute, and the possible non-existence of an attribute will be found by the Java compiler. - Optionally, an endpoint can use one or more [mappings](#mappings). They will be applied before sending, or after receiving a message. Mappings will always be applied in the order they are listed after `using`. diff --git a/ragconnect.base/src/main/jastadd/Analysis.jrag b/ragconnect.base/src/main/jastadd/Analysis.jrag index b5fb2ce..d4e166a 100644 --- a/ragconnect.base/src/main/jastadd/Analysis.jrag +++ b/ragconnect.base/src/main/jastadd/Analysis.jrag @@ -76,15 +76,17 @@ aspect Analysis { } } - // TODO rename entityIsNormalAttribute to actual meaning - syn boolean EndpointTarget.entityIsNormalAttribute(); - eq AttributeEndpointTarget.entityIsNormalAttribute() = true; - eq TokenEndpointTarget.entityIsNormalAttribute() = !getToken().getNTA(); - eq TypeEndpointTarget.entityIsNormalAttribute() = !getType().getNTA(); - eq ContextFreeTypeEndpointTarget.entityIsNormalAttribute() = false; + syn boolean EndpointTarget.hasAttributeResetMethod(); + eq AttributeEndpointTarget.hasAttributeResetMethod() = false; + eq TokenEndpointTarget.hasAttributeResetMethod() = getToken().getNTA(); + eq TypeEndpointTarget.hasAttributeResetMethod() = getType().getNTA(); + eq ContextFreeTypeEndpointTarget.hasAttributeResetMethod() = false; // --- needProxyToken --- - syn boolean TokenComponent.needProxyToken() = !getDependencySourceDefinitionList().isEmpty() || getTokenEndpointTargetList().stream().map(EndpointTarget::containingEndpointDefinition).anyMatch(EndpointDefinition::shouldSendValue); + syn boolean TokenComponent.needProxyToken() = !getDependencySourceDefinitionList().isEmpty() || + getTokenEndpointTargetList().stream() + .map(EndpointTarget::containingEndpointDefinition) + .anyMatch(EndpointDefinition::shouldNotResetValue); // --- effectiveUsedAt --- coll Set<EndpointDefinition> MappingDefinition.effectiveUsedAt() diff --git a/ragconnect.base/src/main/jastadd/Intermediate.jadd b/ragconnect.base/src/main/jastadd/Intermediate.jadd index e26b797..47f3aa4 100644 --- a/ragconnect.base/src/main/jastadd/Intermediate.jadd +++ b/ragconnect.base/src/main/jastadd/Intermediate.jadd @@ -168,7 +168,6 @@ aspect MustacheMappingApplicationAndDefinition { syn String MEndpointDefinition.preemptiveReturn(); syn String MEndpointDefinition.firstInputVarName(); - // TODO check MAttributeSendDefinition eq MAttributeSendDefinition.firstInputVarName() = getterMethodCall(); eq MAttributeSendDefinition.preemptiveExpectedValue() = lastValueGetterCall(); eq MAttributeSendDefinition.preemptiveReturn() = "return false;"; @@ -438,7 +437,7 @@ aspect MustacheSendDefinition { syn String EndpointDefinition.senderName() = getEndpointTarget().senderName(); - syn boolean EndpointDefinition.shouldSendValue() = getSend() && getEndpointTarget().entityIsNormalAttribute(); + syn boolean EndpointDefinition.shouldNotResetValue() = getSend() && !getEndpointTarget().hasAttributeResetMethod(); syn String EndpointDefinition.tokenResetMethodName() = getterMethodName() + "_reset"; @@ -468,13 +467,11 @@ aspect MustacheSendDefinition { syn String EndpointTarget.senderName() = ragconnect().internalRagConnectPrefix() + "_sender_" + entityName(); eq ContextFreeTypeEndpointTarget.senderName() = null; - // TOOO both updateMethodName and writeMethodName could be defined for MEndpointDefinition using getEndpointDefinition().enitityName() syn String MEndpointDefinition.updateMethodName(); syn String MEndpointDefinition.writeMethodName(); - // TODO check MAttributeSendDefinition. maybe name-clash possible if there is an attribute with same name as a child - eq MAttributeSendDefinition.updateMethodName() = ragconnect().internalRagConnectPrefix() + "_update_" + getEndpointDefinition().entityName(); - eq MAttributeSendDefinition.writeMethodName() = ragconnect().internalRagConnectPrefix() + "_writeLastValue_" + getEndpointDefinition().entityName(); + eq MAttributeSendDefinition.updateMethodName() = ragconnect().internalRagConnectPrefix() + "_update_attr_" + getEndpointDefinition().entityName(); + eq MAttributeSendDefinition.writeMethodName() = ragconnect().internalRagConnectPrefix() + "_writeLastValue_attr_" + getEndpointDefinition().entityName(); eq MTokenReceiveDefinition.updateMethodName() = null; eq MTokenReceiveDefinition.writeMethodName() = null; @@ -509,7 +506,7 @@ aspect MustacheTokenComponent { syn EndpointDefinition TokenComponent.normalTokenSendDef() { for (EndpointTarget target : getTokenEndpointTargetList()) { - if (target.isTokenEndpointTarget() && target.containingEndpointDefinition().shouldSendValue()) { + if (target.isTokenEndpointTarget() && target.containingEndpointDefinition().shouldNotResetValue()) { return target.containingEndpointDefinition(); } } diff --git a/ragconnect.base/src/main/jastadd/IntermediateToYAML.jrag b/ragconnect.base/src/main/jastadd/IntermediateToYAML.jrag index fb198ce..49c2693 100644 --- a/ragconnect.base/src/main/jastadd/IntermediateToYAML.jrag +++ b/ragconnect.base/src/main/jastadd/IntermediateToYAML.jrag @@ -103,7 +103,7 @@ aspect IntermediateToYAML { result.put("lastValueGetterCall" , lastValueGetterCall()); result.put("lastValueSetter" , lastValueSetter()); result.put("senderName" , senderName()); - result.put("shouldSendValue" , shouldSendValue()); + result.put("shouldNotResetValue" , shouldNotResetValue()); result.put("tokenResetMethodName" , tokenResetMethodName()); result.put("updateMethodName" , updateMethodName()); result.put("writeMethodName" , writeMethodName()); diff --git a/ragconnect.base/src/main/jastadd/Mappings.jrag b/ragconnect.base/src/main/jastadd/Mappings.jrag index fb7c296..aa07069 100644 --- a/ragconnect.base/src/main/jastadd/Mappings.jrag +++ b/ragconnect.base/src/main/jastadd/Mappings.jrag @@ -191,14 +191,6 @@ aspect Mappings { // --- suitableReceiveDefaultMapping --- syn DefaultMappingDefinition EndpointDefinition.suitableReceiveDefaultMapping() { - // TODO might be expanded to AttributeEndpointTarget - if (getEndpointTarget().isTypeEndpointTarget()) { - try { - TypeDecl typeDecl = program().resolveTypeDecl(targetTypeName()); - return typeIsList() && !getIndexBasedListAccess() ? ragconnect().defaultBytesToListTreeMapping(typeDecl.getName()) : ragconnect().defaultBytesToTreeMapping(typeDecl.getName()); - } catch (Exception ignore) { - } - } switch (targetTypeName()) { case "boolean": case "Boolean": @@ -226,8 +218,7 @@ aspect Mappings { default: try { TypeDecl typeDecl = program().resolveTypeDecl(targetTypeName()); - // TODO: also support list-types, if list is first type - return ragconnect().defaultBytesToTreeMapping(typeDecl.getName()); + return getEndpointTarget().isTypeEndpointTarget() && typeIsList() && !getIndexBasedListAccess() ? ragconnect().defaultBytesToListTreeMapping(typeDecl.getName()) : ragconnect().defaultBytesToTreeMapping(typeDecl.getName()); } catch (Exception ignore) { } System.err.println("Could not find suitable default mapping for " + targetTypeName() + " on " + this); @@ -237,10 +228,6 @@ aspect Mappings { // --- suitableSendDefaultMapping --- syn DefaultMappingDefinition EndpointDefinition.suitableSendDefaultMapping() { - // TODO might be expanded to AttributeEndpointTarget - if (getEndpointTarget().isTypeEndpointTarget() && typeIsList() && !getIndexBasedListAccess()) { - return ragconnect().defaultListTreeToBytesMapping(); - } switch (targetTypeName()) { case "boolean": case "Boolean": @@ -266,6 +253,9 @@ aspect Mappings { case "String": return ragconnect().defaultStringToBytesMapping(); default: + if (getEndpointTarget().isTypeEndpointTarget() && typeIsList() && !getIndexBasedListAccess()) { + return ragconnect().defaultListTreeToBytesMapping(); + } try { TypeDecl typeDecl = program().resolveTypeDecl(targetTypeName()); return ragconnect().defaultTreeToBytesMapping(typeDecl.getName()); diff --git a/ragconnect.base/src/main/jastadd/Navigation.jrag b/ragconnect.base/src/main/jastadd/Navigation.jrag index cb89e31..dac1931 100644 --- a/ragconnect.base/src/main/jastadd/Navigation.jrag +++ b/ragconnect.base/src/main/jastadd/Navigation.jrag @@ -1,6 +1,5 @@ aspect NewStuff { - // TODO regenerate for AttributeEndpointTarget /** Tests if EndpointTarget is a TokenEndpointTarget. * @return 'true' if this is a TokenEndpointTarget, otherwise 'false' */ diff --git a/ragconnect.base/src/main/jastadd/parser/ParserRewrites.jrag b/ragconnect.base/src/main/jastadd/parser/ParserRewrites.jrag index 3de9341..04e8cad 100644 --- a/ragconnect.base/src/main/jastadd/parser/ParserRewrites.jrag +++ b/ragconnect.base/src/main/jastadd/parser/ParserRewrites.jrag @@ -50,7 +50,7 @@ aspect ParserRewrites { eq UntypedEndpointTarget.parentTypeName() = "<untyped.parentTypeName>"; eq UntypedEndpointTarget.entityName() = "<untyped.entityName>"; eq UntypedEndpointTarget.isAlreadyDefined() = false; - eq UntypedEndpointTarget.entityIsNormalAttribute() = false; + eq UntypedEndpointTarget.hasAttributeResetMethod() = false; eq UntypedEndpointTarget.targetTypeName() = "<untyped.targetTypeName>"; eq UntypedEndpointTarget.isTypeEndpointTarget() = false; } diff --git a/ragconnect.base/src/main/resources/sendDefinition.mustache b/ragconnect.base/src/main/resources/sendDefinition.mustache index 2805097..18d48ca 100644 --- a/ragconnect.base/src/main/resources/sendDefinition.mustache +++ b/ragconnect.base/src/main/resources/sendDefinition.mustache @@ -97,9 +97,9 @@ public boolean {{parentTypeName}}.{{disconnectMethodName}}(String {{connectParam } protected boolean {{parentTypeName}}.{{updateMethodName}}({{#IndexBasedListAccess}}int index{{/IndexBasedListAccess}}) { - {{^shouldSendValue}} + {{^shouldNotResetValue}} {{tokenResetMethodName}}(); - {{/shouldSendValue}} + {{/shouldNotResetValue}} {{> mappingApplication}} {{lastValueSetter}}({{#IndexBasedListAccess}}index, {{/IndexBasedListAccess}}{{lastResult}}); // normally we would return true here. unless no connect method was called so far to initialize {{senderName}} yet diff --git a/ragconnect.tests/src/test/01-input/errors/Standard.connect b/ragconnect.tests/src/test/01-input/errors/Standard.connect index 6224df4..fb2c7cb 100644 --- a/ragconnect.tests/src/test/01-input/errors/Standard.connect +++ b/ragconnect.tests/src/test/01-input/errors/Standard.connect @@ -60,6 +60,15 @@ D.SourceDoubledValue canDependOn D.TargetDoubledValue as DoubledValue ; D.SourceDoubledValue canDependOn D.TargetDoubledValue as DoubledValue ; send D.TargetDoubledValue; +// non-existence of attributes is not checked by RagConnect +send A.nonExistingAttribute(int); + +// Already defined endpoints for attributes will be reported, however +send A.nonExistingAttribute(int); + +// mappings are not checked, here string would not match +send A.anotherIntAttribute(int) using StringToString; + // --- mapping definitions --- ListToList maps java.util.List<String> list to java.util.List<String> {: return list; diff --git a/ragconnect.tests/src/test/01-input/errors/Standard.expected b/ragconnect.tests/src/test/01-input/errors/Standard.expected index 9b3d23a..1f2b02d 100644 --- a/ragconnect.tests/src/test/01-input/errors/Standard.expected +++ b/ragconnect.tests/src/test/01-input/errors/Standard.expected @@ -11,3 +11,5 @@ Standard.connect Line 43, column 1: Endpoint definition already defined for C.Do Standard.connect Line 44, column 1: Endpoint definition already defined for C.DoubledValue Standard.connect Line 55, column 1: The name of a dependency definition must not be equal to a list-node on the source Standard.connect Line 60, column 1: Dependency definition already defined for D with name DoubledValue +Standard.connect Line 64, column 1: Endpoint definition already defined for A.nonExistingAttribute +Standard.connect Line 67, column 1: Endpoint definition already defined for A.nonExistingAttribute diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ForwardingTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ForwardingTest.java index e73f769..570c4d0 100644 --- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ForwardingTest.java +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ForwardingTest.java @@ -51,7 +51,7 @@ public class ForwardingTest extends AbstractMqttTest { @Override protected void createModel() { model = new Root(); - model.trace().setReceiver(TestUtils::logEvent); +// model.trace().setReceiver(TestUtils::logEvent); senderRoot = new SenderRoot(); model.setSenderRoot(senderRoot); -- GitLab