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