From 87a1dcd95770e74db5671d3e9131ad60e866b9c7 Mon Sep 17 00:00:00 2001
From: rschoene <rene.schoene@tu-dresden.de>
Date: Mon, 2 May 2022 11:06:34 +0200
Subject: [PATCH 1/7] Updated initial version of Java handler.

- also missing fix for CI (ragdoc-view should always run)
---
 .gitlab-ci.yml                                |  3 -
 .../src/main/jastadd/Handlers.jrag            |  1 +
 .../jastadd/ragconnect/compiler/Compiler.java |  3 +
 .../src/main/resources/JavaHandler.mustache   | 88 +++++++++++++++++++
 .../src/main/resources/handler.mustache       | 10 ++-
 .../main/resources/receiveDefinition.mustache | 12 +++
 .../main/resources/sendDefinition.mustache    | 22 +++++
 ragconnect.tests/build.gradle                 | 29 +++++-
 .../src/test/01-input/java/README.md          |  3 +
 .../src/test/01-input/java/Test.connect       | 25 ++++++
 .../src/test/01-input/java/Test.jadd          | 30 +++++++
 .../src/test/01-input/java/Test.relast        |  9 ++
 .../jastadd/ragconnect/tests/JavaTest.java    | 43 +++++++++
 13 files changed, 270 insertions(+), 8 deletions(-)
 create mode 100644 ragconnect.base/src/main/resources/JavaHandler.mustache
 create mode 100644 ragconnect.tests/src/test/01-input/java/README.md
 create mode 100644 ragconnect.tests/src/test/01-input/java/Test.connect
 create mode 100644 ragconnect.tests/src/test/01-input/java/Test.jadd
 create mode 100644 ragconnect.tests/src/test/01-input/java/Test.relast
 create mode 100644 ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/JavaTest.java

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 87d2c9c..44a279a 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -94,9 +94,6 @@ ragdoc_view:
     - OUTPUT_DIR=$(pwd -P)/pages/docs/ragdoc
     - cd /ragdoc-view/src/ && rm -rf data && ln -s $DATA_DIR
     - /ragdoc-view/build-view.sh --output-path=$OUTPUT_DIR
-  only:
-    - dev
-    - master
   artifacts:
     paths:
       - "pages/docs/ragdoc"
diff --git a/ragconnect.base/src/main/jastadd/Handlers.jrag b/ragconnect.base/src/main/jastadd/Handlers.jrag
index 57c7ccd..e64a8c5 100644
--- a/ragconnect.base/src/main/jastadd/Handlers.jrag
+++ b/ragconnect.base/src/main/jastadd/Handlers.jrag
@@ -1,4 +1,5 @@
 aspect RagConnectHandlers {
+  syn Handler RagConnect.javaHandler() = resolveHandlerByName("java");
   syn Handler RagConnect.mqttHandler() = resolveHandlerByName("mqtt");
   syn Handler RagConnect.restHandler() = resolveHandlerByName("rest");
 
diff --git a/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/Compiler.java b/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/Compiler.java
index 8d4ab2c..e255b3b 100644
--- a/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/Compiler.java
+++ b/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/Compiler.java
@@ -35,6 +35,7 @@ public class Compiler extends AbstractCompiler {
   private static final String OPTION_LOGGING_TARGET_CONSOLE = "console";
   private static final String OPTION_LOGGING_TARGET_SLF4J = "slf4j";
 
+  private static final String OPTION_PROTOCOL_JAVA = "java";
   private static final String OPTION_PROTOCOL_MQTT = "mqtt";
   private static final String OPTION_PROTOCOL_REST = "rest";
 
@@ -179,6 +180,7 @@ public class Compiler extends AbstractCompiler {
         new ValueOption("protocols", "Protocols to enable")
             .acceptMultipleValues(true)
             .addDefaultValue(OPTION_PROTOCOL_MQTT, "Enable MQTT")
+            .addAcceptedValue(OPTION_PROTOCOL_JAVA, "Enable Java")
             .addAcceptedValue(OPTION_PROTOCOL_REST, "Enable REST")
     );
     optionPrintYaml = addOption(
@@ -331,6 +333,7 @@ public class Compiler extends AbstractCompiler {
     ragConnect.getConfiguration().setRootNode(rootNode);
 
     // Handler ::= <ClassName> <UniqueName> <InUse:boolean>;
+    ragConnect.addHandler(new Handler("JavaHandler", "java", optionProtocols.hasValue(OPTION_PROTOCOL_JAVA)));
     ragConnect.addHandler(new Handler("MqttServerHandler", "mqtt", optionProtocols.hasValue(OPTION_PROTOCOL_MQTT)));
     ragConnect.addHandler(new Handler("RestServerHandler", "rest", optionProtocols.hasValue(OPTION_PROTOCOL_REST)));
   }
diff --git a/ragconnect.base/src/main/resources/JavaHandler.mustache b/ragconnect.base/src/main/resources/JavaHandler.mustache
new file mode 100644
index 0000000..bc73c91
--- /dev/null
+++ b/ragconnect.base/src/main/resources/JavaHandler.mustache
@@ -0,0 +1,88 @@
+/**
+ * Singleton class providing routing functionality for byte[] based message calls.
+ */
+public class JavaHandler {
+  public static JavaHandler JAVA_HANDLER_INSTANCE = null;
+  private java.util.Map<String, java.util.List<Pair<String, java.util.function.BiConsumer<String, byte[]>>>> callbackList = new java.util.concurrent.ConcurrentHashMap<>();
+  private String name;
+
+  private JavaHandler() {
+    this("RagConnect");
+  }
+
+  public JavaHandler(String name) {
+    this.name = name;
+  }
+
+  public synchronized static JavaHandler getInstance() {
+    if (JAVA_HANDLER_INSTANCE == null) {
+      JAVA_HANDLER_INSTANCE = new JavaHandler();
+    }
+    return JAVA_HANDLER_INSTANCE;
+  }
+
+  public String registerCallback(String topic, java.util.function.BiConsumer<String, byte[]> callback) {
+    {{logInfo}}("[JAVA_HANDLER] Registering new callback for {{log_}}.", topic);
+
+    String callbackUUID = java.util.UUID.randomUUID().toString();
+
+    java.util.List<Pair<String, java.util.function.BiConsumer<String, byte[]>>> registeredCallbacks = getAllCallbacks().get(topic);
+
+    if (registeredCallbacks == null) {
+      java.util.List<Pair<String, java.util.function.BiConsumer<String, byte[]>>> newCallbackList = java.util.Collections.synchronizedList(new java.util.ArrayList<>());
+      newCallbackList.add(new Pair<>(callbackUUID, callback));
+      callbackList.put(topic, newCallbackList);
+    } else {
+      registeredCallbacks.add(new Pair<>(callbackUUID, callback));
+    }
+
+    return callbackUUID;
+  }
+
+  public boolean unregisterCallback(String path, String uuid) {
+    {{logInfo}}("[JAVA_HANDLER] Unregistering callback with uuid: {{log_}}", uuid + " on path: {{log_}}", path);
+
+    java.util.List<Pair<String, java.util.function.BiConsumer<String, byte[]>>> callbacks = getAllCallbacks().get(path);
+
+    int count = 0;
+
+    if (callbacks != null) {
+      for (Pair<String, java.util.function.BiConsumer<String, byte[]>> callbackPair : callbacks) {
+        if (callbackPair._1.equals(uuid)) {
+          callbacks.remove(count);
+          return true;
+        } else {
+          count++;
+        }
+      }
+    }
+    return false;
+  }
+
+  public void close() {
+  }
+
+  public boolean push(String topic, byte[] data) {
+    {{logDebug}}("[JAVA_HANDLER] Pushing a message.");
+    String dataString = new String(data);
+    {{logDebug}}("[JAVA_HANDLER] Data: {{log_}}", dataString);
+
+    java.util.List<Pair<String, java.util.function.BiConsumer<String, byte[]>>> callbacks = getAllCallbacks().get(topic);
+
+    if (callbacks == null) {
+      {{logError}}("[JAVA_HANDLER] Could not publish message. No callback registered for topic {{log_}}", topic);
+      return false;
+    }
+
+    for (Pair<String, java.util.function.BiConsumer<String, byte[]>> callbackPair : callbacks) {
+      {{logDebug}}("[JAVA_HANDLER] Calling callback: {{log_}}", callbackPair._1);
+      callbackPair._2.accept(topic, data);
+    }
+
+    return true;
+  }
+
+  public java.util.Map<String, java.util.List<Pair<String, java.util.function.BiConsumer<String, byte[]>>>> getAllCallbacks() {
+    return callbackList;
+  }
+}
diff --git a/ragconnect.base/src/main/resources/handler.mustache b/ragconnect.base/src/main/resources/handler.mustache
index 4919426..2c4cf96 100644
--- a/ragconnect.base/src/main/resources/handler.mustache
+++ b/ragconnect.base/src/main/resources/handler.mustache
@@ -19,18 +19,24 @@ aspect RagConnectHandler {
     {{/configIncrementalOptionActive}}
   }
 
+{{#javaHandler}}
+  {{#InUse}}
+  {{> JavaHandler}}
+  {{/InUse}}
+{{/javaHandler}}
+
 {{#mqttHandler}}
   {{#InUse}}
   public void {{rootNodeName}}.{{setupWaitUntilReadyMethodName}}(long time, java.util.concurrent.TimeUnit unit) {
     {{fieldName}}.setupWaitUntilReady(time, unit);
   }
+  {{> MqttHandler}}
   {{/InUse}}
-{{> MqttHandler}}
 {{/mqttHandler}}
 
 {{#restHandler}}
   {{#InUse}}
-{{> RestHandler}}
+  {{> RestHandler}}
   {{/InUse}}
 {{/restHandler}}
 
diff --git a/ragconnect.base/src/main/resources/receiveDefinition.mustache b/ragconnect.base/src/main/resources/receiveDefinition.mustache
index cb4dc8d..c4f070c 100644
--- a/ragconnect.base/src/main/resources/receiveDefinition.mustache
+++ b/ragconnect.base/src/main/resources/receiveDefinition.mustache
@@ -93,6 +93,13 @@ private boolean {{parentTypeName}}.{{internalConnectMethodName}}(String {{connec
   RagConnectToken connectToken = new RagConnectToken(uri, "{{entityName}}");
   boolean success;
   switch (scheme) {
+  {{#javaHandler}}
+  {{#InUse}}
+    case "java":
+      success = {{attributeName}}().registerCallback(path, consumer, connectToken);
+      break;
+  {{/InUse}}
+  {{/javaHandler}}
   {{#mqttHandler}}
   {{#InUse}}
     case "mqtt":
@@ -130,6 +137,11 @@ public boolean {{parentTypeName}}.{{disconnectMethodName}}(String {{connectParam
   }
   RagConnectDisconnectHandlerMethod disconnectingMethod;
   switch (scheme) {
+  {{#javaHandler}}
+  {{#InUse}}
+    case "java": return {{attributeName}}().unregisterCallback(uri.getPath(), connectTokens.get(this).get(uri).globalId);
+  {{/InUse}}
+  {{/javaHandler}}
   {{#mqttHandler}}
   {{#InUse}}
     case "mqtt": disconnectingMethod = {{attributeName}}()::disconnect;
diff --git a/ragconnect.base/src/main/resources/sendDefinition.mustache b/ragconnect.base/src/main/resources/sendDefinition.mustache
index 1e66fde..1485c17 100644
--- a/ragconnect.base/src/main/resources/sendDefinition.mustache
+++ b/ragconnect.base/src/main/resources/sendDefinition.mustache
@@ -5,6 +5,21 @@ public boolean {{parentTypeName}}.{{connectMethodName}}(String {{connectParamete
   RagConnectToken connectToken = new RagConnectToken(uri, "{{entityName}}");
   boolean success;
   switch (scheme) {
+  {{#javaHandler}}
+  {{#InUse}}
+    case "java":
+    final JavaHandler handler = {{attributeName}}().getInstance();
+
+    {{senderName}}.add(() -> {
+      handler.push(path, {{lastValueGetterCall}});
+    }{{#IndexBasedListAccess}}, index{{/IndexBasedListAccess}}, connectToken);
+    {{updateMethodName}}();
+    if (writeCurrentValue) {
+      {{writeMethodName}}({{#IndexBasedListAccess}}index, {{/IndexBasedListAccess}}connectToken);
+    }
+    break;
+  {{/InUse}}
+  {{/javaHandler}}
   {{#mqttHandler}}
   {{#InUse}}
     case "mqtt":
@@ -78,6 +93,13 @@ public boolean {{parentTypeName}}.{{disconnectMethodName}}(String {{connectParam
   {{/configIncrementalOptionActive}}
   RagConnectDisconnectHandlerMethod disconnectingMethod;
   switch (scheme) {
+  {{#javaHandler}}
+  {{#InUse}}
+    case "java":
+      disconnectingMethod = {{senderName}}::remove;
+    break;
+  {{/InUse}}
+  {{/javaHandler}}
   {{#mqttHandler}}
   {{#InUse}}
     case "mqtt":
diff --git a/ragconnect.tests/build.gradle b/ragconnect.tests/build.gradle
index 18dd839..d41a73c 100644
--- a/ragconnect.tests/build.gradle
+++ b/ragconnect.tests/build.gradle
@@ -649,8 +649,6 @@ task compileRelationIncremental(type: RagConnectTest) {
         inputFiles = [file('src/test/01-input/relation/Test.relast'),
                       file('src/test/01-input/relation/Test.connect')]
         rootNode = 'Root'
-        logWrites = true
-        logIncremental = true
         extraOptions = defaultRagConnectOptionsAnd(['--experimental-jastadd-329'])
     }
     relast {
@@ -666,9 +664,34 @@ task compileRelationIncremental(type: RagConnectTest) {
     }
 }
 
+// --- Test: java-incremental ---
+task compileJavaIncremental(type: RagConnectTest) {
+    ragconnect {
+        outputDir = file('src/test/02-after-ragconnect/javaInc')
+        inputFiles = [file('src/test/01-input/java/Test.relast'),
+                      file('src/test/01-input/java/Test.connect')]
+        rootNode = 'Root'
+        logWrites = true
+        logIncremental = true
+        protocols = ['java']
+        extraOptions = defaultRagConnectOptionsAnd(['--experimental-jastadd-329'])
+    }
+    relast {
+        useJastAddNames = true
+        grammarName = 'src/test/03-after-relast/javaInc/javaInc'
+        serializer = 'jackson'
+    }
+    jastadd {
+        jastAddList = 'JastAddList'
+        packageName = 'javaInc.ast'
+        inputFiles = [file('src/test/01-input/java/Test.jadd')]
+        extraOptions = JASTADD_INCREMENTAL_OPTIONS_TRACING_FULL
+    }
+}
+
 // --- Task order ---
 classes.dependsOn(':ragconnect.base:jar')
-//compileAttributeIncremental.outputs.upToDateWhen { false }
+compileJavaIncremental.outputs.upToDateWhen { false }
 
 // --- Misc ---
 static ArrayList<String> defaultRagConnectOptionsAnd(ArrayList<String> options = []) {
diff --git a/ragconnect.tests/src/test/01-input/java/README.md b/ragconnect.tests/src/test/01-input/java/README.md
new file mode 100644
index 0000000..272a851
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/java/README.md
@@ -0,0 +1,3 @@
+# Java
+
+Idea: Use receive and send definitions using the Java handler.
diff --git a/ragconnect.tests/src/test/01-input/java/Test.connect b/ragconnect.tests/src/test/01-input/java/Test.connect
new file mode 100644
index 0000000..dcb6a56
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/java/Test.connect
@@ -0,0 +1,25 @@
+send SenderRoot.SendToken ;
+send SenderRoot.SendNode ;
+send SenderRoot.SendManyNode ;
+send SenderRoot.SendNTA ;
+
+AddSuffix maps A a to A {:
+  A result = new A();
+  String changedValue = a.getValue() + "post";
+  result.setValue(changedValue);
+  result.setInner(new Inner("inner" + a.getInner().getInnerValue()));
+  return result;
+:}
+
+AddStringSuffix maps String s to String {:
+  return s + "post";
+:}
+
+AddPlusOne maps int i to int {:
+  return i + 1;
+:}
+
+receive ReceiverRoot.SomeToken;
+receive ReceiverRoot.SomeNode;
+receive ReceiverRoot.SomeNodeWithMapping using AddSuffix;
+receive ReceiverRoot.ManyNode;
diff --git a/ragconnect.tests/src/test/01-input/java/Test.jadd b/ragconnect.tests/src/test/01-input/java/Test.jadd
new file mode 100644
index 0000000..0bb643e
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/java/Test.jadd
@@ -0,0 +1,30 @@
+aspect Computation {
+  syn String SenderRoot.basic() = getInput();
+  syn String SenderRoot.simple() = getInput() + "Post";
+  syn A SenderRoot.getSendNTA() {
+    A result = new A();
+    result.setValue(getInput());
+    Inner inner = new Inner();
+    inner.setInnerValue("1");
+    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/java/Test.relast b/ragconnect.tests/src/test/01-input/java/Test.relast
new file mode 100644
index 0000000..13c7ba3
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/java/Test.relast
@@ -0,0 +1,9 @@
+Root ::= SenderRoot* ReceiverRoot;
+SenderRoot ::= <Input> <SendToken> SendNode:A SendManyNode:A* /SendNTA:A/ ;
+ReceiverRoot ::=
+    <SomeToken>
+    SomeNode:A
+    SomeNodeWithMapping:A
+    ManyNode:A*;
+A ::= <Value> Inner ;
+Inner ::= <InnerValue> ;
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/JavaTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/JavaTest.java
new file mode 100644
index 0000000..2d22b3c
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/JavaTest.java
@@ -0,0 +1,43 @@
+package org.jastadd.ragconnect.tests;
+
+import javaInc.ast.Root;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Testing the Java handler.
+ *
+ * @author rschoene - Initial contribution
+ */
+@Tag("New")
+public class JavaTest {
+
+  protected Logger logger = LoggerFactory.getLogger(getClass());
+  private Root model;
+
+  void createModel() {
+    model = new Root();
+  }
+
+  @Test
+  public void testCommunicateSendInitialValue() {
+    createModel();
+  }
+
+  @Test
+  public void testCommunicateOnlyUpdatedValue() {
+    createModel();
+  }
+
+  @AfterEach
+  public void alwaysCloseConnections() {
+    logger.debug("Closing connections");
+    if (model != null) {
+      model.ragconnectCloseConnections();
+    }
+  }
+
+}
-- 
GitLab


From b9c399cb09c19451ed6c3b2fcff574adee5e21e2 Mon Sep 17 00:00:00 2001
From: rschoene <rene.schoene@tu-dresden.de>
Date: Mon, 2 May 2022 18:47:49 +0200
Subject: [PATCH 2/7] fixed errors in JavaHandler

---
 .../src/main/resources/JavaHandler.mustache   | 67 ++++++++-----------
 .../main/resources/receiveDefinition.mustache |  5 +-
 .../main/resources/sendDefinition.mustache    |  1 +
 ragconnect.tests/build.gradle                 |  7 ++
 .../tests/relation/RelationTest.java          |  1 -
 5 files changed, 40 insertions(+), 41 deletions(-)

diff --git a/ragconnect.base/src/main/resources/JavaHandler.mustache b/ragconnect.base/src/main/resources/JavaHandler.mustache
index bc73c91..9efd0ba 100644
--- a/ragconnect.base/src/main/resources/JavaHandler.mustache
+++ b/ragconnect.base/src/main/resources/JavaHandler.mustache
@@ -3,7 +3,8 @@
  */
 public class JavaHandler {
   public static JavaHandler JAVA_HANDLER_INSTANCE = null;
-  private java.util.Map<String, java.util.List<Pair<String, java.util.function.BiConsumer<String, byte[]>>>> callbackList = new java.util.concurrent.ConcurrentHashMap<>();
+  private java.util.Map<String, java.util.List<java.util.function.BiConsumer<String, byte[]>>> callbackList = new java.util.concurrent.ConcurrentHashMap<>();
+  private final java.util.Map<RagConnectToken, java.util.function.BiConsumer<String, byte[]>> tokensForRemoval = new java.util.HashMap<>();
   private String name;
 
   private JavaHandler() {
@@ -21,68 +22,58 @@ public class JavaHandler {
     return JAVA_HANDLER_INSTANCE;
   }
 
-  public String registerCallback(String topic, java.util.function.BiConsumer<String, byte[]> callback) {
-    {{logInfo}}("[JAVA_HANDLER] Registering new callback for {{log_}}.", topic);
+  private static String extractPath(java.net.URI uri) {
+    String path = uri.getPath();
+    if (path.charAt(0) == '/') {
+      path = path.substring(1);
+    }
+    return path;
+  }
+
 
-    String callbackUUID = java.util.UUID.randomUUID().toString();
+  public boolean registerCallback(RagConnectToken connectToken, java.util.function.BiConsumer<String, byte[]> callback) {
+    String path = extractPath(connectToken.uri);
+    {{logInfo}}("[JAVA_HANDLER] Registering new callback for {{log_}}.", path);
 
-    java.util.List<Pair<String, java.util.function.BiConsumer<String, byte[]>>> registeredCallbacks = getAllCallbacks().get(topic);
+    java.util.List<java.util.function.BiConsumer<String, byte[]>> registeredCallbacks = callbackList.get(path);
 
     if (registeredCallbacks == null) {
-      java.util.List<Pair<String, java.util.function.BiConsumer<String, byte[]>>> newCallbackList = java.util.Collections.synchronizedList(new java.util.ArrayList<>());
-      newCallbackList.add(new Pair<>(callbackUUID, callback));
-      callbackList.put(topic, newCallbackList);
-    } else {
-      registeredCallbacks.add(new Pair<>(callbackUUID, callback));
+      registeredCallbacks = java.util.Collections.synchronizedList(new java.util.ArrayList<>());
+      callbackList.put(path, registeredCallbacks);
     }
+    registeredCallbacks.add(callback);
+    tokensForRemoval.put(connectToken, callback);
 
-    return callbackUUID;
+    return true;
   }
 
-  public boolean unregisterCallback(String path, String uuid) {
-    {{logInfo}}("[JAVA_HANDLER] Unregistering callback with uuid: {{log_}}", uuid + " on path: {{log_}}", path);
-
-    java.util.List<Pair<String, java.util.function.BiConsumer<String, byte[]>>> callbacks = getAllCallbacks().get(path);
+  public boolean unregisterCallback(RagConnectToken connectToken) {
+    String path = extractPath(connectToken.uri);
+    java.util.function.BiConsumer<String, byte[]> callback = tokensForRemoval.get(connectToken);
+    {{logInfo}}("[JAVA_HANDLER] Unregistering callback with uuid: on path: {{log_}}", path);
 
-    int count = 0;
-
-    if (callbacks != null) {
-      for (Pair<String, java.util.function.BiConsumer<String, byte[]>> callbackPair : callbacks) {
-        if (callbackPair._1.equals(uuid)) {
-          callbacks.remove(count);
-          return true;
-        } else {
-          count++;
-        }
-      }
-    }
-    return false;
+    return callbackList.get(path).remove(callback);
   }
 
   public void close() {
   }
 
-  public boolean push(String topic, byte[] data) {
+  public boolean push(String path, byte[] data) {
     {{logDebug}}("[JAVA_HANDLER] Pushing a message.");
     String dataString = new String(data);
     {{logDebug}}("[JAVA_HANDLER] Data: {{log_}}", dataString);
 
-    java.util.List<Pair<String, java.util.function.BiConsumer<String, byte[]>>> callbacks = getAllCallbacks().get(topic);
+    java.util.List<java.util.function.BiConsumer<String, byte[]>> callbacks = callbackList.get(path);
 
     if (callbacks == null) {
-      {{logError}}("[JAVA_HANDLER] Could not publish message. No callback registered for topic {{log_}}", topic);
+      {{logError}}("[JAVA_HANDLER] Could not publish message. No callback registered for path {{log_}}", path);
       return false;
     }
 
-    for (Pair<String, java.util.function.BiConsumer<String, byte[]>> callbackPair : callbacks) {
-      {{logDebug}}("[JAVA_HANDLER] Calling callback: {{log_}}", callbackPair._1);
-      callbackPair._2.accept(topic, data);
+    for (java.util.function.BiConsumer<String, byte[]> callback : callbacks) {
+      callback.accept(path, data);
     }
 
     return true;
   }
-
-  public java.util.Map<String, java.util.List<Pair<String, java.util.function.BiConsumer<String, byte[]>>>> getAllCallbacks() {
-    return callbackList;
-  }
 }
diff --git a/ragconnect.base/src/main/resources/receiveDefinition.mustache b/ragconnect.base/src/main/resources/receiveDefinition.mustache
index c4f070c..28a9bb5 100644
--- a/ragconnect.base/src/main/resources/receiveDefinition.mustache
+++ b/ragconnect.base/src/main/resources/receiveDefinition.mustache
@@ -96,7 +96,7 @@ private boolean {{parentTypeName}}.{{internalConnectMethodName}}(String {{connec
   {{#javaHandler}}
   {{#InUse}}
     case "java":
-      success = {{attributeName}}().registerCallback(path, consumer, connectToken);
+      success = {{attributeName}}().registerCallback(connectToken, consumer);
       break;
   {{/InUse}}
   {{/javaHandler}}
@@ -139,7 +139,8 @@ public boolean {{parentTypeName}}.{{disconnectMethodName}}(String {{connectParam
   switch (scheme) {
   {{#javaHandler}}
   {{#InUse}}
-    case "java": return {{attributeName}}().unregisterCallback(uri.getPath(), connectTokens.get(this).get(uri).globalId);
+    case "java": disconnectingMethod = {{attributeName}}()::unregisterCallback;
+    break;
   {{/InUse}}
   {{/javaHandler}}
   {{#mqttHandler}}
diff --git a/ragconnect.base/src/main/resources/sendDefinition.mustache b/ragconnect.base/src/main/resources/sendDefinition.mustache
index 1485c17..fae0fd2 100644
--- a/ragconnect.base/src/main/resources/sendDefinition.mustache
+++ b/ragconnect.base/src/main/resources/sendDefinition.mustache
@@ -17,6 +17,7 @@ public boolean {{parentTypeName}}.{{connectMethodName}}(String {{connectParamete
     if (writeCurrentValue) {
       {{writeMethodName}}({{#IndexBasedListAccess}}index, {{/IndexBasedListAccess}}connectToken);
     }
+    success = true;
     break;
   {{/InUse}}
   {{/javaHandler}}
diff --git a/ragconnect.tests/build.gradle b/ragconnect.tests/build.gradle
index d41a73c..5a2a82a 100644
--- a/ragconnect.tests/build.gradle
+++ b/ragconnect.tests/build.gradle
@@ -691,7 +691,14 @@ task compileJavaIncremental(type: RagConnectTest) {
 
 // --- Task order ---
 classes.dependsOn(':ragconnect.base:jar')
+// TODO remove special handling of compileJavaIncremental once finished
+compileJavaIncremental {
+    doFirst {
+        fileTree(dir: 'src/test/java-gen/javaInc/ast/', exclude: '.gitignore')
+    }
+}
 compileJavaIncremental.outputs.upToDateWhen { false }
+compileJavaIncremental.dependsOn(':ragconnect.base:assemble')
 
 // --- Misc ---
 static ArrayList<String> defaultRagConnectOptionsAnd(ArrayList<String> options = []) {
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/relation/RelationTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/relation/RelationTest.java
index a88f97f..d9c4979 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/relation/RelationTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/relation/RelationTest.java
@@ -21,7 +21,6 @@ import static org.junit.jupiter.api.Assertions.*;
  * @author rschoene - Initial contribution
  */
 @Tag("Incremental")
-@Tag("New")
 public class RelationTest extends AbstractMqttTest {
 
   private static final String TOPIC_WILDCARD = "rel/#";
-- 
GitLab


From 853711571efd03429b4020e7d4ada6c9ac223b1f Mon Sep 17 00:00:00 2001
From: rschoene <rene.schoene@tu-dresden.de>
Date: Sat, 21 May 2022 14:42:05 +0200
Subject: [PATCH 3/7] working on JavaHandler

- add registerConsumer
- remove singleton
- working on tests
---
 .../src/main/resources/JavaHandler.mustache   |  22 ++-
 .../main/resources/sendDefinition.mustache    |   2 +-
 ragconnect.tests/build.gradle                 |   5 +-
 .../src/test/01-input/java/Test.connect       |   1 +
 .../src/test/01-input/java/Test.relast        |   3 +-
 .../jastadd/ragconnect/tests/JavaTest.java    | 179 +++++++++++++++++-
 .../jastadd/ragconnect/tests/TestUtils.java   |  13 +-
 7 files changed, 209 insertions(+), 16 deletions(-)

diff --git a/ragconnect.base/src/main/resources/JavaHandler.mustache b/ragconnect.base/src/main/resources/JavaHandler.mustache
index 9efd0ba..90caae3 100644
--- a/ragconnect.base/src/main/resources/JavaHandler.mustache
+++ b/ragconnect.base/src/main/resources/JavaHandler.mustache
@@ -15,13 +15,6 @@ public class JavaHandler {
     this.name = name;
   }
 
-  public synchronized static JavaHandler getInstance() {
-    if (JAVA_HANDLER_INSTANCE == null) {
-      JAVA_HANDLER_INSTANCE = new JavaHandler();
-    }
-    return JAVA_HANDLER_INSTANCE;
-  }
-
   private static String extractPath(java.net.URI uri) {
     String path = uri.getPath();
     if (path.charAt(0) == '/') {
@@ -30,6 +23,12 @@ public class JavaHandler {
     return path;
   }
 
+  public RagConnectToken registerConsumer(String path, java.util.function.Consumer<byte[]> consumer) {
+    RagConnectToken token = new RagConnectToken(java.net.URI.create("internal://host/" + path), null);
+    registerCallback(token, (s, bytes) -> consumer.accept(bytes));
+    return token;
+  }
+
 
   public boolean registerCallback(RagConnectToken connectToken, java.util.function.BiConsumer<String, byte[]> callback) {
     String path = extractPath(connectToken.uri);
@@ -58,8 +57,13 @@ public class JavaHandler {
   public void close() {
   }
 
-  public boolean push(String path, byte[] data) {
-    {{logDebug}}("[JAVA_HANDLER] Pushing a message.");
+  public boolean push(String uriString, byte[] data) {
+    String path = extractPath(java.net.URI.create(uriString));
+    {{logDebug}}("[JAVA_HANDLER] Pushing a message for {{log_}}.", path);
+    if (data == null) {
+      {{logDebug}}("[JAVA_HANDLER] data was null, aborting");
+      return false;
+    }
     String dataString = new String(data);
     {{logDebug}}("[JAVA_HANDLER] Data: {{log_}}", dataString);
 
diff --git a/ragconnect.base/src/main/resources/sendDefinition.mustache b/ragconnect.base/src/main/resources/sendDefinition.mustache
index fae0fd2..0829973 100644
--- a/ragconnect.base/src/main/resources/sendDefinition.mustache
+++ b/ragconnect.base/src/main/resources/sendDefinition.mustache
@@ -8,7 +8,7 @@ public boolean {{parentTypeName}}.{{connectMethodName}}(String {{connectParamete
   {{#javaHandler}}
   {{#InUse}}
     case "java":
-    final JavaHandler handler = {{attributeName}}().getInstance();
+    final JavaHandler handler = {{attributeName}}();
 
     {{senderName}}.add(() -> {
       handler.push(path, {{lastValueGetterCall}});
diff --git a/ragconnect.tests/build.gradle b/ragconnect.tests/build.gradle
index 5a2a82a..719b55d 100644
--- a/ragconnect.tests/build.gradle
+++ b/ragconnect.tests/build.gradle
@@ -70,6 +70,8 @@ dependencies {
 
     testImplementation group: 'net.sf.beaver', name: 'beaver-rt', version: '0.9.11'
     api group: 'com.google.protobuf', name: 'protobuf-java', version: '3.0.0'
+
+    implementation group: 'de.tudresden.inf.st', name: 'dumpAst', version: "1.0.2-68"
 }
 
 // --- Preprocessors ---
@@ -671,6 +673,7 @@ task compileJavaIncremental(type: RagConnectTest) {
         inputFiles = [file('src/test/01-input/java/Test.relast'),
                       file('src/test/01-input/java/Test.connect')]
         rootNode = 'Root'
+        logReads = true
         logWrites = true
         logIncremental = true
         protocols = ['java']
@@ -697,7 +700,7 @@ compileJavaIncremental {
         fileTree(dir: 'src/test/java-gen/javaInc/ast/', exclude: '.gitignore')
     }
 }
-compileJavaIncremental.outputs.upToDateWhen { false }
+//compileJavaIncremental.outputs.upToDateWhen { false }
 compileJavaIncremental.dependsOn(':ragconnect.base:assemble')
 
 // --- Misc ---
diff --git a/ragconnect.tests/src/test/01-input/java/Test.connect b/ragconnect.tests/src/test/01-input/java/Test.connect
index dcb6a56..d24dde5 100644
--- a/ragconnect.tests/src/test/01-input/java/Test.connect
+++ b/ragconnect.tests/src/test/01-input/java/Test.connect
@@ -23,3 +23,4 @@ receive ReceiverRoot.SomeToken;
 receive ReceiverRoot.SomeNode;
 receive ReceiverRoot.SomeNodeWithMapping using AddSuffix;
 receive ReceiverRoot.ManyNode;
+receive ReceiverRoot.NTA;
diff --git a/ragconnect.tests/src/test/01-input/java/Test.relast b/ragconnect.tests/src/test/01-input/java/Test.relast
index 13c7ba3..62a23c2 100644
--- a/ragconnect.tests/src/test/01-input/java/Test.relast
+++ b/ragconnect.tests/src/test/01-input/java/Test.relast
@@ -4,6 +4,7 @@ ReceiverRoot ::=
     <SomeToken>
     SomeNode:A
     SomeNodeWithMapping:A
-    ManyNode:A*;
+    ManyNode:A*
+    NTA:A;
 A ::= <Value> Inner ;
 Inner ::= <InnerValue> ;
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/JavaTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/JavaTest.java
index 2d22b3c..5d6e492 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/JavaTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/JavaTest.java
@@ -1,12 +1,21 @@
 package org.jastadd.ragconnect.tests;
 
-import javaInc.ast.Root;
+import javaInc.ast.*;
+import org.assertj.core.groups.Tuple;
+import org.jastadd.ragconnect.tests.TestUtils.TestChecker;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Tag;
 import org.junit.jupiter.api.Test;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.IOException;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.tuple;
+import static org.jastadd.ragconnect.tests.TestUtils.javaUri;
+import static org.junit.jupiter.api.Assertions.*;
+
 /**
  * Testing the Java handler.
  *
@@ -15,21 +24,140 @@ import org.slf4j.LoggerFactory;
 @Tag("New")
 public class JavaTest {
 
+  private static final String TOPIC_RECEIVE_TOKEN = "receiveToken";
+  private static final String TOPIC_RECEIVE_NODE_PLAIN = "receiveNode/plain";
+  private static final String TOPIC_RECEIVE_NODE_MAPPED = "receiveNode/mapped";
+  private static final String TOPIC_RECEIVE_MANY = "receiveMany";
+  private static final String TOPIC_RECEIVE_NTA = "receiveNTA";
+
+  private static final String TOPIC_SEND_TOKEN = "sendToken";
+  private static final String TOPIC_SEND_NODE = "sendNode";
+  private static final String TOPIC_SEND_MANY = "sendMany";
+  private static final String TOPIC_SEND_NTA = "sendNTA";
+
   protected Logger logger = LoggerFactory.getLogger(getClass());
   private Root model;
+  private SenderRoot senderRoot;
+  private ReceiverRoot receiverRoot;
+
+  private String lastValueToken;
+  private A lastValueNode;
+  private JastAddList<A> lastValueMany;
+  private A lastValueNTA;
+  private TestChecker checker;
+  private JavaHandler handler;
 
   void createModel() {
     model = new Root();
+    senderRoot = new SenderRoot().setInput("1").setSendNode(createA("1"));
+    receiverRoot = new ReceiverRoot();
+    model.addSenderRoot(senderRoot);
+    model.setReceiverRoot(receiverRoot);
+  }
+
+  private static A createA(String value) {
+    return new A().setValue(value).setInner(new Inner("inner"));
+  }
+
+  private void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException {
+    // checker
+    checker = new TestChecker();
+    checker.setActualString(TOPIC_SEND_TOKEN, () -> lastValueToken)
+            .setCheckForString(TOPIC_SEND_NODE, (name, expected) -> checkA(expected, lastValueNode, name))
+            .setCheckForTuple(TOPIC_SEND_MANY, (name, expected) -> checkAList(expected, lastValueMany, name))
+            .setCheckForString(TOPIC_SEND_NTA, (name, expected) -> checkA(expected, lastValueNTA, name))
+            .setActualString(TOPIC_RECEIVE_TOKEN, () -> receiverRoot.getSomeToken())
+            .put(TOPIC_RECEIVE_TOKEN, "")
+            .setActualNumberOfValues(() -> 0)
+            .disableManualWait();
+
+    // callbacks
+    handler = model._ragconnect_javaHandler();
+    handler.registerConsumer(TOPIC_SEND_TOKEN, bytes -> lastValueToken = new String(bytes));
+    handler.registerConsumer(TOPIC_SEND_NODE, bytes -> lastValueNode = ExposingASTNode.INSTANCE.bytesToA(bytes));
+    handler.registerConsumer(TOPIC_SEND_MANY, bytes -> lastValueMany = ExposingASTNode.INSTANCE.bytesToList(bytes));
+    handler.registerConsumer(TOPIC_SEND_NTA, bytes -> lastValueNTA = ExposingASTNode.INSTANCE.bytesToA(bytes));
+
+    // receive
+    receiverRoot.connectSomeToken(javaUri(TOPIC_RECEIVE_TOKEN));
+    receiverRoot.connectSomeNode(javaUri(TOPIC_RECEIVE_NODE_PLAIN));
+    receiverRoot.connectSomeNodeWithMapping(javaUri(TOPIC_RECEIVE_NODE_MAPPED));
+    receiverRoot.connectManyNodeList(javaUri(TOPIC_RECEIVE_MANY));
+    receiverRoot.connectNTA(javaUri(TOPIC_RECEIVE_NTA));
+
+    // send
+    senderRoot.connectSendToken(javaUri(TOPIC_SEND_TOKEN), writeCurrentValue);
+    senderRoot.connectSendNode(javaUri(TOPIC_SEND_NODE), writeCurrentValue);
+    senderRoot.connectSendManyNodeList(javaUri(TOPIC_SEND_MANY), writeCurrentValue);
+    senderRoot.connectSendNTA(javaUri(TOPIC_SEND_NTA), writeCurrentValue);
   }
 
   @Test
-  public void testCommunicateSendInitialValue() {
+  public void testCommunicateSendInitialValue() throws IOException {
     createModel();
+    setupReceiverAndConnect(true);
+    checker.put(TOPIC_SEND_TOKEN, "")
+            .put(TOPIC_SEND_NODE, "1")
+            .put(TOPIC_SEND_MANY, tuple())
+            .put(TOPIC_SEND_NTA, "1")
+    ;
+
+    communicateBoth();
+
+    handler.push(TOPIC_RECEIVE_TOKEN, ExposingASTNode.INSTANCE.stringToBytes("7"));
+    checker.put(TOPIC_RECEIVE_TOKEN, "7").check();
   }
 
   @Test
-  public void testCommunicateOnlyUpdatedValue() {
+  public void testCommunicateOnlyUpdatedValue() throws IOException {
     createModel();
+    setupReceiverAndConnect(false);
+
+    checker.put(TOPIC_SEND_TOKEN, (String) null)
+            .put(TOPIC_SEND_NODE, (String) null)
+            .put(TOPIC_SEND_MANY, (Tuple) null)
+            .put(TOPIC_SEND_NTA, (String) null)
+    ;
+
+    communicateBoth();
+  }
+
+  private void communicateBoth() {
+    checker.check();
+
+    senderRoot.setInput("2");
+    checker.put(TOPIC_SEND_NTA, "2").check();
+
+    senderRoot.getSendNode().setValue("3");
+    checker.put(TOPIC_SEND_NODE, "3").check();
+
+    senderRoot.setSendToken("test-4");
+    checker.put(TOPIC_SEND_TOKEN, "test-4").check();
+
+    senderRoot.addSendManyNode(createA("5"));
+    checker.put(TOPIC_SEND_MANY, tuple("5")).check();
+  }
+
+  private void checkA(String expectedValue, A actual, String alias) {
+    if (expectedValue == null) {
+      assertNull(actual, alias);
+    } else {
+      assertNotNull(actual, alias);
+      assertEquals(expectedValue, actual.getValue(), alias);
+    }
+  }
+
+  private void checkAList(Tuple expectedTuple, JastAddList<A> actual, String alias) {
+    if (expectedTuple == null) {
+      assertNull(actual, alias);
+      return;
+    }
+    List<Object> expected = expectedTuple.toList();
+    assertEquals(expected.size(), actual.getNumChild(), alias + ".size");
+    for (int i = 0, expectedSize = expected.size(); i < expectedSize; i++) {
+      String s = (String) expected.get(i);
+      checkA(s, actual.getChild(i), alias + "[" + i + "]");
+    }
   }
 
   @AfterEach
@@ -40,4 +168,49 @@ public class JavaTest {
     }
   }
 
+  @SuppressWarnings({"rawtypes" , "unchecked"})
+  static class ExposingASTNode extends ASTNode {
+    static ExposingASTNode INSTANCE = new ExposingASTNode();
+
+    public A bytesToA(byte[] bytes) {
+      try {
+        return _ragconnect__apply__TreeDefaultBytesToAMapping(bytes);
+      } catch (Exception e) {
+        return null;
+      }
+    }
+
+    public JastAddList<A> bytesToList(byte[] input) {
+      try {
+        return _ragconnect__apply__TreeDefaultBytesToJastAddListAListMapping(input);
+      } catch (Exception e) {
+        return null;
+      }
+    }
+
+    public byte[] aToBytes(A input) {
+      try {
+        return _ragconnect__apply__TreeDefaultAToBytesMapping(input);
+      } catch (Exception e) {
+        return null;
+      }
+    }
+
+    public byte[] listToBytes(JastAddList<A> input) {
+      try {
+        return _ragconnect__apply__TreeDefaultJastAddListToBytesMapping(input);
+      } catch (Exception e) {
+        return null;
+      }
+    }
+
+    public byte[] stringToBytes(String input) {
+      try {
+        return _ragconnect__apply__DefaultStringToBytesMapping(input);
+      } catch (Exception e) {
+        return null;
+      }
+    }
+  }
+
 }
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 3457ce0..13700ef 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
@@ -62,6 +62,10 @@ public class TestUtils {
     return "rest://localhost:" + port + "/" + path;
   }
 
+  public static String javaUri(String path) {
+    return "java://localhost/" + path;
+  }
+
   public static int getMqttDefaultPort() {
     return 1883;
   }
@@ -218,6 +222,7 @@ public class TestUtils {
     private final Map<String, ActualAndExpected<Tuple>> tupleValues = new HashMap<>();
     private final Map<String, ActualAndExpected<Integer>> intValues = new HashMap<>();
     private boolean needManualWait = true;
+    private boolean useManualWait = true;
 
     public TestChecker incNumberOfValues() {
       return addToNumberOfValues(1);
@@ -281,6 +286,12 @@ public class TestUtils {
       return this;
     }
 
+    public TestChecker disableManualWait() {
+      useManualWait = false;
+      needManualWait = false;
+      return this;
+    }
+
     public void check() {
       if (needManualWait) {
         try {
@@ -298,7 +309,7 @@ public class TestUtils {
           aae.check(name);
         }
       });
-      needManualWait = true;
+      needManualWait = useManualWait;
     }
   }
 
-- 
GitLab


From 2b726f1aae76a1d5033ca92257b95970761fac82 Mon Sep 17 00:00:00 2001
From: rschoene <rene.schoene@tu-dresden.de>
Date: Sun, 22 May 2022 17:43:53 +0200
Subject: [PATCH 4/7] working on EvaluationCounter

(not directly related to JavaHandler)

- add EvaluationCounter commandline arg
- counting receive/send, stages of mapping application
- make mappings non-static
---
 .../src/main/jastadd/Intermediate.jadd        |  8 ++
 .../src/main/jastadd/Mappings.jrag            |  4 +-
 .../src/main/jastadd/RagConnect.relast        |  1 +
 .../jastadd/ragconnect/compiler/Compiler.java |  9 +-
 .../src/main/resources/handleUri.mustache     |  6 +-
 .../resources/mappingApplication.mustache     | 15 +++
 .../main/resources/mappingDefinition.mustache |  2 +-
 .../src/main/resources/ragconnect.mustache    | 95 +++++++++++++++++++
 .../main/resources/receiveDefinition.mustache |  6 ++
 .../main/resources/sendDefinition.mustache    | 11 +++
 ragconnect.tests/build.gradle                 |  4 +-
 .../jastadd/ragconnect/tests/JavaTest.java    |  2 +
 .../tests/relation/RelationTest.java          |  1 +
 13 files changed, 154 insertions(+), 10 deletions(-)

diff --git a/ragconnect.base/src/main/jastadd/Intermediate.jadd b/ragconnect.base/src/main/jastadd/Intermediate.jadd
index 69ff5d8..f7a12a6 100644
--- a/ragconnect.base/src/main/jastadd/Intermediate.jadd
+++ b/ragconnect.base/src/main/jastadd/Intermediate.jadd
@@ -15,6 +15,10 @@ aspect SharedMustache {
 
   syn boolean RagConnect.configExperimentalJastAdd329() = getConfiguration().getExperimentalJastAdd329();
 
+  syn boolean RagConnect.configEvaluationCounter() = getConfiguration().getEvaluationCounter();
+
+  syn String RagConnect.evaluationCounterVariable() = internalRagConnectPrefix() + "evaluationCounter";
+
   syn String RagConnect.internalRagConnectPrefix() = "_ragconnect_";
 
   syn String RagConnect.logDebug() = logStatement("debug");
@@ -99,6 +103,10 @@ aspect MustacheHandler {
   // === RagConnect ===
   syn String RagConnect.closeMethodName() = "ragconnectCloseConnections";
 
+  syn String RagConnect.evaluationCounterInnerClass() = internalRagConnectPrefix() + "Counter";
+
+  syn String RagConnect.evaluationCounterSummaryMethodName() = "ragconnectEvaluationCounterSummary";
+
   syn boolean RagConnect.hasRootTypeComponents() = !rootTypeComponents().isEmpty();
 
   syn List<TypeComponent> RagConnect.rootTypeComponents() {
diff --git a/ragconnect.base/src/main/jastadd/Mappings.jrag b/ragconnect.base/src/main/jastadd/Mappings.jrag
index 9fd55aa..e92fc4f 100644
--- a/ragconnect.base/src/main/jastadd/Mappings.jrag
+++ b/ragconnect.base/src/main/jastadd/Mappings.jrag
@@ -243,7 +243,7 @@ aspect Mappings {
           return getEndpointTarget().isTypeEndpointTarget() && typeIsList() && !getIndexBasedListAccess() ? ragconnect().defaultBytesToListMapping(typeDecl.getName()) : ragconnect().defaultBytesToTreeMapping(typeDecl.getName());
         } catch (Exception ignore) {
         }
-        System.err.println("Could not find suitable default mapping for " + targetTypeName() + " on " + this);
+        System.err.println("Could not find suitable default receive mapping for " + targetTypeName() + " on " + this);
         return null;
     }
   }
@@ -287,7 +287,7 @@ aspect Mappings {
         } catch (Exception ignore) {
           // exception should be logged to debug/fine
         }
-        System.err.println("Could not find suitable default mapping for " + targetTypeName() + " on " + this);
+        System.err.println("Could not find suitable default send mapping for " + targetTypeName() + " on " + this);
         return null;
     }
   }
diff --git a/ragconnect.base/src/main/jastadd/RagConnect.relast b/ragconnect.base/src/main/jastadd/RagConnect.relast
index cf12d73..df50e3d 100644
--- a/ragconnect.base/src/main/jastadd/RagConnect.relast
+++ b/ragconnect.base/src/main/jastadd/RagConnect.relast
@@ -40,5 +40,6 @@ Configuration ::=
 <JastAddOpt:String>
 <IncrementalOptionActive:boolean>
 <CacheAllOptionActive:boolean>
+<EvaluationCounter:boolean>
 <ExperimentalJastAdd329:boolean>;
 rel Configuration.RootNode -> TypeDecl ;
diff --git a/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/Compiler.java b/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/Compiler.java
index e255b3b..772cebb 100644
--- a/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/Compiler.java
+++ b/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/Compiler.java
@@ -31,6 +31,7 @@ public class Compiler extends AbstractCompiler {
   private BooleanOption optionLogIncremental;
   private ValueOption optionLogTarget;
   private BooleanOption optionExperimentalJastAdd329;
+  private BooleanOption optionEvaluationCounter;
 
   private static final String OPTION_LOGGING_TARGET_CONSOLE = "console";
   private static final String OPTION_LOGGING_TARGET_SLF4J = "slf4j";
@@ -206,6 +207,9 @@ public class Compiler extends AbstractCompiler {
     optionExperimentalJastAdd329 = addOption(
         new BooleanOption("experimental-jastadd-329", "Use trace events INC_FLUSH_START and INC_FLUSH_END (JastAdd issue #329).")
             .defaultValue(false));
+    optionEvaluationCounter = addOption(
+            new BooleanOption("evaluationCounter", "Enable counters for evaluation.")
+                    .defaultValue(false));
   }
 
   private RagConnect parseProgram(Collection<String> files) throws CompilerException {
@@ -301,13 +305,14 @@ public class Compiler extends AbstractCompiler {
    * Set all configuration values.
    * @param ragConnect the RagConnect instance to set configuration values
    */
-  private void setConfiguration(RagConnect ragConnect) {
+  private void setConfiguration(RagConnect ragConnect) throws CompilerException {
     ragConnect.setConfiguration(new Configuration());
     ragConnect.getConfiguration().setLoggingEnabledForReads(optionLogReads.value());
     ragConnect.getConfiguration().setLoggingEnabledForWrites(optionLogWrites.value());
     ragConnect.getConfiguration().setLoggingEnabledForIncremental(optionLogIncremental.value());
     ragConnect.getConfiguration().setLoggingTarget(optionLogTarget.value());
     ragConnect.getConfiguration().setExperimentalJastAdd329(optionExperimentalJastAdd329.value());
+    ragConnect.getConfiguration().setEvaluationCounter(optionEvaluationCounter.value());
 
     // reuse "--incremental" and "--trace=flush" options of JastAdd
     boolean incrementalOptionActive = this.getConfiguration().incremental() && this.getConfiguration().traceFlush();
@@ -328,7 +333,7 @@ public class Compiler extends AbstractCompiler {
       rootNode = ragConnect.getProgram().resolveTypeDecl(optionRootNode.value());
     } catch (RuntimeException re) {
       // root node was not found
-      throw new RuntimeException("Could not resolve root node '" + optionRootNode.value() + "'!", re);
+      throw new CompilerException("Could not resolve root node '" + optionRootNode.value() + "'!", re);
     }
     ragConnect.getConfiguration().setRootNode(rootNode);
 
diff --git a/ragconnect.base/src/main/resources/handleUri.mustache b/ragconnect.base/src/main/resources/handleUri.mustache
index 2312cd6..efb4aac 100644
--- a/ragconnect.base/src/main/resources/handleUri.mustache
+++ b/ragconnect.base/src/main/resources/handleUri.mustache
@@ -10,14 +10,14 @@ try {
   return false;
 }
 if (scheme == null || scheme.isBlank()) {
-  {{logError}}("Missing or empty scheme in " + uri);
+  {{logError}}("Missing or empty scheme in {{log_}}", uri);
   return false;
 }
 if (host == null || host.isBlank()) {
-  {{logError}}("Missing or empty host in " + uri);
+  {{logError}}("Missing or empty host in {{log_}}", uri);
   return false;
 }
 if (path == null || path.isBlank()) {
-  {{logError}}("Missing or empty path in " + uri);
+  {{logError}}("Missing or empty path in {{log_}}", uri);
   return false;
 }
diff --git a/ragconnect.base/src/main/resources/mappingApplication.mustache b/ragconnect.base/src/main/resources/mappingApplication.mustache
index 482d547..efd17af 100644
--- a/ragconnect.base/src/main/resources/mappingApplication.mustache
+++ b/ragconnect.base/src/main/resources/mappingApplication.mustache
@@ -1,6 +1,12 @@
+{{#configEvaluationCounter}}
+  {{evaluationCounterVariable}}.incrementCall("{{parentTypeName}}", "{{entityName}}");
+{{/configEvaluationCounter}}
 {{#Send}}
 {{^PrimitiveType}}
 if ({{firstInputVarName}} == null) {
+  {{#configEvaluationCounter}}
+    {{evaluationCounterVariable}}.incrementFirstNull("{{parentTypeName}}", "{{entityName}}");
+  {{/configEvaluationCounter}}
   {{preemptiveReturn}}
 }
 {{/PrimitiveType}}
@@ -12,13 +18,22 @@ try {
   {{/innerMappingDefinitions}}
 } catch (RagConnectRejectMappingException e) {
   // do not print message in case of rejection
+  {{#configEvaluationCounter}}
+    {{evaluationCounterVariable}}.incrementReject("{{parentTypeName}}", "{{entityName}}");
+  {{/configEvaluationCounter}}
   {{preemptiveReturn}}
 } catch (Exception e) {
   e.printStackTrace();
+  {{#configEvaluationCounter}}
+    {{evaluationCounterVariable}}.incrementException("{{parentTypeName}}", "{{entityName}}");
+  {{/configEvaluationCounter}}
   {{preemptiveReturn}}
 }
 {{^AlwaysApply}}
 if ({{{condition}}}) {
+  {{#configEvaluationCounter}}
+    {{evaluationCounterVariable}}.incrementSkip("{{parentTypeName}}", "{{entityName}}");
+  {{/configEvaluationCounter}}
   {{preemptiveReturn}}
 }
 {{/AlwaysApply}}
diff --git a/ragconnect.base/src/main/resources/mappingDefinition.mustache b/ragconnect.base/src/main/resources/mappingDefinition.mustache
index 5be93e4..b6a035e 100644
--- a/ragconnect.base/src/main/resources/mappingDefinition.mustache
+++ b/ragconnect.base/src/main/resources/mappingDefinition.mustache
@@ -1,3 +1,3 @@
-protected static {{{toType}}} ASTNode.{{methodName}}({{{fromType}}} {{FromVariableName}}) throws Exception {
+protected {{{toType}}} ASTNode.{{methodName}}({{{fromType}}} {{FromVariableName}}) throws Exception {
   {{{Content}}}
 }
diff --git a/ragconnect.base/src/main/resources/ragconnect.mustache b/ragconnect.base/src/main/resources/ragconnect.mustache
index ac7b906..7e054a4 100644
--- a/ragconnect.base/src/main/resources/ragconnect.mustache
+++ b/ragconnect.base/src/main/resources/ragconnect.mustache
@@ -285,3 +285,98 @@ aspect RagConnectObserver {
   }
 }
 {{/configIncrementalOptionActive}}
+
+aspect EvaluationCounter {
+  public String ASTNode.{{evaluationCounterSummaryMethodName}}() {
+  {{#configEvaluationCounter}}
+    return {{evaluationCounterVariable}}.summary();
+  {{/configEvaluationCounter}}
+  {{^configEvaluationCounter}}
+    String message = "Option --evaluationCounter was not set. No Summary available";
+    {{logWarn}}(message);
+    return message;
+  {{/configEvaluationCounter}}
+  }
+{{#configEvaluationCounter}}
+  static EvaluationCounter ASTNode.{{evaluationCounterVariable}} = new EvaluationCounter();
+
+  public class EvaluationCounter {
+    private java.util.Map<String, java.util.Map<String, {{evaluationCounterInnerClass}}>> counters = new java.util.HashMap<>();
+    private final java.util.function.Function<? super String, ? extends java.util.Map<String, {{evaluationCounterInnerClass}}>> parentAbsent = key -> {
+      return new java.util.HashMap<>();
+    };
+    private final java.util.function.Function<? super String, ? extends {{evaluationCounterInnerClass}}> entityAbsent = key -> {
+      return new {{evaluationCounterInnerClass}}();
+    };
+
+    public void incrementReceive(String parentTypeName, String entityName) {
+      getCounter(parentTypeName, entityName).receive += 1;
+    }
+
+    public void incrementSend(String parentTypeName, String entityName) {
+      getCounter(parentTypeName, entityName).send += 1;
+    }
+
+    public void incrementCall(String parentTypeName, String entityName) {
+      getCounter(parentTypeName, entityName).call += 1;
+    }
+
+    public void incrementFirstNull(String parentTypeName, String entityName) {
+      getCounter(parentTypeName, entityName).firstNull += 1;
+    }
+
+    public void incrementSkip(String parentTypeName, String entityName) {
+      getCounter(parentTypeName, entityName).skip += 1;
+    }
+
+    public void incrementException(String parentTypeName, String entityName) {
+      getCounter(parentTypeName, entityName).exception += 1;
+    }
+
+    public void incrementReject(String parentTypeName, String entityName) {
+      getCounter(parentTypeName, entityName).reject += 1;
+    }
+
+    public String summary() {
+      StringBuilder sb = new StringBuilder();
+      // header
+      sb.append("parentTypeName,entityName,receive,send,call,firstNull,skip,exception,reject").append("\n");
+      // values
+      java.util.Set<String> sortedParentTypes = new java.util.TreeSet<>(counters.keySet());
+      for (String parentType : sortedParentTypes) {
+        java.util.Set<String> sortedEntityNames = new java.util.TreeSet<>(counters.get(parentType).keySet());
+        for (String entityName : sortedEntityNames) {
+          {{evaluationCounterInnerClass}} count = getCounter(parentType, entityName);
+          java.util.StringJoiner sj = new java.util.StringJoiner(",", "", "\n");
+          sj.add(parentType)
+             .add(entityName)
+             .add(Integer.toString(count.receive))
+             .add(Integer.toString(count.send))
+             .add(Integer.toString(count.call))
+             .add(Integer.toString(count.firstNull))
+             .add(Integer.toString(count.skip))
+             .add(Integer.toString(count.exception))
+             .add(Integer.toString(count.reject))
+          ;
+          sb.append(sj);
+        }
+      }
+      return sb.toString();
+    }
+
+    private {{evaluationCounterInnerClass}} getCounter(String parentTypeName, String entityName) {
+      return counters.computeIfAbsent(parentTypeName, parentAbsent).computeIfAbsent(entityName, entityAbsent);
+    }
+  }
+
+  class {{evaluationCounterInnerClass}} {
+    int receive = 0;
+    int send = 0;
+    int call = 0;
+    int firstNull = 0;
+    int skip = 0;
+    int exception = 0;
+    int reject = 0;
+  }
+{{/configEvaluationCounter}}
+}
diff --git a/ragconnect.base/src/main/resources/receiveDefinition.mustache b/ragconnect.base/src/main/resources/receiveDefinition.mustache
index 28a9bb5..81b0ecb 100644
--- a/ragconnect.base/src/main/resources/receiveDefinition.mustache
+++ b/ragconnect.base/src/main/resources/receiveDefinition.mustache
@@ -25,6 +25,9 @@ private int {{parentTypeName}}.{{resolveInListMethodName}}(String topic) {
  */
 public boolean {{parentTypeName}}.{{connectMethodName}}(String {{connectParameterName}}{{#typeIsList}}{{#IndexBasedListAccess}}{{^WithAdd}}, int index{{/WithAdd}}{{/IndexBasedListAccess}}{{/typeIsList}}) throws java.io.IOException {
   java.util.function.BiConsumer<String, byte[]> consumer = (topic, message) -> {
+    {{#configEvaluationCounter}}
+      {{evaluationCounterVariable}}.incrementReceive("{{parentTypeName}}", "{{entityName}}");
+    {{/configEvaluationCounter}}
     {{> mappingApplication}}
 {{#configLoggingEnabledForReads}}
     {{logDebug}}("[Receive] {{log_}} -> {{entityName}} = {{log_}}", {{connectParameterName}}, {{lastResult}});
@@ -70,6 +73,9 @@ public boolean {{parentTypeName}}.{{connectMethodName}}(String {{connectParamete
 */
 public boolean {{parentTypeName}}.{{connectMethodName}}(String {{connectParameterName}}) throws java.io.IOException {
   java.util.function.BiConsumer<String, byte[]> consumer = (topic, message) -> {
+    {{#configEvaluationCounter}}
+      {{evaluationCounterVariable}}.incrementReceive("{{parentTypeName}}", "{{entityName}}");
+    {{/configEvaluationCounter}}
     int index = {{resolveInListMethodName}}(topic);
     {{> mappingApplication}}
 {{#configLoggingEnabledForReads}}
diff --git a/ragconnect.base/src/main/resources/sendDefinition.mustache b/ragconnect.base/src/main/resources/sendDefinition.mustache
index 0829973..abcbdff 100644
--- a/ragconnect.base/src/main/resources/sendDefinition.mustache
+++ b/ragconnect.base/src/main/resources/sendDefinition.mustache
@@ -134,14 +134,25 @@ protected boolean {{parentTypeName}}.{{updateMethodName}}({{#IndexBasedListAcces
   {{> mappingApplication}}
   {{lastValueSetter}}({{#IndexBasedListAccess}}index, {{/IndexBasedListAccess}}{{lastResult}});
   // normally we would return true here. unless no connect method was called so far to initialize {{senderName}} yet
+  {{#configEvaluationCounter}}
+    if ({{senderName}} == null) {
+      {{evaluationCounterVariable}}.incrementSkip("{{parentTypeName}}", "{{entityName}}");
+    }
+  {{/configEvaluationCounter}}
   return {{senderName}} != null;
 }
 
 protected void {{parentTypeName}}.{{writeMethodName}}({{#IndexBasedListAccess}}int index{{/IndexBasedListAccess}}) {
+  {{#configEvaluationCounter}}
+    {{evaluationCounterVariable}}.incrementSend("{{parentTypeName}}", "{{entityName}}");
+  {{/configEvaluationCounter}}
   {{senderName}}.run({{#IndexBasedListAccess}}index{{/IndexBasedListAccess}});
 }
 
 protected void {{parentTypeName}}.{{writeMethodName}}({{#IndexBasedListAccess}}int index, {{/IndexBasedListAccess}}RagConnectToken token) {
+  {{#configEvaluationCounter}}
+    {{evaluationCounterVariable}}.incrementSend("{{parentTypeName}}", "{{entityName}}");
+  {{/configEvaluationCounter}}
   {{senderName}}.run({{#IndexBasedListAccess}}index, {{/IndexBasedListAccess}}token);
 }
 
diff --git a/ragconnect.tests/build.gradle b/ragconnect.tests/build.gradle
index 719b55d..42c2cf6 100644
--- a/ragconnect.tests/build.gradle
+++ b/ragconnect.tests/build.gradle
@@ -651,7 +651,7 @@ task compileRelationIncremental(type: RagConnectTest) {
         inputFiles = [file('src/test/01-input/relation/Test.relast'),
                       file('src/test/01-input/relation/Test.connect')]
         rootNode = 'Root'
-        extraOptions = defaultRagConnectOptionsAnd(['--experimental-jastadd-329'])
+        extraOptions = defaultRagConnectOptionsAnd(['--experimental-jastadd-329', '--evaluationCounter'])
     }
     relast {
         useJastAddNames = true
@@ -677,7 +677,7 @@ task compileJavaIncremental(type: RagConnectTest) {
         logWrites = true
         logIncremental = true
         protocols = ['java']
-        extraOptions = defaultRagConnectOptionsAnd(['--experimental-jastadd-329'])
+        extraOptions = defaultRagConnectOptionsAnd(['--experimental-jastadd-329', '--evaluationCounter'])
     }
     relast {
         useJastAddNames = true
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/JavaTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/JavaTest.java
index 5d6e492..fa4319c 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/JavaTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/JavaTest.java
@@ -106,6 +106,8 @@ public class JavaTest {
 
     handler.push(TOPIC_RECEIVE_TOKEN, ExposingASTNode.INSTANCE.stringToBytes("7"));
     checker.put(TOPIC_RECEIVE_TOKEN, "7").check();
+
+    System.out.println(model.ragconnectEvaluationCounterSummary());
   }
 
   @Test
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/relation/RelationTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/relation/RelationTest.java
index d9c4979..a245387 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/relation/RelationTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/relation/RelationTest.java
@@ -616,6 +616,7 @@ public class RelationTest extends AbstractMqttTest {
     biB(3).getInner().setInnerValue("inner-bi-b3");
     checker.check();
 
+    System.out.println(model.ragconnectEvaluationCounterSummary());
   }
 
   private void assertNullOrA(String expectedValue, A actual, String alias) {
-- 
GitLab


From 31979651befe350b16a1091325690911373a1b35 Mon Sep 17 00:00:00 2001
From: rschoene <rene.schoene@tu-dresden.de>
Date: Sun, 22 May 2022 17:54:52 +0200
Subject: [PATCH 5/7] fix tests after making mappings non-static

---
 .../jastadd/ragconnect/tests/JavaTest.java    | 12 ++-
 .../jastadd/ragconnect/tests/TestUtils.java   | 99 ++++++++++---------
 2 files changed, 58 insertions(+), 53 deletions(-)

diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/JavaTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/JavaTest.java
index fa4319c..7655c07 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/JavaTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/JavaTest.java
@@ -103,11 +103,6 @@ public class JavaTest {
     ;
 
     communicateBoth();
-
-    handler.push(TOPIC_RECEIVE_TOKEN, ExposingASTNode.INSTANCE.stringToBytes("7"));
-    checker.put(TOPIC_RECEIVE_TOKEN, "7").check();
-
-    System.out.println(model.ragconnectEvaluationCounterSummary());
   }
 
   @Test
@@ -138,6 +133,13 @@ public class JavaTest {
 
     senderRoot.addSendManyNode(createA("5"));
     checker.put(TOPIC_SEND_MANY, tuple("5")).check();
+
+    handler.push(TOPIC_RECEIVE_TOKEN, ExposingASTNode.INSTANCE.stringToBytes("7"));
+    checker.put(TOPIC_RECEIVE_TOKEN, "7").check();
+
+    // TODO check other receive ports
+
+    System.out.println(model.ragconnectEvaluationCounterSummary());
   }
 
   private void checkA(String expectedValue, A actual, String alias) {
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 13700ef..91bbd40 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
@@ -335,56 +335,56 @@ public class TestUtils {
   @SuppressWarnings({"unused", "rawtypes"})
   public static class DefaultMappings {
     static class ReadNode extends defaultOnlyRead.ast.ASTNode {
-      public static boolean DefaultBytesToBooleanMapping(byte[] input) throws Exception {
-        return defaultOnlyRead.ast.ASTNode._ragconnect__apply__DefaultBytesToBooleanMapping(input);
+      public boolean DefaultBytesToBooleanMapping(byte[] input) throws Exception {
+        return _ragconnect__apply__DefaultBytesToBooleanMapping(input);
       }
-      public static int DefaultBytesToIntMapping(byte[] input) throws Exception {
-        return defaultOnlyRead.ast.ASTNode._ragconnect__apply__DefaultBytesToIntMapping(input);
+      public int DefaultBytesToIntMapping(byte[] input) throws Exception {
+        return _ragconnect__apply__DefaultBytesToIntMapping(input);
       }
-      public static short DefaultBytesToShortMapping(byte[] input) throws Exception {
-        return defaultOnlyRead.ast.ASTNode._ragconnect__apply__DefaultBytesToShortMapping(input);
+      public short DefaultBytesToShortMapping(byte[] input) throws Exception {
+        return _ragconnect__apply__DefaultBytesToShortMapping(input);
       }
-      public static long DefaultBytesToLongMapping(byte[] input) throws Exception {
-        return defaultOnlyRead.ast.ASTNode._ragconnect__apply__DefaultBytesToLongMapping(input);
+      public long DefaultBytesToLongMapping(byte[] input) throws Exception {
+        return _ragconnect__apply__DefaultBytesToLongMapping(input);
       }
-      public static float DefaultBytesToFloatMapping(byte[] input) throws Exception {
-        return defaultOnlyRead.ast.ASTNode._ragconnect__apply__DefaultBytesToFloatMapping(input);
+      public float DefaultBytesToFloatMapping(byte[] input) throws Exception {
+        return _ragconnect__apply__DefaultBytesToFloatMapping(input);
       }
-      public static double DefaultBytesToDoubleMapping(byte[] input) throws Exception {
-        return defaultOnlyRead.ast.ASTNode._ragconnect__apply__DefaultBytesToDoubleMapping(input);
+      public double DefaultBytesToDoubleMapping(byte[] input) throws Exception {
+        return _ragconnect__apply__DefaultBytesToDoubleMapping(input);
       }
-      public static char DefaultBytesToCharMapping(byte[] input) throws Exception {
-        return defaultOnlyRead.ast.ASTNode._ragconnect__apply__DefaultBytesToCharMapping(input);
+      public char DefaultBytesToCharMapping(byte[] input) throws Exception {
+        return _ragconnect__apply__DefaultBytesToCharMapping(input);
       }
-      public static String DefaultBytesToStringMapping(byte[] input) throws Exception {
-        return defaultOnlyRead.ast.ASTNode._ragconnect__apply__DefaultBytesToStringMapping(input);
+      public String DefaultBytesToStringMapping(byte[] input) throws Exception {
+        return _ragconnect__apply__DefaultBytesToStringMapping(input);
       }
     }
 
     static class WriteNode extends defaultOnlyWrite.ast.ASTNode {
-      public static byte[] DefaultBooleanToBytesMapping(boolean input) throws Exception {
-        return defaultOnlyWrite.ast.ASTNode._ragconnect__apply__DefaultBooleanToBytesMapping(input);
+      public byte[] DefaultBooleanToBytesMapping(boolean input) throws Exception {
+        return _ragconnect__apply__DefaultBooleanToBytesMapping(input);
       }
-      public static byte[] DefaultIntToBytesMapping(int input) throws Exception {
-        return defaultOnlyWrite.ast.ASTNode._ragconnect__apply__DefaultIntToBytesMapping(input);
+      public byte[] DefaultIntToBytesMapping(int input) throws Exception {
+        return _ragconnect__apply__DefaultIntToBytesMapping(input);
       }
-      public static byte[] DefaultShortToBytesMapping(short input) throws Exception {
-        return defaultOnlyWrite.ast.ASTNode._ragconnect__apply__DefaultShortToBytesMapping(input);
+      public byte[] DefaultShortToBytesMapping(short input) throws Exception {
+        return _ragconnect__apply__DefaultShortToBytesMapping(input);
       }
-      public static byte[] DefaultLongToBytesMapping(long input) throws Exception {
-        return defaultOnlyWrite.ast.ASTNode._ragconnect__apply__DefaultLongToBytesMapping(input);
+      public byte[] DefaultLongToBytesMapping(long input) throws Exception {
+        return _ragconnect__apply__DefaultLongToBytesMapping(input);
       }
-      public static byte[] DefaultFloatToBytesMapping(float input) throws Exception {
-        return defaultOnlyWrite.ast.ASTNode._ragconnect__apply__DefaultFloatToBytesMapping(input);
+      public byte[] DefaultFloatToBytesMapping(float input) throws Exception {
+        return _ragconnect__apply__DefaultFloatToBytesMapping(input);
       }
-      public static byte[] DefaultDoubleToBytesMapping(double input) throws Exception {
-        return defaultOnlyWrite.ast.ASTNode._ragconnect__apply__DefaultDoubleToBytesMapping(input);
+      public byte[] DefaultDoubleToBytesMapping(double input) throws Exception {
+        return _ragconnect__apply__DefaultDoubleToBytesMapping(input);
       }
-      public static byte[] DefaultCharToBytesMapping(char input) throws Exception {
-        return defaultOnlyWrite.ast.ASTNode._ragconnect__apply__DefaultCharToBytesMapping(input);
+      public byte[] DefaultCharToBytesMapping(char input) throws Exception {
+        return _ragconnect__apply__DefaultCharToBytesMapping(input);
       }
-      public static byte[] DefaultStringToBytesMapping(String input) throws Exception {
-        return defaultOnlyWrite.ast.ASTNode._ragconnect__apply__DefaultStringToBytesMapping(input);
+      public byte[] DefaultStringToBytesMapping(String input) throws Exception {
+        return _ragconnect__apply__DefaultStringToBytesMapping(input);
       }
     }
     @FunctionalInterface
@@ -392,9 +392,12 @@ public class TestUtils {
       void accept(JsonGenerator g, String fieldName) throws E;
     }
 
+    static ReadNode readNode = new ReadNode();
+    static WriteNode writeNode = new WriteNode();
+
     public static boolean BytesToBool(byte[] input) {
       try {
-        return ReadNode.DefaultBytesToBooleanMapping(input);
+        return readNode.DefaultBytesToBooleanMapping(input);
       } catch (Exception e) {
         e.printStackTrace();
         return false;
@@ -402,7 +405,7 @@ public class TestUtils {
     }
     public static int BytesToInt(byte[] input) {
       try {
-        return ReadNode.DefaultBytesToIntMapping(input);
+        return readNode.DefaultBytesToIntMapping(input);
       } catch (Exception e) {
         e.printStackTrace();
         return 0;
@@ -410,7 +413,7 @@ public class TestUtils {
     }
     public static short BytesToShort(byte[] input) {
       try {
-        return ReadNode.DefaultBytesToShortMapping(input);
+        return readNode.DefaultBytesToShortMapping(input);
       } catch (Exception e) {
         e.printStackTrace();
         return 0;
@@ -418,7 +421,7 @@ public class TestUtils {
     }
     public static long BytesToLong(byte[] input) {
       try {
-        return ReadNode.DefaultBytesToLongMapping(input);
+        return readNode.DefaultBytesToLongMapping(input);
       } catch (Exception e) {
         e.printStackTrace();
         return 0;
@@ -426,7 +429,7 @@ public class TestUtils {
     }
     public static float BytesToFloat(byte[] input) {
       try {
-        return ReadNode.DefaultBytesToFloatMapping(input);
+        return readNode.DefaultBytesToFloatMapping(input);
       } catch (Exception e) {
         e.printStackTrace();
         return 0;
@@ -434,7 +437,7 @@ public class TestUtils {
     }
     public static double BytesToDouble(byte[] input) {
       try {
-        return ReadNode.DefaultBytesToDoubleMapping(input);
+        return readNode.DefaultBytesToDoubleMapping(input);
       } catch (Exception e) {
         e.printStackTrace();
         return 0;
@@ -442,7 +445,7 @@ public class TestUtils {
     }
     public static char BytesToChar(byte[] input) {
       try {
-        return ReadNode.DefaultBytesToCharMapping(input);
+        return readNode.DefaultBytesToCharMapping(input);
       } catch (Exception e) {
         e.printStackTrace();
         return 0;
@@ -450,7 +453,7 @@ public class TestUtils {
     }
     public static String BytesToString(byte[] input) {
       try {
-        return ReadNode.DefaultBytesToStringMapping(input);
+        return readNode.DefaultBytesToStringMapping(input);
       } catch (Exception e) {
         e.printStackTrace();
         return null;
@@ -458,7 +461,7 @@ public class TestUtils {
     }
     public static byte[] BoolToBytes(boolean input) {
       try {
-        return WriteNode.DefaultBooleanToBytesMapping(input);
+        return writeNode.DefaultBooleanToBytesMapping(input);
       } catch (Exception e) {
         e.printStackTrace();
         return null;
@@ -466,7 +469,7 @@ public class TestUtils {
     }
     public static byte[] IntToBytes(int input) {
       try {
-        return WriteNode.DefaultIntToBytesMapping(input);
+        return writeNode.DefaultIntToBytesMapping(input);
       } catch (Exception e) {
         e.printStackTrace();
         return null;
@@ -474,7 +477,7 @@ public class TestUtils {
     }
     public static byte[] ShortToBytes(short input) {
       try {
-        return WriteNode.DefaultShortToBytesMapping(input);
+        return writeNode.DefaultShortToBytesMapping(input);
       } catch (Exception e) {
         e.printStackTrace();
         return null;
@@ -482,7 +485,7 @@ public class TestUtils {
     }
     public static byte[] LongToBytes(long input) {
       try {
-        return WriteNode.DefaultLongToBytesMapping(input);
+        return writeNode.DefaultLongToBytesMapping(input);
       } catch (Exception e) {
         e.printStackTrace();
         return null;
@@ -490,7 +493,7 @@ public class TestUtils {
     }
     public static byte[] FloatToBytes(float input) {
       try {
-        return WriteNode.DefaultFloatToBytesMapping(input);
+        return writeNode.DefaultFloatToBytesMapping(input);
       } catch (Exception e) {
         e.printStackTrace();
         return null;
@@ -498,7 +501,7 @@ public class TestUtils {
     }
     public static byte[] DoubleToBytes(double input) {
       try {
-        return WriteNode.DefaultDoubleToBytesMapping(input);
+        return writeNode.DefaultDoubleToBytesMapping(input);
       } catch (Exception e) {
         e.printStackTrace();
         return null;
@@ -506,7 +509,7 @@ public class TestUtils {
     }
     public static byte[] CharToBytes(char input) {
       try {
-        return WriteNode.DefaultCharToBytesMapping(input);
+        return writeNode.DefaultCharToBytesMapping(input);
       } catch (Exception e) {
         e.printStackTrace();
         return null;
@@ -514,7 +517,7 @@ public class TestUtils {
     }
     public static byte[] StringToBytes(String input) {
       try {
-        return WriteNode.DefaultStringToBytesMapping(input);
+        return writeNode.DefaultStringToBytesMapping(input);
       } catch (Exception e) {
         e.printStackTrace();
         return null;
-- 
GitLab


From bd4174314121238c46337130d3d73b0bcc029dca Mon Sep 17 00:00:00 2001
From: rschoene <rene.schoene@tu-dresden.de>
Date: Mon, 30 May 2022 16:15:47 +0200
Subject: [PATCH 6/7] Finalize tests for initial JavaHandler

- moved methods "registerConsumer" and "push" up to root node (prefixed)
- mark Java handler as experimental
- update documentation
- begin with changelog for 1.0.0
---
 pages/docs/changelog.md                       | 15 +++
 pages/docs/compiler.md                        | 24 +++--
 pages/docs/dsl.md                             | 24 ++---
 pages/docs/using.md                           |  2 +-
 .../src/main/jastadd/Intermediate.jadd        |  4 +
 .../jastadd/ragconnect/compiler/Compiler.java |  4 +-
 .../src/main/resources/JavaHandler.mustache   | 11 +--
 .../src/main/resources/handler.mustache       |  6 ++
 ragconnect.tests/build.gradle                 | 11 ---
 .../src/test/01-input/java/Test.connect       |  4 +-
 .../jastadd/ragconnect/tests/JavaTest.java    | 50 +++++++---
 .../jastadd/ragconnect/tests/TestUtils.java   | 97 +++++++++++++------
 12 files changed, 172 insertions(+), 80 deletions(-)

diff --git a/pages/docs/changelog.md b/pages/docs/changelog.md
index fe3b87a..1f41f95 100644
--- a/pages/docs/changelog.md
+++ b/pages/docs/changelog.md
@@ -1,5 +1,20 @@
 # Changelog
 
+## 1.0.0 (dev)
+
+### Changes
+
+- Allow connection endpoints for relations ([#37](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/37)) and attributes ([#38](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/38))
+- Allow send connection endpoints non-NTA nonterminals ([#36](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/36))
+- Allow context-free context endpoints ([#34](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/34))
+- Experimental support for Java handler ([#52](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/52))
+- Make specification language more concise ([#33](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/33))
+
+### Development Changes
+
+- Make grammar(s) more concise  ([#40](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/40))
+- Enhance documentation, adding a DSL description
+
 ## 0.3.2
 
 - Allow connection endpoints for list nonterminals ([#21](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/21))
diff --git a/pages/docs/compiler.md b/pages/docs/compiler.md
index d60822b..80a9acb 100644
--- a/pages/docs/compiler.md
+++ b/pages/docs/compiler.md
@@ -6,16 +6,16 @@ Additional options are as follows.
 | Name | Required (Default) | Description |
 |---|---|---|
 | `--rootNode` | Yes | Root node in the base grammar. |
-| `--protocols` | No (`mqtt`) | Protocols to enable, currently available: `mqtt, rest`. |
+| `--protocols` | No (`mqtt`) | Protocols to enable, currently available: `java` (experimental), `mqtt`, `rest`. |
 | `--printYaml` | No (false) | Print out YAML instead of generating files. |
 | `--verbose` | No (false) | Print more messages while compiling. |
 | `--logReads` | No (false) | Enable logging for every received message. |
 | `--logWrites` | No (false) | Enable logging for every sent message. |
 | `--logIncremental` | No (false) | Enable logging for observer in incremental dependency tracking. |
 | `--logTarget` | No (`console`) | Logging target to use, currently available: `console, slf4j`. |
-| `--experimental-jastadd-329` | No (false) | Use trace events `INC_FLUSH_START` and `INC_FLUSH_END` ([JastAdd issue #329][jastadd-issue-329]), see [section about automatic dependency tracking](/using#dependency-tracking-automatically-derived). |
-| `--incremental` | No (false) | Enables incremental dependency tracking (if `trace` is also set appropriately). |
-| `--trace[=flush]` | No (false) | Enables incremental dependency tracking (if `incremental` is also set appropriately). |
+| `--experimental-jastadd-329` | No (false) | Use tracing events `INC_FLUSH_START` and `INC_FLUSH_END` ([JastAdd issue #329][jastadd-issue-329]), see [section about automatic dependency tracking](/using#dependency-tracking-automatically-derived). |
+| `--incremental` | No (false) | Enables incremental dependency tracking (if `tracing` is also set appropriately). |
+| `--tracing[=flush]` | No (false) | Enables incremental dependency tracking (if `incremental` is also set appropriately). |
 | `--version` | No (false) | Print version info and exit (reused JastAdd option) |
 | `--o` | No (`.`) | Output directory (reused JastAdd option) |
 
@@ -29,6 +29,16 @@ However, depending on the selected protocols and/or used features, additional de
 
 ## Communication protocol characteristics
 
+### Java
+
+- Protocol identifier: `java`
+- URI scheme: `java://<ignored-host>[:ignored-port]/<topic>`
+    - the value for host and port are always ignored, but are necessary to form a legal URI
+- No required runtime dependencies
+- Additional remarks:
+    - First leading slash not included in topic.
+    - Currently, the default mappings are applied, which requires a consumer to expect `byte[]` (instead of a more intuitive token or node value). This might change in future versions.
+
 ### MQTT
 
 - Protocol identifier: `mqtt`
@@ -59,15 +69,15 @@ However, depending on the selected protocols and/or used features, additional de
 
 ### Automatic dependency tracking
 
-- Condition: When passing `--incremental` and `--trace=flush` to RagConnect
+- Condition: When passing `--incremental` and `--tracing=flush` to RagConnect
 - Required runtime dependencies: _none_
 - Required options for RelAST compiler: _none_
 - Required options for JastAdd:
     - `--incremental`
-    - `--trace=flush`
+    - `--tracing=flush`
 - Remarks:
     - Other (additional) values passed to those two options must be equal (e.g., `--incremental=param` passed to RagConnect must be also passed to JastAdd)
-    - Other values besides `flush` can be added to `--trace`
+    - Other values besides `flush` can be added to `--tracing`
     - [Feature description](/using#dependency-tracking-automatically-derived)
 
 ### (Safer) Automatic dependency tracking
diff --git a/pages/docs/dsl.md b/pages/docs/dsl.md
index e8b6b2b..98ee13c 100644
--- a/pages/docs/dsl.md
+++ b/pages/docs/dsl.md
@@ -21,19 +21,19 @@ A breakdown of the parts of that syntax:
 - The first word (`send` or `receive`) defines the kind of endpoint - sending or receiving, respectively.
 - The optional `indexed` applies only for list children and lets the endpoint act on elements of that list.
   This only works for receiving endpoints, and is further changed by `with add`.
-  - A lonely `indexed` assigns each incoming "topic" to an index in a list.
-    This can be useful if multiple instances of this endpoint are connected, or the communication protocol supports wildcard topics.
-    For the former case, the connect method with an explicit index can be used, whereas the "normal" connect method without the index acts as a method for "wildcard-connect".
-  - Combining `indexed with add`, incoming data is required to be an element of the list, and will be appended to the list.
+    - A lonely `indexed` assigns each incoming "topic" to an index in a list.
+      This can be useful if multiple instances of this endpoint are connected, or the communication protocol supports wildcard topics.
+      For the former case, the connect method with an explicit index can be used, whereas the "normal" connect method without the index acts as a method for "wildcard-connect".
+    - Combining `indexed with add`, incoming data is required to be an element of the list, and will be appended to the list.
 - 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>["(<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 `(<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.
+    - 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 `(<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`.
@@ -49,7 +49,7 @@ Specifying such an endpoint has several consequences:
 
 **Example**:
 
-```java
+```
 // grammar
 Root ::= A SingleA:A [OptA:A] ListA:A* ;
 A ::= <Value> ;
@@ -60,13 +60,15 @@ receive Root.SingleA using MyMapping; // specialized endpoint
 ```
 
 Implied, additional connect specifications:
-```java
+
+```
 receive Root.A;
 receive Root.OptA;
 receive indexed Root.ListA;
 ```
 
 Application code:
+
 ```java
 A a = root.getOptA();
 // new method on A:
diff --git a/pages/docs/using.md b/pages/docs/using.md
index d4d2a30..87d9192 100644
--- a/pages/docs/using.md
+++ b/pages/docs/using.md
@@ -47,7 +47,7 @@ Otherwise, the deprecated manual dependencies must be used.
 
 ### Dependency tracking: Automatically derived
 
-To automatically track dependencies, the two additional parameters `--incremental` and `--trace=flush` have to be provided to both RagConnect and (in the later stage) JastAdd.
+To automatically track dependencies, the two additional parameters `--incremental` and `--tracing=flush` have to be provided to both RagConnect and (in the later stage) JastAdd.
 This will generate a different implementation of RagConnect relying on enabled incremental evaluation of JastAdd.
 The value for `incremental` has only been tested for `incremental=param`.
 The value for `trace` can include other values besides `flush`.
diff --git a/ragconnect.base/src/main/jastadd/Intermediate.jadd b/ragconnect.base/src/main/jastadd/Intermediate.jadd
index f7a12a6..95f9b86 100644
--- a/ragconnect.base/src/main/jastadd/Intermediate.jadd
+++ b/ragconnect.base/src/main/jastadd/Intermediate.jadd
@@ -126,6 +126,10 @@ aspect MustacheHandler {
 
   syn String Handler.fieldName() = ragconnect().internalRagConnectPrefix() + getUniqueName() + "Handler";
 
+  syn String Handler.pushMethodName() = "ragconnect" + capitalize(getUniqueName()) + "Push";
+
+  syn String Handler.registerConsumerMethodName() = "ragconnect" + capitalize(getUniqueName()) + "RegisterConsumer";
+
   syn String Handler.setupWaitUntilReadyMethodName() = "ragconnectSetup" + capitalize(getUniqueName()) + "WaitUntilReady";
 }
 
diff --git a/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/Compiler.java b/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/Compiler.java
index 772cebb..fdb5215 100644
--- a/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/Compiler.java
+++ b/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/Compiler.java
@@ -181,7 +181,7 @@ public class Compiler extends AbstractCompiler {
         new ValueOption("protocols", "Protocols to enable")
             .acceptMultipleValues(true)
             .addDefaultValue(OPTION_PROTOCOL_MQTT, "Enable MQTT")
-            .addAcceptedValue(OPTION_PROTOCOL_JAVA, "Enable Java")
+            .addAcceptedValue(OPTION_PROTOCOL_JAVA, "Enable Java (experimental)")
             .addAcceptedValue(OPTION_PROTOCOL_REST, "Enable REST")
     );
     optionPrintYaml = addOption(
@@ -314,7 +314,7 @@ public class Compiler extends AbstractCompiler {
     ragConnect.getConfiguration().setExperimentalJastAdd329(optionExperimentalJastAdd329.value());
     ragConnect.getConfiguration().setEvaluationCounter(optionEvaluationCounter.value());
 
-    // reuse "--incremental" and "--trace=flush" options of JastAdd
+    // reuse "--incremental" and "--tracing=flush" options of JastAdd
     boolean incrementalOptionActive = this.getConfiguration().incremental() && this.getConfiguration().traceFlush();
     ragConnect.getConfiguration().setIncrementalOptionActive(incrementalOptionActive);
     if (isVerbose()) {
diff --git a/ragconnect.base/src/main/resources/JavaHandler.mustache b/ragconnect.base/src/main/resources/JavaHandler.mustache
index 90caae3..d8d907b 100644
--- a/ragconnect.base/src/main/resources/JavaHandler.mustache
+++ b/ragconnect.base/src/main/resources/JavaHandler.mustache
@@ -23,14 +23,13 @@ public class JavaHandler {
     return path;
   }
 
-  public RagConnectToken registerConsumer(String path, java.util.function.Consumer<byte[]> consumer) {
+  RagConnectToken registerConsumer(String path, java.util.function.Consumer<byte[]> consumer) {
     RagConnectToken token = new RagConnectToken(java.net.URI.create("internal://host/" + path), null);
     registerCallback(token, (s, bytes) -> consumer.accept(bytes));
     return token;
   }
 
-
-  public boolean registerCallback(RagConnectToken connectToken, java.util.function.BiConsumer<String, byte[]> callback) {
+  boolean registerCallback(RagConnectToken connectToken, java.util.function.BiConsumer<String, byte[]> callback) {
     String path = extractPath(connectToken.uri);
     {{logInfo}}("[JAVA_HANDLER] Registering new callback for {{log_}}.", path);
 
@@ -46,7 +45,7 @@ public class JavaHandler {
     return true;
   }
 
-  public boolean unregisterCallback(RagConnectToken connectToken) {
+  boolean unregisterCallback(RagConnectToken connectToken) {
     String path = extractPath(connectToken.uri);
     java.util.function.BiConsumer<String, byte[]> callback = tokensForRemoval.get(connectToken);
     {{logInfo}}("[JAVA_HANDLER] Unregistering callback with uuid: on path: {{log_}}", path);
@@ -54,10 +53,10 @@ public class JavaHandler {
     return callbackList.get(path).remove(callback);
   }
 
-  public void close() {
+  void close() {
   }
 
-  public boolean push(String uriString, byte[] data) {
+  boolean push(String uriString, byte[] data) {
     String path = extractPath(java.net.URI.create(uriString));
     {{logDebug}}("[JAVA_HANDLER] Pushing a message for {{log_}}.", path);
     if (data == null) {
diff --git a/ragconnect.base/src/main/resources/handler.mustache b/ragconnect.base/src/main/resources/handler.mustache
index 2c4cf96..75bd14c 100644
--- a/ragconnect.base/src/main/resources/handler.mustache
+++ b/ragconnect.base/src/main/resources/handler.mustache
@@ -21,6 +21,12 @@ aspect RagConnectHandler {
 
 {{#javaHandler}}
   {{#InUse}}
+  public RagConnectToken {{rootNodeName}}.{{registerConsumerMethodName}}(String path, java.util.function.Consumer<byte[]> consumer) {
+    return {{fieldName}}.registerConsumer(path, consumer);
+  }
+  public boolean {{rootNodeName}}.{{pushMethodName}}(String uriString, byte[] data) {
+    return {{fieldName}}.push(uriString, data);
+  }
   {{> JavaHandler}}
   {{/InUse}}
 {{/javaHandler}}
diff --git a/ragconnect.tests/build.gradle b/ragconnect.tests/build.gradle
index 42c2cf6..4da4ee9 100644
--- a/ragconnect.tests/build.gradle
+++ b/ragconnect.tests/build.gradle
@@ -673,9 +673,6 @@ task compileJavaIncremental(type: RagConnectTest) {
         inputFiles = [file('src/test/01-input/java/Test.relast'),
                       file('src/test/01-input/java/Test.connect')]
         rootNode = 'Root'
-        logReads = true
-        logWrites = true
-        logIncremental = true
         protocols = ['java']
         extraOptions = defaultRagConnectOptionsAnd(['--experimental-jastadd-329', '--evaluationCounter'])
     }
@@ -694,14 +691,6 @@ task compileJavaIncremental(type: RagConnectTest) {
 
 // --- Task order ---
 classes.dependsOn(':ragconnect.base:jar')
-// TODO remove special handling of compileJavaIncremental once finished
-compileJavaIncremental {
-    doFirst {
-        fileTree(dir: 'src/test/java-gen/javaInc/ast/', exclude: '.gitignore')
-    }
-}
-//compileJavaIncremental.outputs.upToDateWhen { false }
-compileJavaIncremental.dependsOn(':ragconnect.base:assemble')
 
 // --- Misc ---
 static ArrayList<String> defaultRagConnectOptionsAnd(ArrayList<String> options = []) {
diff --git a/ragconnect.tests/src/test/01-input/java/Test.connect b/ragconnect.tests/src/test/01-input/java/Test.connect
index d24dde5..d516cfe 100644
--- a/ragconnect.tests/src/test/01-input/java/Test.connect
+++ b/ragconnect.tests/src/test/01-input/java/Test.connect
@@ -5,9 +5,9 @@ send SenderRoot.SendNTA ;
 
 AddSuffix maps A a to A {:
   A result = new A();
-  String changedValue = a.getValue() + "post";
+  String changedValue = a.getValue() + "-post";
   result.setValue(changedValue);
-  result.setInner(new Inner("inner" + a.getInner().getInnerValue()));
+  result.setInner(new Inner(a.getInner().getInnerValue() + "-post"));
   return result;
 :}
 
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/JavaTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/JavaTest.java
index 7655c07..4e807cc 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/JavaTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/JavaTest.java
@@ -45,7 +45,6 @@ public class JavaTest {
   private JastAddList<A> lastValueMany;
   private A lastValueNTA;
   private TestChecker checker;
-  private JavaHandler handler;
 
   void createModel() {
     model = new Root();
@@ -67,16 +66,24 @@ public class JavaTest {
             .setCheckForTuple(TOPIC_SEND_MANY, (name, expected) -> checkAList(expected, lastValueMany, name))
             .setCheckForString(TOPIC_SEND_NTA, (name, expected) -> checkA(expected, lastValueNTA, name))
             .setActualString(TOPIC_RECEIVE_TOKEN, () -> receiverRoot.getSomeToken())
+            .setCheckForString(TOPIC_RECEIVE_NODE_PLAIN, (name, expected) ->
+                    checkA(expected, receiverRoot.getSomeNode(), name))
+            .setCheckForString(TOPIC_RECEIVE_NODE_MAPPED, (name, expected) ->
+                    checkA(expected, receiverRoot.getSomeNodeWithMapping(), name))
+            .setCheckForTuple(TOPIC_RECEIVE_MANY, (name, expected) ->
+                    checkAList(expected, receiverRoot.getManyNodeList(), name))
+            .setCheckForString(TOPIC_RECEIVE_NTA, (name, expected) ->
+                    checkA(expected, receiverRoot.getNTA(), name))
             .put(TOPIC_RECEIVE_TOKEN, "")
+            .put(TOPIC_RECEIVE_MANY, tuple())
             .setActualNumberOfValues(() -> 0)
             .disableManualWait();
 
     // callbacks
-    handler = model._ragconnect_javaHandler();
-    handler.registerConsumer(TOPIC_SEND_TOKEN, bytes -> lastValueToken = new String(bytes));
-    handler.registerConsumer(TOPIC_SEND_NODE, bytes -> lastValueNode = ExposingASTNode.INSTANCE.bytesToA(bytes));
-    handler.registerConsumer(TOPIC_SEND_MANY, bytes -> lastValueMany = ExposingASTNode.INSTANCE.bytesToList(bytes));
-    handler.registerConsumer(TOPIC_SEND_NTA, bytes -> lastValueNTA = ExposingASTNode.INSTANCE.bytesToA(bytes));
+    model.ragconnectJavaRegisterConsumer(TOPIC_SEND_TOKEN, bytes -> lastValueToken = new String(bytes));
+    model.ragconnectJavaRegisterConsumer(TOPIC_SEND_NODE, bytes -> lastValueNode = ExposingASTNode.INSTANCE.bytesToA(bytes));
+    model.ragconnectJavaRegisterConsumer(TOPIC_SEND_MANY, bytes -> lastValueMany = ExposingASTNode.INSTANCE.bytesToList(bytes));
+    model.ragconnectJavaRegisterConsumer(TOPIC_SEND_NTA, bytes -> lastValueNTA = ExposingASTNode.INSTANCE.bytesToA(bytes));
 
     // receive
     receiverRoot.connectSomeToken(javaUri(TOPIC_RECEIVE_TOKEN));
@@ -99,7 +106,7 @@ public class JavaTest {
     checker.put(TOPIC_SEND_TOKEN, "")
             .put(TOPIC_SEND_NODE, "1")
             .put(TOPIC_SEND_MANY, tuple())
-            .put(TOPIC_SEND_NTA, "1")
+            .put(TOPIC_SEND_NTA, "1|1")
     ;
 
     communicateBoth();
@@ -123,7 +130,7 @@ public class JavaTest {
     checker.check();
 
     senderRoot.setInput("2");
-    checker.put(TOPIC_SEND_NTA, "2").check();
+    checker.put(TOPIC_SEND_NTA, "2|1").check();
 
     senderRoot.getSendNode().setValue("3");
     checker.put(TOPIC_SEND_NODE, "3").check();
@@ -134,10 +141,20 @@ public class JavaTest {
     senderRoot.addSendManyNode(createA("5"));
     checker.put(TOPIC_SEND_MANY, tuple("5")).check();
 
-    handler.push(TOPIC_RECEIVE_TOKEN, ExposingASTNode.INSTANCE.stringToBytes("7"));
+    model.ragconnectJavaPush(TOPIC_RECEIVE_TOKEN, ExposingASTNode.INSTANCE.stringToBytes("7"));
     checker.put(TOPIC_RECEIVE_TOKEN, "7").check();
 
-    // TODO check other receive ports
+    model.ragconnectJavaPush(TOPIC_RECEIVE_NODE_PLAIN, ExposingASTNode.INSTANCE.aToBytes(createA("8")));
+    checker.put(TOPIC_RECEIVE_NODE_PLAIN, "8").check();
+
+    model.ragconnectJavaPush(TOPIC_RECEIVE_NODE_MAPPED, ExposingASTNode.INSTANCE.aToBytes(createA("9")));
+    checker.put(TOPIC_RECEIVE_NODE_MAPPED, "9-post|inner-post").check();
+
+    model.ragconnectJavaPush(TOPIC_RECEIVE_MANY, ExposingASTNode.INSTANCE.listToBytes(new JastAddList<>(createA("10"), createA("11"))));
+    checker.put(TOPIC_RECEIVE_MANY, tuple("10", "11")).check();
+
+    model.ragconnectJavaPush(TOPIC_RECEIVE_NTA, ExposingASTNode.INSTANCE.aToBytes(createA("12")));
+    checker.put(TOPIC_RECEIVE_NTA, "12").check();
 
     System.out.println(model.ragconnectEvaluationCounterSummary());
   }
@@ -146,8 +163,19 @@ public class JavaTest {
     if (expectedValue == null) {
       assertNull(actual, alias);
     } else {
+      final String expectedValueInA, expectedInnerValue;
+      if (expectedValue.contains("|")) {
+        String[] tokens = expectedValue.split("\\|");
+        expectedValueInA = tokens[0];
+        expectedInnerValue = tokens[1];
+      } else {
+        expectedValueInA = expectedValue;
+        expectedInnerValue = "inner";
+      }
       assertNotNull(actual, alias);
-      assertEquals(expectedValue, actual.getValue(), alias);
+      assertEquals(expectedValueInA, actual.getValue(), alias);
+      assertNotNull(actual.getInner(), alias + ".inner");
+      assertEquals(expectedInnerValue, actual.getInner().getInnerValue(), alias + ".inner");
     }
   }
 
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 91bbd40..3fb3e22 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
@@ -217,73 +217,112 @@ public class TestUtils {
         awaitMqtt().alias(name).until(actual, Predicate.isEqual(expected));
       }
     }
+    static class ValuesToCompare<T> {
+      protected final Map<String, ActualAndExpected<T>> values = new HashMap<>();
+      protected final TestChecker parent;
+
+      ValuesToCompare(TestChecker parent) {
+        this.parent = parent;
+      }
+
+      public TestChecker setActual(String name, Callable<T> actual) {
+        values.computeIfAbsent(name, ActualAndExpected::new).actual = actual;
+        return parent;
+      }
+
+      public TestChecker setCheck(String name, BiConsumer<String, T> check) {
+        values.computeIfAbsent(name, ActualAndExpected::new).customCheck = check;
+        return parent;
+      }
+
+      public TestChecker put(String name, T expected) {
+        values.computeIfAbsent(name, ActualAndExpected::new).expected = expected;
+        return parent;
+      }
+
+      ActualAndExpected<T> get(String name) {
+        return values.get(name);
+      }
+
+      void forEach(BiConsumer<? super String, ? super ActualAndExpected<T>> action) {
+        values.forEach(action);
+      }
+    }
+    static class IntegerValuesToCompare extends ValuesToCompare<Integer> {
+      IntegerValuesToCompare(TestChecker parent) {
+        super(parent);
+      }
+
+      public TestChecker incNumberOfValues() {
+        return addToNumberOfValues(1);
+      }
+
+      public TestChecker addToNumberOfValues(int increment) {
+        // if there is at least one call to this, we do not need to manually wait in the next check()
+        parent.needManualWait = false;
+        Integer currentExpected = values.computeIfAbsent(NUMBER_OF_VALUES, ActualAndExpected::new).expected;
+        return put(NUMBER_OF_VALUES, currentExpected + increment);
+      }
+
+      public TestChecker setActualNumberOfValues(Callable<Integer> actual) {
+        setActual(NUMBER_OF_VALUES, actual);
+        values.get(NUMBER_OF_VALUES).expected = 0;
+        return parent;
+      }
+    }
     private final static String NUMBER_OF_VALUES = "numberOfValues";
-    private final Map<String, ActualAndExpected<String>> stringValues = new HashMap<>();
-    private final Map<String, ActualAndExpected<Tuple>> tupleValues = new HashMap<>();
-    private final Map<String, ActualAndExpected<Integer>> intValues = new HashMap<>();
+    public final ValuesToCompare<String> stringValues = new ValuesToCompare<>(this);
+    public final ValuesToCompare<Tuple> tupleValues = new ValuesToCompare<>(this);
+    public final IntegerValuesToCompare intValues = new IntegerValuesToCompare(this);
     private boolean needManualWait = true;
     private boolean useManualWait = true;
 
     public TestChecker incNumberOfValues() {
-      return addToNumberOfValues(1);
+      return intValues.incNumberOfValues();
     }
 
     public TestChecker addToNumberOfValues(int increment) {
-      // if there is at least one call to this, we do not need to manually wait in the next check()
-      needManualWait = false;
-      Integer currentExpected = intValues.computeIfAbsent(NUMBER_OF_VALUES, ActualAndExpected::new).expected;
-      return put(NUMBER_OF_VALUES, currentExpected + increment);
+      return intValues.addToNumberOfValues(increment);
     }
 
     public TestChecker setActualNumberOfValues(Callable<Integer> actual) {
-      setActualInteger(NUMBER_OF_VALUES, actual);
-      intValues.get(NUMBER_OF_VALUES).expected = 0;
-      return this;
+      return intValues.setActualNumberOfValues(actual);
     }
 
     public TestChecker setActualString(String name, Callable<String> actual) {
-      stringValues.computeIfAbsent(name, ActualAndExpected::new).actual = actual;
-      return this;
+      return stringValues.setActual(name, actual);
     }
 
     public TestChecker setCheckForString(String name, BiConsumer<String, String> check) {
-      stringValues.computeIfAbsent(name, ActualAndExpected::new).customCheck = check;
-      return this;
+      return stringValues.setCheck(name, check);
     }
 
     public TestChecker put(String name, String expected) {
-      stringValues.computeIfAbsent(name, ActualAndExpected::new).expected = expected;
-      return this;
+      return stringValues.put(name, expected);
     }
 
     public TestChecker setActualTuple(String name, Callable<Tuple> actual) {
-      tupleValues.computeIfAbsent(name, ActualAndExpected::new).actual = actual;
-      return this;
+      return tupleValues.setActual(name, actual);
     }
 
     public TestChecker setCheckForTuple(String name, BiConsumer<String, Tuple> check) {
-      tupleValues.computeIfAbsent(name, ActualAndExpected::new).customCheck = check;
-      return this;
+      return tupleValues.setCheck(name, check);
     }
 
     public TestChecker put(String name, Tuple expected) {
-      tupleValues.computeIfAbsent(name, ActualAndExpected::new).expected = expected;
-      return this;
+      return tupleValues.put(name, expected);
     }
 
     public TestChecker setActualInteger(String name, Callable<Integer> actual) {
-      intValues.computeIfAbsent(name, ActualAndExpected::new).actual = actual;
-      return this;
+      return intValues.setActual(name, actual);
     }
 
     public TestChecker setCheckForInteger(String name, BiConsumer<String, Integer> check) {
-      intValues.computeIfAbsent(name, ActualAndExpected::new).customCheck = check;
-      return this;
+      return intValues.setCheck(name, check);
     }
 
     public TestChecker put(String name, Integer expected) {
-      intValues.computeIfAbsent(name, ActualAndExpected::new).expected = expected;
-      return this;
+      return intValues.put(name, expected);
     }
 
     public TestChecker disableManualWait() {
-- 
GitLab


From d37e1825a2df901adc68912c35fc95e5bf4358b3 Mon Sep 17 00:00:00 2001
From: rschoene <rene.schoene@tu-dresden.de>
Date: Mon, 30 May 2022 17:19:06 +0200
Subject: [PATCH 7/7] Finalize tests for initial JavaHandler

- try to find cause for failing RelationTest
---
 .gitlab-ci.yml                                |  2 ++
 ragconnect.tests/.gitignore                   |  1 +
 .../ragconnect/tests/AbstractMqttTest.java    |  6 +----
 .../org/jastadd/ragconnect/tests/Errors.java  |  3 +--
 .../jastadd/ragconnect/tests/JavaTest.java    |  4 +--
 .../ragconnect/tests/MqttHandlerTest.java     |  2 +-
 .../ragconnect/tests/RagConnectTest.java      | 26 +++++++++++++++++++
 .../ragconnect/tests/RegressionTests.java     |  2 +-
 .../jastadd/ragconnect/tests/Warnings.java    |  3 +--
 .../tests/relation/RelationTest.java          |  2 +-
 .../src/test/resources/log4j2.xml             |  3 +++
 11 files changed, 39 insertions(+), 15 deletions(-)
 create mode 100644 ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/RagConnectTest.java

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 44a279a..401f721 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -42,6 +42,8 @@ test:
     - ./gradlew --console=plain --no-daemon allTests
   artifacts:
     when: always
+    paths:
+      - "ragconnect.tests/test.log"
     reports:
       junit: "ragconnect.tests/build/test-results/**/TEST-*.xml"
     expire_in: 1 week
diff --git a/ragconnect.tests/.gitignore b/ragconnect.tests/.gitignore
index 87b4cdd..31993ce 100644
--- a/ragconnect.tests/.gitignore
+++ b/ragconnect.tests/.gitignore
@@ -3,3 +3,4 @@ src/gen-res/
 src/gen/
 out/
 *.class
+test.log
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AbstractMqttTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AbstractMqttTest.java
index c537a1d..61fc420 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AbstractMqttTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AbstractMqttTest.java
@@ -14,14 +14,12 @@ import java.util.concurrent.TimeUnit;
  * @author rschoene - Initial contribution
  */
 @Tag("mqtt")
-public abstract class AbstractMqttTest {
+public abstract class AbstractMqttTest extends RagConnectTest {
 
   private static boolean checkDone = false;
 
   protected static MqttHandler publisher;
 
-  protected Logger logger = LoggerFactory.getLogger(getClass());
-
   /**
    * if the initial/current value shall be sent upon connecting
    */
@@ -61,7 +59,6 @@ public abstract class AbstractMqttTest {
   public final void testCommunicateSendInitialValue() throws IOException, InterruptedException {
     this.writeCurrentValue = true;
 
-    logger.debug("Start testCommunicateSendInitialValue");
     createModel();
     setupReceiverAndConnect();
 
@@ -80,7 +77,6 @@ public abstract class AbstractMqttTest {
   public final void testCommunicateOnlyUpdatedValue() throws IOException, InterruptedException {
     this.writeCurrentValue = false;
 
-    logger.debug("Start testCommunicateOnlyUpdatedValue");
     createModel();
     setupReceiverAndConnect();
 
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Errors.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Errors.java
index d774d91..9d4c730 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Errors.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Errors.java
@@ -22,9 +22,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
  *
  * @author rschoene - Initial contribution
  */
-public class Errors {
+public class Errors extends RagConnectTest {
 
-  private static final Logger logger = LoggerFactory.getLogger(Errors.class);
   private static final String ERROR_DIRECTORY = "errors/";
   private static final String OUTPUT_DIRECTORY = TestUtils.OUTPUT_DIRECTORY_PREFIX + ERROR_DIRECTORY;
 
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/JavaTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/JavaTest.java
index 4e807cc..0c33e9c 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/JavaTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/JavaTest.java
@@ -21,8 +21,7 @@ import static org.junit.jupiter.api.Assertions.*;
  *
  * @author rschoene - Initial contribution
  */
-@Tag("New")
-public class JavaTest {
+public class JavaTest extends RagConnectTest {
 
   private static final String TOPIC_RECEIVE_TOKEN = "receiveToken";
   private static final String TOPIC_RECEIVE_NODE_PLAIN = "receiveNode/plain";
@@ -35,7 +34,6 @@ public class JavaTest {
   private static final String TOPIC_SEND_MANY = "sendMany";
   private static final String TOPIC_SEND_NTA = "sendNTA";
 
-  protected Logger logger = LoggerFactory.getLogger(getClass());
   private Root model;
   private SenderRoot senderRoot;
   private ReceiverRoot receiverRoot;
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/MqttHandlerTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/MqttHandlerTest.java
index 091990a..be61201 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/MqttHandlerTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/MqttHandlerTest.java
@@ -20,7 +20,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
  * @author rschoene - Initial contribution
  */
 @Tag("mqtt")
-public class MqttHandlerTest {
+public class MqttHandlerTest extends RagConnectTest {
 
   @Test
   public void defaultBehaviour() {
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/RagConnectTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/RagConnectTest.java
new file mode 100644
index 0000000..e2da157
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/RagConnectTest.java
@@ -0,0 +1,26 @@
+package org.jastadd.ragconnect.tests;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.TestInfo;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Base for all RagConnect tests.
+ *
+ * @author rschoene - Initial contribution
+ */
+public class RagConnectTest {
+  protected Logger logger = LoggerFactory.getLogger(getClass());
+
+  @BeforeEach
+  public void logStart(TestInfo testInfo) {
+    logger.info("Starting {}.{}", getClass().getSimpleName(), testInfo.getDisplayName());
+  }
+
+  @AfterEach
+  public void logEnd(TestInfo testInfo) {
+    logger.info("Finished {}.{}", getClass().getSimpleName(), testInfo.getDisplayName());
+  }
+}
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/RegressionTests.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/RegressionTests.java
index 67c8217..5f74942 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/RegressionTests.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/RegressionTests.java
@@ -17,7 +17,7 @@ import static org.junit.jupiter.api.Assertions.*;
  *
  * @author rschoene - Initial contribution
  */
-public class RegressionTests {
+public class RegressionTests extends RagConnectTest {
 
   private static final String REGRESSION_TEST_OUTPUT_DIRECTORY = "regression-test/";
 
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Warnings.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Warnings.java
index b0840d6..121843c 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Warnings.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Warnings.java
@@ -21,9 +21,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
  *
  * @author rschoene - Initial contribution
  */
-public class Warnings {
+public class Warnings extends RagConnectTest {
 
-  private static final Logger logger = LoggerFactory.getLogger(Warnings.class);
   private static final String WARNING_DIRECTORY = "warnings/";
   private static final String OUTPUT_DIRECTORY = TestUtils.OUTPUT_DIRECTORY_PREFIX + WARNING_DIRECTORY;
 
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/relation/RelationTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/relation/RelationTest.java
index a245387..4ac0f55 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/relation/RelationTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/relation/RelationTest.java
@@ -616,7 +616,7 @@ public class RelationTest extends AbstractMqttTest {
     biB(3).getInner().setInnerValue("inner-bi-b3");
     checker.check();
 
-    System.out.println(model.ragconnectEvaluationCounterSummary());
+    logger.debug(model.ragconnectEvaluationCounterSummary());
   }
 
   private void assertNullOrA(String expectedValue, A actual, String alias) {
diff --git a/ragconnect.tests/src/test/resources/log4j2.xml b/ragconnect.tests/src/test/resources/log4j2.xml
index 653d6c3..9a1b22e 100644
--- a/ragconnect.tests/src/test/resources/log4j2.xml
+++ b/ragconnect.tests/src/test/resources/log4j2.xml
@@ -4,6 +4,9 @@
         <Console name="Console" target="SYSTEM_OUT">
             <PatternLayout pattern="%highlight{%d{HH:mm:ss.SSS} %-5level [%t] %logger{20} - %msg%n}" disableAnsi="false"/>
         </Console>
+        <File name="TestLogs" fileName="test.log" append="false">
+            <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level [%t] %logger{20} - %msg%n"/>
+        </File>
     </Appenders>
     <Loggers>
         <Root level="debug">
-- 
GitLab