diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 56c0e1b7d49ac9c19ac72b6e1bbc0d7be7b1191c..127cb13c90a5269189fbf9d8e5f29e43ec5d82cc 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -52,6 +52,7 @@ publish: script: - "./gradlew publish" only: + - dev - master ragdoc_build: @@ -81,23 +82,40 @@ 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" pages: - image: python:3.8-buster + image: python:3.10.0-bullseye stage: publish needs: - ragdoc_view - test + variables: + PAGES_BRANCH: pages + HTTPS_REMOTE: https://${PROJECT_BOT_USER}:${PROJECT_BOT_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git before_script: - - pip install -U mkdocs mkdocs-macros-plugin mkdocs-git-revision-date-localized-plugin + - apt update && apt install git-lfs + - pip install -r pages/requirements.txt + - git config user.name $PROJECT_BOT_USER + - git config user.email $PROJECT_BOT_USER@git-st.inf.tu-dresden.de + - git fetch -f origin $PAGES_BRANCH:$PAGES_BRANCH || echo "Pages branch not deployed yet." + - git checkout $CI_COMMIT_SHA script: - - cd pages && mkdocs build + - cd pages + - export VERSION=$(python main.py) + - echo $VERSION + - mike list --json --prefix public -r $HTTPS_REMOTE -b $PAGES_BRANCH + - mike deploy --rebase --prefix public -r $HTTPS_REMOTE -p -b $PAGES_BRANCH --update-aliases $VERSION + - cd .. + - git checkout $PAGES_BRANCH -- public/ + artifacts: + paths: + - public/ only: - dev - master - artifacts: - paths: - - public diff --git a/pages/docs/adding.md b/pages/docs/adding.md index ffbeda297edb3b458229fcad5006a6e95f39ad76..e06e9fc190a42dba21bec50807508c1ccf0d4bfa 100644 --- a/pages/docs/adding.md +++ b/pages/docs/adding.md @@ -51,7 +51,7 @@ You might need to add another task for [compiling relast specifications](#compil ## Build from source -If you want to use `RagConnect`, the currently suggested way is to first build the jar from the [RagConnect repository](https://git-st.inf.tu-dresden.de/jastadd/ragconnect): +If you want to plan to extend `RagConnect`, the suggested way is to first build the jar from the [RagConnect repository](https://git-st.inf.tu-dresden.de/jastadd/ragconnect) (if you only want to _use_ it, consider using [the packaged version](#use-packaged-version)). ```bash git clone https://git-st.inf.tu-dresden.de/jastadd/ragconnect.git @@ -60,14 +60,17 @@ cd ragconnect ls ragconnect.base/build/libs/ ``` -This `ragconnect-<version>.jar` can then be copied to your project. Please note, that you can safely use `ragconnect.jar` as filename, because the version can always be printed using `java -jar path/to/ragconnect.jar --version`. +This `ragconnect-<version>.jar` can then be copied to your project. +Please note, that you can safely use `ragconnect.jar` as filename, because the version can always be printed using `java -jar path/to/ragconnect.jar --version`. ```bash cp ragconnect.base/build/libs/ragconnect-<version>.jar ../your-project/libs/ragconnect.jar cd ../your-project/ ``` -Finally, this jar has to be integrated into your build process. In case, [Gradle](https://gradle.org/) is used, a task could look like the following (example taken from the [ros2rag usecase](https://git-st.inf.tu-dresden.de/jastadd/ros2rag)). The path to the jar file may need to be changed according to your project structure. +Finally, this jar has to be integrated into your build process. +In case, [Gradle](https://gradle.org/) is used, a task could look like the following (example taken from the [ros2rag use case](https://git-st.inf.tu-dresden.de/jastadd/ros2rag)). +The path to the jar file may need to be changed according to your project structure. ```groovy task ragConnect(type: JavaExec) { @@ -87,9 +90,12 @@ task ragConnect(type: JavaExec) { You might need to add another task for [compiling relast specifications](#compiling-relast-specifications). -## Compiling RelAst specifications +# Compiling RelAst specifications -The task to compile `RagConnect` specifications is typically accompanied with a task to invoke the [RelAst compiler](http://relational-rags.eu/) and the [JastAdd gradle plugin](https://plugins.gradle.org/plugin/org.jastadd). The additional arguments `--useJastAddNames`, `--listClass`, `--jastAddList` and `--resolverHelper` to relast are not required. Please see the user manual of the RelAst compiler for more information. +The task to compile `RagConnect` specifications is typically accompanied by a task to invoke the [RelAst compiler](http://relational-rags.eu/) and the [JastAdd gradle plugin](https://plugins.gradle.org/plugin/org.jastadd). +Currently, the parameter `--useJastAddNames` is **required**, and it may cause incompatibilities if not set. +The additional arguments `--listClass`, `--jastAddList` and `--resolverHelper` to relast are not required. +Please see the user manual of the RelAst compiler for more information. ```groovy task relastToJastAdd(type: JavaExec) { @@ -118,13 +124,3 @@ One also has to specify the dependencies to get correct ordering of tasks. generateAst.dependsOn relastToJastAdd relastToJastAdd.dependsOn ragConnect ``` - -## Introduced dependencies - -RagConnect itself does not introduce dependencies. -However, depending on the selected protocols (see [compiler options](using#compiler-options)), additional dependencies are required. - -| Protocol | Dependency (Gradle format) | Remarks | -|-|-|-| -| `mqtt` | `group: 'org.fusesource.mqtt-client', name: 'mqtt-client', version: '1.15'` | Mqtt is selected by default, so this dependency therefore is required "by default". Might work with other versions as well. | -| `rest` | `group: 'com.sparkjava', name: 'spark-core', version: '2.9.2'` | Might work with other versions as well. For debugging, it is beneficial to include an implementation for [SLF4J](http://www.slf4j.org/). | diff --git a/pages/docs/changelog.md b/pages/docs/changelog.md index eb0fe4d555048fb90f71cec631baafeaef35a4d7..fe3b87a7249a5b554902ea7239b58f91ac55a500 100644 --- a/pages/docs/changelog.md +++ b/pages/docs/changelog.md @@ -1,5 +1,11 @@ # Changelog +## 0.3.2 + +- Allow connection endpoints for list nonterminals ([#21](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/21)) +- Ensure correct connect and disconnect functionality ([#31](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/31)) +- Enhance documentation ([#13](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/13), [#20](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/20), [#41](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/41)) + ## 0.3.1 - Full support for incremental dependency tracking @@ -15,3 +21,20 @@ - Add methods to `disconnect` an endpoint - Internal: PoC for incremental dependency tracking and subtree endpoint definitions ([#14](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/14)) - Bugfix [#17](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/17): Added missing support for `boolean` + +## 0.2.2 + +- Allow normal tokens to be used in send definitions + +## 0.2.1 + +- New communication protocol: REST +- Selection of protocol when `connect` methods are called, by scheme of given URI +- Development changes: + - Supported printing out YAML data used for mustache templates + - Moved string constants to `MRagConnect` structure + +## 0.2.0 + +- Version submitted in paper "A Connection from ROS to RAG-Based Models" (2020) +- Supported communication protocols: MQTT diff --git a/pages/docs/compiler.md b/pages/docs/compiler.md new file mode 100644 index 0000000000000000000000000000000000000000..86f15b7cdb2fcee74dc9d63f9bfa64cee2e8f62f --- /dev/null +++ b/pages/docs/compiler.md @@ -0,0 +1,95 @@ +# Compiler options + +The compiler is JastAdd-compliant, i.e., it accepts all flags available for JastAdd, though there is no process how to chain pre-processors _yet_. +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`. | +| `--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. | +| `--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). | +| `--version` | No (false) | Print version info and exit (reused JastAdd option) | +| `--o` | No (`.`) | Output directory (reused JastAdd option) | + +All files to be process have to be passed as arguments. +Their type is decided by the file extension (`ast` and `relast` for input grammars, `connect` and `ragconnect` for RagConnect definitions file). + +# Additional software dependencies + +Using RagConnect itself does not introduce dependencies. +However, depending on the selected protocols and/or used features, additional dependencies are required when using the generated code. + +## Communication protocol characteristics + +### MQTT + +- Protocol identifier: `mqtt` +- URI scheme: `mqtt://<broker-host>[:port]/<topic>` +- Default port: 1883 +- Type for mapping definitions: `byte[]` +- Required runtime dependencies: + - `group: 'org.fusesource.mqtt-client', name: 'mqtt-client', version: '1.15'` +- Additional remarks: + - First leading slash not included in topic. + - Mqtt is selected by default, so this dependency therefore is required "by default". + - Might work with other versions of `org.fusesource.mqtt-client.mqtt.client` as well. + +### REST + +- Protocol identifier: `rest` +- URI scheme: `rest://localhost[:port]/<path>` +- Default port: 4567 +- Type for mapping definitions: `String` +- Required runtime dependencies: + - `group: 'com.sparkjava', name: 'spark-core', version: '2.9.3'` +- Additional remarks: + - Host is always `localhost`. + - Might work with newer versions of `com.sparkjava.spark-core` as well. + - For debugging, it is beneficial to include an implementation for [SLF4J](http://www.slf4j.org/). + +## Used features + +### Automatic dependency tracking + +- Condition: When passing `--incremental` and `--trace=flush` to RagConnect +- Required runtime dependencies: _none_ +- Required options for RelAST compiler: _none_ +- Required options for JastAdd: + - `--incremental` + - `--trace=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` + - [Feature description](/using#dependency-tracking-automatically-derived) + +### (Safer) Automatic dependency tracking + +- Condition: When passing `--experimental-jastadd-329` to RagConnect +- Required runtime dependencies: _none_ +- Required options for RelAST compiler: _none_ +- Required options for JastAdd: _none_ +- Remarks: + - JastAdd version has to support `INC_FLUSH_START` and `INC_FLUSH_END` (i.e., has [issue #329][jastadd-issue-329] resolved) + - [Feature description](/using#dependency-tracking-automatically-derived) + +### Tree/List Endpoints + +- Condition: When using `tree` or `list` endpoints along with default mappings +- Required runtime dependencies: + - `group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.12.1'` + - `group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.12.1'` +- Required options for RelAST compiler: + - `--serializer=jackson` +- Required options for JastAdd: _none_ +- Remarks: + - [Feature description](/using#an-advanced-example) + + +[jastadd-issue-329]: https://bitbucket.org/jastadd/jastadd2/issues/329/add-event-for-completion-of-flush diff --git a/pages/docs/using.md b/pages/docs/using.md index a8fc0da6cf144ac4a6adaf118c3faffc814cdff5..0118524d95148c180bb6242d0823b9b64099853b 100644 --- a/pages/docs/using.md +++ b/pages/docs/using.md @@ -2,14 +2,14 @@ The full example is available at <https://git-st.inf.tu-dresden.de/jastadd/ragconnect-minimal>. -## Preparation +## Preparation and Specification The following examples are inspired by the real test case [read1write2](https://git-st.inf.tu-dresden.de/jastadd/ragconnect-tests/-/tree/master/ragconnect.tests/src/test/01-input/read1write2) The idea is to have two nonterminals, where input information is received on one of them, and - after transformation - is sent out by both. Let the following grammar be used: -```bnf +``` A ::= <Input:String> /<OutputOnA:String>/ B* ; B ::= /<OutputOnB:String>/ ; ``` @@ -46,8 +46,11 @@ eq A.getB().input() = getInput(); ``` In other words, `OutputOnA` depends on `Input` of the same node, and `OutputOnB` depends on `Input` of its parent node. -Currently, those dependencies have to be explicitely written down. It is expected, that in future version, this won't be necessary anymore. -This happens also in the DSL (dependencies have to be named to uniquely identify them): +Currently, those dependencies can be explicitly written down, or incremental evaluation can be used. + +### Dependency tracking: Manually specified + +This specification happens also in the DSL (dependencies have to be named to uniquely identify them): ```java // dependency definitions @@ -55,9 +58,19 @@ A.OutputOnA canDependOn A.Input as dependencyA ; B.OutputOnB canDependOn A.Input as dependencyB ; ``` +### 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. +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`. + +An experimental, optimized version can be selected using `--experimental-jastadd-329` reducing the risk of conflicts between concurrent attribute evaluations. +However, this requires a version of JastAdd that resolved the [issue 329](https://bitbucket.org/jastadd/jastadd2/issues/329/add-event-for-completion-of-flush). + ## Using generated code -After specifying everything, code will be generated if [setup properly](adding). +After specifying everything, code will be generated if [setup properly](/adding). Let's create an AST in some driver code: ```java @@ -70,7 +83,7 @@ a.addB(b1); a.addB(b2); ``` -Now, we have to set the dependencies as described earlier. +If necessary, we have to set the dependencies as [described earlier](#dependency-tracking-manually-specified). ```java // a.OutputOnA -> a.Input @@ -82,7 +95,7 @@ b2.addDependencyB(a); ``` Finally, we can actually _connect_ the tokens. -Depending on the enabled protocols, [different URI schemes are allowed](#communication-protocol-characteristics). +Depending on the enabled protocols, [different URI schemes are allowed](/compiler#communication-protocol-characteristics). In this example, we use the default protocol: MQTT. ```java @@ -96,41 +109,16 @@ The first parameter of those connect-methods is always an URI-like String, to id In case of MQTT, the server is the host running an MQTT broker, and the path is equal to the topic to publish or subscribe to. Please note, that the first leading slash (`/`) is removed for MQTT topics, e.g., for `A.Input` the topic is actually `topic/for/input`. -For sending endpoints, there is a second boolean parameter to specify whether the current value shall be send immediately after connecting. - -## Communication protocol characteristics - -| Protocol | URI scheme | Default port | Type for mapping definitions | Remarks | -|-|-|-|-|-| -| `mqtt` | `mqtt://<broker-host>[:port]/<topic>` | 1883 | `byte[]` | First leading slash not included in topic. | -| `rest` | `rest://localhost[:port]/<path>` | 4567 | `String` | Host is always `localhost`. | +For sending endpoints, there is a second boolean parameter to specify whether the current value shall be sent immediately after connecting. -## Compiler options - -The compiler is JastAdd-compliant, i.e., it accepts all flags available for JastAdd, though there is no process how to chain pre-processors _yet_. -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`. | -| `--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. | -| `--version` | No (false) | Print version info and exit (reused JastAdd option) | -| `--o` | No (`.`) | Output directory (reused JastAdd option) | - -All files to be process have to be passed as arguments. Their type is decided by the file extension (`ast` and `relast` for input grammars, `connect` and `ragconnect` for RagConnect definitions file). - -## Remarks +## Remarks for using manual dependency tracking When constructing the AST and connecting it, one should always set dependencies before connecting, especially if updates already arriving for receiving endpoints. Otherwise, updates might not be propagated after setting dependencies, if values are equal after applying transformations of mapping definitions. As an example, when using the following grammar and definitions for RagConnect ... -```bnf +``` A ::= <Input:int> /<Output:String>/ ; ``` @@ -146,3 +134,121 @@ Round maps float f to int {: ``` ... connecting first could mean to store the first rounded value and not propagating this update, since no dependencies are set, and not propagating further updates leading to the same rounded value even after setting the dependencies. + +# An advanced example + +Non-terminal children can also be selected as endpoints (not only tokens). + +## Normal Non-Terminal Children + +Receiving normal non-terminal children and optionals means to replace them with a new node deserialized from the received message. +Sending them involves serializing a node, and sending this representation in a message. + +Suppose, the following (shortened) grammar is used (inspired from the testcase [tree](https://git-st.inf.tu-dresden.de/jastadd/ragconnect-tests/-/tree/master/ragconnect.tests/src/test/01-input/tree)) + +``` +Root ::= SenderRoot ReceiverRoot ; +SenderRoot ::= <Input:int> /Alfa/ ; +ReceiverRoot ::= Alfa ; +Alfa ::= // some content ... +``` + +Now, the complete node of type `Alfa` can be sent, and received again using the following connect specification: + +``` +send tree SenderRoot.Alfa ; +receive tree ReceiverRoot.Alfa ; +``` + +Currently, receiving and sending trees requires the explicit demarcation from tokens using the keyword `tree`. + +To process non-terminals, default mappings are provided for every non-terminal type of the used grammar. +They use the JSON serialization offered by the RelAST compiler, i.e., interpret the message as a `String`, deserialize the content reading the message as JSON, or vice versa. +Additional dependencies are required to use this feature, as detailed in [the compiler section](/compiler#treelist-endpoints). + +## Receiving List Children + +When receiving list children, there are a few more options to match the connection to given requirements. + +Suppose we use a similar grammar as above, i.e.: + +``` +SenderRoot ::= /AlfaList:Alfa*/ /SingleAlfa:Alfa/; +ReceiverRoot ::= Alfa* ; +``` + +Several options are possible: + +### list + +A message for a list endpoint can be interpreted as a complete list (a sequence of nodes of type `Alfa`) by using the `list` keyword instead of `tree`: + +``` +receive list ReceiverRoot.Alfa ; +``` + +### list + with add + +Upon receiving the message, the deserialized list can also be appended to the existing list instead of replace the latter. +This can be achieved using the keyword `with add` in addition to the keyword `list`: + +``` +receive list with add ReceiverRoot.Alfa ; +``` + +### tree (indexed) + +A message for a list endpoint can also be interpreted as an element of this list. + +``` +receive tree ReceiverRoot.Alfa ; +``` + +Upon connection, the index of the deserialized element to set, has to be passed (`1` in the example below). +The list must have enough elements once a message is received. + +```java +receiverRoot.connectAlfa("<some-url>", 1); +``` + +### tree (wildcard) + +Similar to the `tree (indexed)` case above, messages are interpreted as an element of the list, but the connection can also be made using a "wildcard topic" and without an index. +Then, once a message is received from a new concrete topic, the deserialized element will be appended to the list and this topic is associated with the index of the newly added element. +Any further message from that topic will replace the element at the associated index. +In the short example below, MQTT is used to with a wildcard topic, as `#` matches every sub-topic. + +```java +receiverRoot.connectAlfa("mqtt://<broker>/some/topic/#"); + +// list is initially empty +assertEquals(receiverRoot.getAlfaList(), list()); +// after receiving "1" on new topic "some/topic/one" (index 0) +assertEquals(receiverRoot.getAlfaList(), list("1")); +// after receiving "other" on new topic "some/topic/two" (index 1) +assertEquals(receiverRoot.getAlfaList(), list("1", "other")); +// after receiving "new" on existing topic "some/topic/one" (index 0) +assertEquals(receiverRoot.getAlfaList(), list("new", "other")); +``` + +### tree (indexed/wildcard) + with add + +Combining `tree` and `with add` results in a connection, where messages are interpreted as elements of the list, and new elements are appended to the existing list. +In that case, wildcard and non-wildcard connections behave in the same way, as no index has to be passed, and the element is always append at the end. +Reusing the example from above, the following observations can be made. + +```java +receiverRoot.connectAlfa("mqtt://<broker>/some/topic/#"); +// or +receiverRoot.connectAlfa("mqtt://<broker>/some/topic/one"); +receiverRoot.connectAlfa("mqtt://<broker>/some/topic/two"); + +// list is initially empty +assertEquals(receiverRoot.getAlfaList(), list()); +// after receiving "1" on topic "some/topic/one" +assertEquals(receiverRoot.getAlfaList(), list("1")); +// after receiving "other" on topic "some/topic/two" +assertEquals(receiverRoot.getAlfaList(), list("1", "other")); +// after receiving "new" on topic "some/topic/one" +assertEquals(receiverRoot.getAlfaList(), list("1", "other", "new")); +``` diff --git a/pages/main.py b/pages/main.py index 5f574f69198e31785d5d9a4e2b228d5567d61384..34fc17a618280621b5d737559c8a1839a9e019e7 100644 --- a/pages/main.py +++ b/pages/main.py @@ -1,7 +1,11 @@ +import os + ragconnectVersionFileName = '../ragconnect.base/src/main/resources/ragConnectVersion.properties' def get_version(): + if os.environ.get('CI_COMMIT_BRANCH', 'unknown') == 'dev': + return 'dev' with open(ragconnectVersionFileName) as ragconnectVersionFile: versionFileContent = ragconnectVersionFile.read() return versionFileContent[versionFileContent.rindex('version=') + 8:].strip() diff --git a/pages/mkdocs.yml b/pages/mkdocs.yml index 6363c73ae7ac43b9c4d04c4eb0036ef36fb5019a..c00e8c5eaf3bc9593a30800d532baedb56be2620 100644 --- a/pages/mkdocs.yml +++ b/pages/mkdocs.yml @@ -1,15 +1,25 @@ site_name: RagConnect +repo_url: https://git-st.inf.tu-dresden.de/jastadd/ragconnect +site_dir: ../public + nav: - - use_cases.md - - adding.md - - inner-workings.md - - using.md - - extending.md - - changelog.md - - API documentation: ragdoc/index.html + - "RagConnect by Example": using.md + - "Use Cases": use_cases.md + - "Adding RagConnect to your project": adding.md + - "Compiler options": compiler.md + - "Inner workings": inner-workings.md + - "Extending RagConnect": extending.md + - "Changelog": changelog.md + - "API documentation": ragdoc/index.html + theme: name: readthedocs custom_dir: custom_theme/ + +markdown_extensions: + - toc: + permalink: + plugins: - search - git-revision-date-localized: @@ -18,5 +28,3 @@ plugins: locale: en fallback_to_build_date: True - macros -repo_url: https://git-st.inf.tu-dresden.de/jastadd/ragconnect -site_dir: ../public diff --git a/pages/requirements.txt b/pages/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..43e5e8286a08c67e0f31725821347f38ae4641ef --- /dev/null +++ b/pages/requirements.txt @@ -0,0 +1,4 @@ +mkdocs==1.2.2 +mkdocs-git-revision-date-localized-plugin==0.10.3 +mkdocs-macros-plugin==0.6.3 +mike==1.1.2 diff --git a/ragconnect.base/build.gradle b/ragconnect.base/build.gradle index 77bafe579a620ca243e1061785fcf1ddef8a3108..c1ee98d52184b358a411a17a359f75a4fa601e8e 100644 --- a/ragconnect.base/build.gradle +++ b/ragconnect.base/build.gradle @@ -34,11 +34,11 @@ dependencies { } def versionFile = 'src/main/resources/ragConnectVersion.properties' -def oldProps = new Properties() +def props = new Properties() try { - file(versionFile).withInputStream { stream -> oldProps.load(stream) } - version = oldProps['version'] + file(versionFile).withInputStream { stream -> props.load(stream) } + version = props['version'] } catch (e) { // this happens, if either the properties file is not present, or cannot be read from throw new GradleException("File ${versionFile} not found or unreadable. Aborting.", e) @@ -52,9 +52,9 @@ task printVersion() { task newVersion() { doFirst { - def props = new Properties() - props['version'] = value - props.store(file(versionFile).newWriter(), null) + def newProps = new Properties() + newProps['version'] = value + newProps.store(file(versionFile).newWriter(), null) } } @@ -108,6 +108,14 @@ task relast(type: JavaExec) { './src/gen/jastadd/RagConnectResolverStubs.jrag') } +clean { + delete "src/gen/jastadd/Coverage.jrag" + delete "src/gen/jastadd/RagConnect.ast" + delete "src/gen/jastadd/RagConnect.jadd" + delete "src/gen/jastadd/RagConnectRefResolver.jadd" + delete "src/gen/jastadd/RagConnectResolverStubs.jrag" +} + jastadd { configureModuleBuild() modules { diff --git a/ragconnect.base/src/main/jastadd/Analysis.jrag b/ragconnect.base/src/main/jastadd/Analysis.jrag index e8afb8ba2d4bb1485be30b543812b48e291dd4b3..ac891b0e819b1e57e135afced64495a76396db5e 100644 --- a/ragconnect.base/src/main/jastadd/Analysis.jrag +++ b/ragconnect.base/src/main/jastadd/Analysis.jrag @@ -95,4 +95,10 @@ aspect Analysis { to MappingDefinition.effectiveUsedAt() for each effectiveMappings(); + // --- typeIsList --- + syn boolean EndpointDefinition.typeIsList() = false; + eq TypeEndpointDefinition.typeIsList() { + return getType().isListComponent(); + } + } diff --git a/ragconnect.base/src/main/jastadd/Configuration.jadd b/ragconnect.base/src/main/jastadd/Configuration.jadd index e7e1268df443a278496a499c59e896d1ee27c80a..8fd0e64b14bbd9a05c7cbf9b7de59ed0cd216d78 100644 --- a/ragconnect.base/src/main/jastadd/Configuration.jadd +++ b/ragconnect.base/src/main/jastadd/Configuration.jadd @@ -3,6 +3,7 @@ aspect Configuration { public static boolean ASTNode.loggingEnabledForWrites = false; public static boolean ASTNode.loggingEnabledForIncremental = false; public static TypeDecl ASTNode.rootNode; + public static String ASTNode.JastAddList = "List"; public static boolean ASTNode.usesMqtt; public static boolean ASTNode.usesRest; public static boolean ASTNode.incrementalOptionActive; diff --git a/ragconnect.base/src/main/jastadd/Errors.jrag b/ragconnect.base/src/main/jastadd/Errors.jrag index 1aa3666c2b19bf446667f1a763bfb4824aa44f25..770e506cc5c0145a0ba9979d57a1086a399ed39e 100644 --- a/ragconnect.base/src/main/jastadd/Errors.jrag +++ b/ragconnect.base/src/main/jastadd/Errors.jrag @@ -1,7 +1,3 @@ -import java.util.Set; -import java.util.TreeSet; -import java.util.LinkedList; - aspect Errors { coll Set<ErrorMessage> RagConnect.errors() [new TreeSet<ErrorMessage>()] diff --git a/ragconnect.base/src/main/jastadd/Imports.jadd b/ragconnect.base/src/main/jastadd/Imports.jadd new file mode 100644 index 0000000000000000000000000000000000000000..27fc9a5cfc5bbcbd2772fe35548bda22ab888e01 --- /dev/null +++ b/ragconnect.base/src/main/jastadd/Imports.jadd @@ -0,0 +1,5 @@ +import java.util.*; + +aspect Imports { + // empty +} diff --git a/ragconnect.base/src/main/jastadd/Navigation.jrag b/ragconnect.base/src/main/jastadd/Navigation.jrag index d3890f7d22a195127e7688435f9168aa64a37eac..437f65196c2dc3b8c4f5a112a23e80608084a063 100644 --- a/ragconnect.base/src/main/jastadd/Navigation.jrag +++ b/ragconnect.base/src/main/jastadd/Navigation.jrag @@ -1,6 +1,3 @@ -import java.util.List; -import java.util.ArrayList; - aspect RagConnectNavigation { // --- program --- diff --git a/ragconnect.base/src/main/jastadd/RagConnect.relast b/ragconnect.base/src/main/jastadd/RagConnect.relast index 309c4776d923a6afd7b98b7bec190d96db74f28e..c1cbbb2445e55956f41ee780eab2e216175b6b6f 100644 --- a/ragconnect.base/src/main/jastadd/RagConnect.relast +++ b/ragconnect.base/src/main/jastadd/RagConnect.relast @@ -13,10 +13,10 @@ rel TokenEndpointDefinition.Token <-> TokenComponent.TokenEndpointDefinition*; ReceiveTokenEndpointDefinition : TokenEndpointDefinition; SendTokenEndpointDefinition : TokenEndpointDefinition; -abstract TypeEndpointDefinition : EndpointDefinition; +abstract TypeEndpointDefinition : EndpointDefinition ::= <UseList:boolean> ; rel TypeEndpointDefinition.Type <-> TypeComponent.TypeEndpointDefinition*; -ReceiveTypeEndpointDefinition : TypeEndpointDefinition; +ReceiveTypeEndpointDefinition : TypeEndpointDefinition ::= <WithAdd:boolean>; SendTypeEndpointDefinition : TypeEndpointDefinition; DependencyDefinition ::= <ID>; diff --git a/ragconnect.base/src/main/jastadd/intermediate/Generation.jadd b/ragconnect.base/src/main/jastadd/intermediate/Generation.jadd index 3b63bddfd2079518793308e0320fe2311724828e..b58b9670abd0abb26db113c3fee6f98033e3f4f7 100644 --- a/ragconnect.base/src/main/jastadd/intermediate/Generation.jadd +++ b/ragconnect.base/src/main/jastadd/intermediate/Generation.jadd @@ -4,6 +4,9 @@ Design considerations */ aspect AttributesForMustache { + // --- EndpointDefinition --- + syn String EndpointDefinition.idTokenName() = "InternalRagconnectTopicInList"; + // --- MRagConnect --- eq MRagConnect.getRootTypeComponent(int i).isFirst() = i == 0; @@ -15,6 +18,32 @@ aspect AttributesForMustache { syn String MRagConnect.restHandlerAttribute() = "_restHandler"; syn String MRagConnect.restHandlerField() = "_restHandler"; + syn boolean MRagConnect.hasTreeListEndpoints() = !sendingTreeListEndpoints().isEmpty() || !receivingTreeListEndpoints().isEmpty(); + syn List<MTypeEndpointDefinition> MRagConnect.sendingTreeListEndpoints() { + List<MTypeEndpointDefinition> result = new ArrayList<>(); + for (var mEndpointDef : getTypeSendDefinitionList()) { + if (mEndpointDef.typeIsList()) { + result.add(mEndpointDef); + } + } + return result; + } + syn List<MTypeEndpointDefinition> MRagConnect.receivingTreeListEndpoints() { + List<MTypeEndpointDefinition> result = new ArrayList<>(); + for (var mEndpointDef : getTypeReceiveDefinitionList()) { + if (mEndpointDef.typeIsList()) { + result.add(mEndpointDef); + } + } + return result; + } + syn List<TypeDecl> MRagConnect.typesForReceivingListEndpoints() { + return receivingTreeListEndpoints().stream() + .map(mEndpointDef -> mEndpointDef.type().getTypeDecl()) + .distinct() + .collect(java.util.stream.Collectors.toList()); + } + // --- MEndpointDefinition --- syn String MEndpointDefinition.preemptiveExpectedValue(); syn String MEndpointDefinition.preemptiveReturn(); @@ -24,16 +53,18 @@ aspect AttributesForMustache { syn String MEndpointDefinition.entityName(); syn String MEndpointDefinition.updateMethod(); syn String MEndpointDefinition.writeMethod(); + syn String MEndpointDefinition.getterMethod(); eq MEndpointDefinition.getInnerMappingDefinition(int i).isLast() = i == getNumInnerMappingDefinition() - 1; eq MEndpointDefinition.getInnerMappingDefinition(int i).inputVarName() = i == 0 ? firstInputVarName() : getInnerMappingDefinition(i - 1).outputVarName(); syn String MEndpointDefinition.connectParameterName() = "uriString"; syn String MEndpointDefinition.connectMethod() = "connect" + entityName(); + syn String MEndpointDefinition.internalConnectMethod() = "_internal_" + connectMethod(); syn boolean MEndpointDefinition.isTypeEndpointDefinition() = endpointDef().isTypeEndpointDefinition(); syn String MEndpointDefinition.disconnectMethod() { - // if both (send and receive) are defined for the token, ensure methods with different names + // if both (send and receive) are defined for an endpoint, ensure methods with different names String extra; if (endpointDef().isTokenEndpointDefinition()) { extra = endpointDef().asTokenEndpointDefinition().lookupTokenEndpointDefinitions(token()).size() > 1 ? uniqueSuffix() : ""; @@ -55,6 +86,7 @@ aspect AttributesForMustache { syn TokenComponent MEndpointDefinition.token() = endpointDef().asTokenEndpointDefinition().getToken(); syn TypeComponent MEndpointDefinition.type() = endpointDef().asTypeEndpointDefinition().getType(); syn boolean MEndpointDefinition.alwaysApply() = endpointDef().getAlwaysApply(); + syn boolean MEndpointDefinition.typeIsList() = endpointDef().typeIsList(); syn String MEndpointDefinition.tokenName() = token().getName(); syn String MEndpointDefinition.typeName() = type().getName(); syn String MEndpointDefinition.typeDeclName() = type().getTypeDecl().getName(); @@ -69,6 +101,10 @@ aspect AttributesForMustache { if (endpointDef().isTokenEndpointDefinition() && token().isPrimitiveType() && lastDefinition().mappingDef().getToType().isPrimitiveType()) { return preemptiveExpectedValue() + " == " + lastResult(); } + if (endpointDef().isReceiveTypeEndpointDefinition() && endpointDef().asReceiveTypeEndpointDefinition().getWithAdd()) { + // only check if received list is not null + return lastResult() + " == null"; + } if (endpointDef().isTypeEndpointDefinition() && type().isOptComponent()) { // use "hasX()" instead of "getX() != null" for optionals return "has" + typeName() + "()" + " && " + preemptiveExpectedValue() + ".equals(" + lastResult() + ")"; @@ -78,14 +114,20 @@ aspect AttributesForMustache { } return preemptiveExpectedValue() + " != null ? " + preemptiveExpectedValue() + ".equals(" + lastResult() + ") : " + lastResult() + " == null"; } + syn String MEndpointDefinition.sender() = null; // only for M*SendDefinitions + syn String MEndpointDefinition.lastValue() = sender() + ".lastValue"; // only for M*SendDefinitions // --- MTokenEndpointDefinition --- + eq MTokenEndpointDefinition.getterMethod() = "get" + tokenName(); eq MTokenEndpointDefinition.parentTypeName() = token().containingTypeDecl().getName(); eq MTokenEndpointDefinition.entityName() = tokenName(); // --- MTypeEndpointDefinition --- + syn boolean MTypeEndpointDefinition.isWithAdd() = endpointDef().isReceiveTypeEndpointDefinition() ? endpointDef().asReceiveTypeEndpointDefinition().getWithAdd() : false; + syn boolean MTypeEndpointDefinition.isUseList() = endpointDef().asTypeEndpointDefinition().getUseList(); + eq MTypeEndpointDefinition.getterMethod() = "get" + typeName() + (typeIsList() ? "List" : ""); eq MTypeEndpointDefinition.parentTypeName() = type().containingTypeDecl().getName(); - eq MTypeEndpointDefinition.entityName() = typeName(); + eq MTypeEndpointDefinition.entityName() = typeName() + (isUseList() ? "List" : ""); // --- MInnerMappingDefinition --- inh boolean MInnerMappingDefinition.isLast(); @@ -96,7 +138,7 @@ aspect AttributesForMustache { syn String MInnerMappingDefinition.outputVarName() = "result" + methodName(); // we do not need "_" in between here, because methodName begins with one // --- MTokenReceiveDefinition --- - eq MTokenReceiveDefinition.preemptiveExpectedValue() = "get" + tokenName() + "()"; + eq MTokenReceiveDefinition.preemptiveExpectedValue() = getterMethod() + "()"; eq MTokenReceiveDefinition.preemptiveReturn() = "return;"; eq MTokenReceiveDefinition.endpointDef() = getReceiveTokenEndpointDefinition(); eq MTokenReceiveDefinition.firstInputVarName() = "message"; @@ -107,34 +149,35 @@ aspect AttributesForMustache { eq MTokenSendDefinition.preemptiveExpectedValue() = lastValue(); eq MTokenSendDefinition.preemptiveReturn() = "return false;"; eq MTokenSendDefinition.endpointDef() = getSendTokenEndpointDefinition(); - eq MTokenSendDefinition.firstInputVarName() = "get" + tokenName() + "()"; + eq MTokenSendDefinition.firstInputVarName() = getterMethod() + "()"; eq MTokenSendDefinition.updateMethod() = "_update_" + tokenName(); eq MTokenSendDefinition.writeMethod() = "_writeLastValue_" + tokenName(); - syn String MTokenSendDefinition.sender() = "_sender_" + tokenName(); - syn String MTokenSendDefinition.lastValue() = "_lastValue" + tokenName(); - syn String MTokenSendDefinition.tokenResetMethod() = "get" + tokenName() + "_reset"; + eq MTokenSendDefinition.sender() = "_sender_" + tokenName(); + syn String MTokenSendDefinition.tokenResetMethod() = getterMethod() + "_reset"; syn boolean MTokenSendDefinition.shouldSendValue() = endpointDef().asTokenEndpointDefinition().shouldSendValue(); // MTypeReceiveDefinition - eq MTypeReceiveDefinition.preemptiveExpectedValue() = "get" + typeName() + "()"; + eq MTypeReceiveDefinition.preemptiveExpectedValue() = getterMethod() + "()"; eq MTypeReceiveDefinition.preemptiveReturn() = "return;"; eq MTypeReceiveDefinition.endpointDef() = getReceiveTypeEndpointDefinition(); eq MTypeReceiveDefinition.firstInputVarName() = "message"; eq MTypeReceiveDefinition.updateMethod() = null; eq MTypeReceiveDefinition.writeMethod() = null; + syn String MTypeReceiveDefinition.resolveInListMethodName() = "_ragconnect_resolve" + entityName() + "InList"; + syn String MTypeReceiveDefinition.idTokenName() = endpointDef().idTokenName(); + // MTypeSendDefinition eq MTypeSendDefinition.preemptiveExpectedValue() = lastValue(); eq MTypeSendDefinition.preemptiveReturn() = "return false;"; eq MTypeSendDefinition.endpointDef() = getSendTypeEndpointDefinition(); - eq MTypeSendDefinition.firstInputVarName() = "get" + typeName() + "()"; + eq MTypeSendDefinition.firstInputVarName() = getterMethod() + "()"; eq MTypeSendDefinition.updateMethod() = "_update_" + typeName(); eq MTypeSendDefinition.writeMethod() = "_writeLastValue_" + typeName(); - syn String MTypeSendDefinition.sender() = "_sender_" + typeName(); - syn String MTypeSendDefinition.lastValue() = "_lastValue" + typeName(); - syn String MTypeSendDefinition.tokenResetMethod() = "get" + typeName() + "_reset"; + eq MTypeSendDefinition.sender() = "_sender_" + typeName(); + syn String MTypeSendDefinition.tokenResetMethod() = getterMethod() + "_reset"; syn boolean MTypeSendDefinition.shouldSendValue() = endpointDef().asTypeEndpointDefinition().shouldSendValue(); // --- MMappingDefinition --- @@ -313,7 +356,7 @@ aspect AspectGeneration { } } -aspect RelationGeneration { +aspect GrammarGeneration { syn java.util.List<Relation> RagConnect.additionalRelations() { java.util.List<Relation> result = new java.util.ArrayList<>(); for (DependencyDefinition dd : allDependencyDefinitionList()) { @@ -334,6 +377,37 @@ aspect RelationGeneration { result.addComment(new WhitespaceComment("\n")); return result; } + +// coll java.util.Map<TypeDecl, TokenComponent> RagConnect.additionalTokens() [new java.util.HashMap<>()] with put root RagConnect; + +// TypeEndpointDefinition contributes getTokenToCreate() +// when typeIsList() && !getUseList() +// to RagConnect.additionalTokens() +//// for ragconnect() +// ; + + syn java.util.Map<TypeDecl, TokenComponent> RagConnect.additionalTokens() { + java.util.Map<TypeDecl, TokenComponent> result = new java.util.HashMap<>(); + for (EndpointDefinition def : allEndpointDefinitionList()) { + if (def.isTypeEndpointDefinition() && def.getTokenToCreate() != null) { + result.put(def.asTypeEndpointDefinition().getType().getTypeDecl(), def.getTokenToCreate()); + } + } + return result; + } + + syn TokenComponent EndpointDefinition.getTokenToCreate() = null; + eq TypeEndpointDefinition.getTokenToCreate() { + if (typeIsList() && !getUseList()) { + TokenComponent result = new TokenComponent(); + result.setName(idTokenName()); + result.setNTA(false); + result.setJavaTypeUse(new SimpleJavaTypeUse("String")); + return result; + } else { + return null; + } + } } aspect GrammarExtension { diff --git a/ragconnect.base/src/main/jastadd/intermediate/Mappings.jrag b/ragconnect.base/src/main/jastadd/intermediate/Mappings.jrag index 47379bd06921509023f686ab05621b63eb5729d9..3a7fce51404e43be679d7b8829127099fd78b683 100644 --- a/ragconnect.base/src/main/jastadd/intermediate/Mappings.jrag +++ b/ragconnect.base/src/main/jastadd/intermediate/Mappings.jrag @@ -1,7 +1,7 @@ aspect DefaultMappings { private String RagConnect.baseDefaultMappingTypeNamePart(String typeName) { - return capitalize(typeName).replace("[]", "s"); + return capitalize(typeName).replace("[]", "s").replace("<", "").replace(">", "List"); } private MappingDefinitionType RagConnect.baseDefaultMappingTypeFromName(String typeName) { @@ -67,6 +67,28 @@ aspect DefaultMappings { ); } + syn nta DefaultMappingDefinition RagConnect.defaultBytesToListTreeMapping(String typeName) { + return treeDefaultMappingDefinition("byte[]", JastAddList + "<" + typeName + ">", + "String content = new String(input);\n" + + "com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();\n" + + "com.fasterxml.jackson.core.JsonFactory factory = new com.fasterxml.jackson.core.JsonFactory();\n" + + "com.fasterxml.jackson.core.JsonParser parser = factory.createParser(content);\n" + + JastAddList + "<" + typeName + ">" + " result = " + typeName + ".deserializeList((com.fasterxml.jackson.databind.node.ArrayNode)mapper.readTree(parser));\n" + + "parser.close();\n" + + "return result;" + ); + } + syn nta DefaultMappingDefinition RagConnect.defaultListTreeToBytesMapping() { + return treeDefaultMappingDefinition(JastAddList, "byte[]", + "java.io.ByteArrayOutputStream outputStream = new java.io.ByteArrayOutputStream();\n" + + "com.fasterxml.jackson.core.JsonFactory factory = new com.fasterxml.jackson.core.JsonFactory();\n" + + "com.fasterxml.jackson.core.JsonGenerator generator = factory.createGenerator(outputStream, com.fasterxml.jackson.core.JsonEncoding.UTF8);\n"+ + "input.serialize(generator);\n" + + "generator.flush();\n" + + "return outputStream.toString().getBytes();" + ); + } + syn nta DefaultMappingDefinition RagConnect.defaultBooleanToBytesMapping() = baseDefaultMappingDefinition( "boolean", "byte[]", "return java.nio.ByteBuffer.allocate(1).put((byte) (input ? 1 : 0)).array();"); syn nta DefaultMappingDefinition RagConnect.defaultIntToBytesMapping() = baseDefaultMappingDefinition( @@ -187,13 +209,22 @@ aspect Mappings { case "String": return ragconnect().defaultBytesToStringMapping(); default: try { - TypeDecl typeDecl = program().resolveTypeDecl(targetTypeName()); - return ragconnect().defaultBytesToTreeMapping(typeDecl.getName()); + TypeDecl typeDecl = program().resolveTypeDecl(targetTypeName()); + // TODO: also support list-types, if list is first type + return ragconnect().defaultBytesToTreeMapping(typeDecl.getName()); } catch (Exception ignore) {} System.err.println("Could not find suitable default mapping for " + targetTypeName() + " on " + this); return null; } } + eq TypeEndpointDefinition.suitableReceiveDefaultMapping() { + try { + TypeDecl typeDecl = program().resolveTypeDecl(targetTypeName()); + return typeIsList() && getUseList() ? ragconnect().defaultBytesToListTreeMapping(typeDecl.getName()) : ragconnect().defaultBytesToTreeMapping(typeDecl.getName()); + } catch (Exception ignore) {} + return super.suitableReceiveDefaultMapping(); + } + // --- suitableSendDefaultMapping --- syn DefaultMappingDefinition EndpointDefinition.suitableSendDefaultMapping() { switch (targetTypeName()) { @@ -214,13 +245,21 @@ aspect Mappings { case "String": return ragconnect().defaultStringToBytesMapping(); default: try { - TypeDecl typeDecl = program().resolveTypeDecl(targetTypeName()); - return ragconnect().defaultTreeToBytesMapping(typeDecl.getName()); + TypeDecl typeDecl = program().resolveTypeDecl(targetTypeName()); + // TODO: also support list-types, if list is last type + return ragconnect().defaultTreeToBytesMapping(typeDecl.getName()); } catch (Exception ignore) {} System.err.println("Could not find suitable default mapping for " + targetTypeName() + " on " + this); return null; } } + eq TypeEndpointDefinition.suitableSendDefaultMapping() { + try { + TypeDecl typeDecl = program().resolveTypeDecl(targetTypeName()); + return typeIsList() && getUseList() ? ragconnect().defaultListTreeToBytesMapping() : ragconnect().defaultTreeToBytesMapping(typeDecl.getName()); + } catch (Exception ignore) {} + return super.suitableSendDefaultMapping(); + } // --- targetTypeName --- syn String EndpointDefinition.targetTypeName(); @@ -320,7 +359,9 @@ aspect Mappings { for (TypeDecl typeDecl : getProgram().typeDecls()) { result.add(defaultBytesToTreeMapping(typeDecl.getName())); result.add(defaultTreeToBytesMapping(typeDecl.getName())); + result.add(defaultBytesToListTreeMapping(typeDecl.getName())); } + result.add(defaultListTreeToBytesMapping()); // // string conversion // result.add(defaultStringToBooleanMapping()); // result.add(defaultStringToIntMapping()); diff --git a/ragconnect.base/src/main/jastadd/intermediate2mustache/MustacheNodesToYAML.jrag b/ragconnect.base/src/main/jastadd/intermediate2mustache/MustacheNodesToYAML.jrag index ef54a74e6fd57f11f17530e53524c63446277ca3..04b9cf89c248c39740677c88835b56c5325493bf 100644 --- a/ragconnect.base/src/main/jastadd/intermediate2mustache/MustacheNodesToYAML.jrag +++ b/ragconnect.base/src/main/jastadd/intermediate2mustache/MustacheNodesToYAML.jrag @@ -121,12 +121,14 @@ aspect MustacheNodesToYAML { syn MappingElement MTypeReceiveDefinition.toYAML() { MappingElement result = super.toYAML(); + result.put("typeIsList", typeIsList()); result.put("loggingEnabledForReads", loggingEnabledForReads); return result; } syn MappingElement MTypeSendDefinition.toYAML() { MappingElement result = super.toYAML(); + result.put("typeIsList", typeIsList()); result.put("sender", sender()); result.put("lastValue", lastValue()); result.put("loggingEnabledForWrites", loggingEnabledForWrites); diff --git a/ragconnect.base/src/main/jastadd/parser/RagConnect.parser b/ragconnect.base/src/main/jastadd/parser/RagConnect.parser index eefb64e8ec713991b1c8d214025e71586ad4a645..a7ba289535a01938c1284ad67f46b2a9e81b9fb5 100644 --- a/ragconnect.base/src/main/jastadd/parser/RagConnect.parser +++ b/ragconnect.base/src/main/jastadd/parser/RagConnect.parser @@ -46,7 +46,36 @@ EndpointDefinition endpoint_definition_type = RECEIVE token_ref {: return new ReceiveTokenEndpointDefinition().setToken(token_ref); :} | SEND token_ref {: return new SendTokenEndpointDefinition().setToken(token_ref); :} | RECEIVE TREE type_ref {: return new ReceiveTypeEndpointDefinition().setType(type_ref); :} + | RECEIVE TREE WITH ADD type_ref + {: + ReceiveTypeEndpointDefinition result = new ReceiveTypeEndpointDefinition(); + result.setType(type_ref); + result.setWithAdd(true); + return result; + :} | SEND TREE type_ref {: return new SendTypeEndpointDefinition().setType(type_ref); :} + | RECEIVE LIST type_ref + {: + ReceiveTypeEndpointDefinition result = new ReceiveTypeEndpointDefinition(); + result.setType(type_ref); + result.setUseList(true); + return result; + :} + | RECEIVE LIST WITH ADD type_ref + {: + ReceiveTypeEndpointDefinition result = new ReceiveTypeEndpointDefinition(); + result.setType(type_ref); + result.setWithAdd(true); + result.setUseList(true); + return result; + :} + | SEND LIST type_ref + {: + SendTypeEndpointDefinition result = new SendTypeEndpointDefinition(); + result.setType(type_ref); + result.setUseList(true); + return result; + :} ; TokenComponent token_ref diff --git a/ragconnect.base/src/main/jastadd/scanner/Keywords.flex b/ragconnect.base/src/main/jastadd/scanner/Keywords.flex index 40d751b37ffab7ffe63355f50e226f794de02c3b..69830108bfb0644cd75008b47b58ee149dc3b2ad 100644 --- a/ragconnect.base/src/main/jastadd/scanner/Keywords.flex +++ b/ragconnect.base/src/main/jastadd/scanner/Keywords.flex @@ -6,3 +6,6 @@ "to" { return sym(Terminals.TO); } "as" { return sym(Terminals.AS); } "tree" { return sym(Terminals.TREE); } +"list" { return sym(Terminals.LIST); } +"with" { return sym(Terminals.WITH); } +"add" { return sym(Terminals.ADD); } 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 8035d76e424cd813e62c400ffd70f84a6167a98f..915d9f92d49cf3a847f05d0b23393bce1832fc1a 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 @@ -232,6 +232,7 @@ public class Compiler extends AbstractCompiler { ragConnect.treeResolveAll(); ragConnect.additionalRelations().forEach(ragConnectGrammarPart::addDeclaration); + ragConnect.additionalTokens().forEach(TypeDecl::addComponent); ASTNode.loggingEnabledForReads = optionLogReads.value(); ASTNode.loggingEnabledForWrites = optionLogWrites.value(); ASTNode.loggingEnabledForIncremental = optionLogIncremental.value(); @@ -241,6 +242,9 @@ public class Compiler extends AbstractCompiler { ASTNode.incrementalOptionActive = getConfiguration().incremental() && getConfiguration().traceFlush(); LOGGER.fine(() -> "ASTNode.incrementalOptionActive = " + ASTNode.incrementalOptionActive); + // reuse "--List" option of JastAdd + ASTNode.JastAddList = getConfiguration().listType(); + ASTNode.usesMqtt = optionProtocols.hasValue(OPTION_PROTOCOL_MQTT); ASTNode.usesRest = optionProtocols.hasValue(OPTION_PROTOCOL_REST); return ragConnect; diff --git a/ragconnect.base/src/main/resources/ListAspect.mustache b/ragconnect.base/src/main/resources/ListAspect.mustache new file mode 100644 index 0000000000000000000000000000000000000000..764b5441862c96a010f6d07f86e2c1aad2dec81c --- /dev/null +++ b/ragconnect.base/src/main/resources/ListAspect.mustache @@ -0,0 +1,24 @@ +{{#hasTreeListEndpoints}} +public void {{JastAddList}}.serialize(com.fasterxml.jackson.core.JsonGenerator g) throws SerializationException { + try { + g.writeStartArray(); + for (T child : this) { + child.serialize(g); + } + g.writeEndArray(); + } catch (java.io.IOException e) { + throw new SerializationException("unable to serialize {{JastAddList}}", e); + } +} + +{{#typesForReceivingListEndpoints}} +public static {{JastAddList}}<{{Name}}> {{Name}}.deserializeList(com.fasterxml.jackson.databind.node.ArrayNode node) throws DeserializationException { + {{JastAddList}}<{{Name}}> result = new {{JastAddList}}<>(); + for (java.util.Iterator<com.fasterxml.jackson.databind.JsonNode> it = node.elements(); it.hasNext();) { + com.fasterxml.jackson.databind.JsonNode element = it.next(); + result.add(deserialize(element)); + } + return result; +} +{{/typesForReceivingListEndpoints}} +{{/hasTreeListEndpoints}} diff --git a/ragconnect.base/src/main/resources/MqttHandler.jadd b/ragconnect.base/src/main/resources/MqttHandler.jadd index f84d8657ca7db7be868205b11cd939f27c08965d..d859d38d819c17df941d4b0179fbeec28df6f83f 100644 --- a/ragconnect.base/src/main/resources/MqttHandler.jadd +++ b/ragconnect.base/src/main/resources/MqttHandler.jadd @@ -1,11 +1,7 @@ -import java.io.IOException; -import java.util.ArrayList; -import java.util.concurrent.TimeUnit; - aspect MqttHandler { public class MqttServerHandler { private final java.util.Map<String, MqttHandler> handlers = new java.util.HashMap<>(); - private final java.util.Map<ConnectToken, Object> tokensForRemoval = new java.util.HashMap<>(); + private final java.util.Map<RagConnectToken, java.util.function.BiConsumer<String, byte[]>> tokensForRemoval = new java.util.HashMap<>(); private long time; private java.util.concurrent.TimeUnit unit; private String name; @@ -16,7 +12,7 @@ public class MqttServerHandler { public MqttServerHandler(String name) { this.name = name; - setupWaitUntilReady(1, TimeUnit.SECONDS); + setupWaitUntilReady(1, java.util.concurrent.TimeUnit.SECONDS); } public void setupWaitUntilReady(long time, java.util.concurrent.TimeUnit unit) { @@ -24,7 +20,7 @@ public class MqttServerHandler { this.unit = unit; } - public MqttHandler resolveHandler(java.net.URI uri) throws IOException { + public MqttHandler resolveHandler(java.net.URI uri) throws java.io.IOException { MqttHandler handler = handlers.get(uri.getHost()); if (handler == null) { // first connect to that server @@ -40,33 +36,35 @@ public class MqttServerHandler { return handler; } - public ConnectToken newConnection(java.net.URI uri, java.util.function.Consumer<byte[]> callback) throws IOException { - ConnectToken connectToken = new ConnectToken(uri); - resolveHandler(uri).newConnection(extractTopic(uri), callback); + public boolean newConnection(RagConnectToken connectToken, java.util.function.BiConsumer<String, byte[]> callback) throws java.io.IOException { tokensForRemoval.put(connectToken, callback); - return connectToken; + return resolveHandler(connectToken.uri).newConnection(extractTopic(connectToken.uri), callback); } - public boolean disconnect(ConnectToken connectToken) throws IOException { + public boolean disconnect(RagConnectToken connectToken) throws java.io.IOException { MqttHandler handler = resolveHandler(connectToken.uri); return handler != null ? handler.disconnect(extractTopic(connectToken.uri), tokensForRemoval.get(connectToken)) : false; } - public void publish(java.net.URI uri, byte[] bytes) throws IOException { + public void publish(java.net.URI uri, byte[] bytes) throws java.io.IOException { resolveHandler(uri).publish(extractTopic(uri), bytes); } - public void publish(java.net.URI uri, byte[] bytes, boolean retain) throws IOException { + public void publish(java.net.URI uri, byte[] bytes, boolean retain) throws java.io.IOException { resolveHandler(uri).publish(extractTopic(uri), bytes, retain); } public void publish(java.net.URI uri, byte[] bytes, - org.fusesource.mqtt.client.QoS qos, boolean retain) throws IOException { + org.fusesource.mqtt.client.QoS qos, boolean retain) throws java.io.IOException { resolveHandler(uri).publish(extractTopic(uri), bytes, qos, retain); } public static String extractTopic(java.net.URI uri) { String path = uri.getPath(); + if (uri.getFragment() != null) { + // do not also append fragment, as it is illegal, that anything follows "#" in a mqtt topic anyway + path += "#"; + } if (path.charAt(0) == '/') { path = path.substring(1); } @@ -86,6 +84,10 @@ public class MqttServerHandler { * @author rschoene - Initial contribution */ public class MqttHandler { + private class PatternCallbackListPair { + java.util.regex.Pattern pattern; + java.util.List<java.util.function.BiConsumer<String, byte[]>> callbacks; + } private static final int DEFAULT_PORT = 1883; private final org.apache.logging.log4j.Logger logger; @@ -97,10 +99,12 @@ public class MqttHandler { private org.fusesource.mqtt.client.CallbackConnection connection; /** Whether we are connected yet */ private final java.util.concurrent.CountDownLatch readyLatch; + private final java.util.concurrent.locks.Lock astLock; private boolean sendWelcomeMessage = true; private org.fusesource.mqtt.client.QoS qos; /** Dispatch knowledge */ - private final java.util.Map<String, java.util.List<java.util.function.Consumer<byte[]>>> callbacks; + private final java.util.Map<String, java.util.List<java.util.function.BiConsumer<String, byte[]>>> normalCallbacks; + private final java.util.List<PatternCallbackListPair> wildcardCallbacks; public MqttHandler() { this("RagConnect"); @@ -109,9 +113,11 @@ public class MqttHandler { public MqttHandler(String name) { this.name = java.util.Objects.requireNonNull(name, "Name must be set"); this.logger = org.apache.logging.log4j.LogManager.getLogger(MqttHandler.class); - this.callbacks = new java.util.HashMap<>(); + this.normalCallbacks = new java.util.HashMap<>(); + this.wildcardCallbacks = new java.util.ArrayList<>(); this.readyLatch = new java.util.concurrent.CountDownLatch(1); this.qos = org.fusesource.mqtt.client.QoS.AT_LEAST_ONCE; + this.astLock = new java.util.concurrent.locks.ReentrantLock(); } public MqttHandler dontSendWelcomeMessage() { @@ -122,21 +128,21 @@ public class MqttHandler { /** * Sets the host to receive messages from, and connects to it. * @param host name of the host to connect to, format is either <code>"$name"</code> or <code>"$name:$port"</code> - * @throws IOException if could not connect, or could not subscribe to a topic + * @throws java.io.IOException if could not connect, or could not subscribe to a topic * @return self */ public MqttHandler setHost(String host) throws java.io.IOException { if (host.contains(":")) { int colon_index = host.indexOf(":"); return setHost(host.substring(0, colon_index), - Integer.parseInt(host.substring(colon_index + 1))); + Integer.parseInt(host.substring(colon_index + 1))); } return setHost(host, DEFAULT_PORT); } /** * Sets the host to receive messages from, and connects to it. - * @throws IOException if could not connect, or could not subscribe to a topic + * @throws java.io.IOException if could not connect, or could not subscribe to a topic * @return self */ public MqttHandler setHost(String host, int port) throws java.io.IOException { @@ -167,16 +173,19 @@ public class MqttHandler { org.fusesource.mqtt.client.Callback<org.fusesource.mqtt.client.Callback<Void>> ack) { // this method is called, whenever a MQTT message is received String topicString = topic.toString(); - java.util.List<java.util.function.Consumer<byte[]>> callbackList = new java.util.ArrayList<>(callbacks.get(topicString)); - if (callbackList == null || callbackList.isEmpty()) { + java.util.List<java.util.function.BiConsumer<String, byte[]>> callbackList = callbacksFor(topicString); + if (callbackList.isEmpty()) { logger.debug("Got a message at {}, but no callback to call. Forgot to subscribe?", topic); } else { byte[] message = body.toByteArray(); - for (java.util.function.Consumer<byte[]> callback : callbackList) { + for (java.util.function.BiConsumer<String, byte[]> callback : callbackList) { try { - callback.accept(message); + astLock.lock(); + callback.accept(topicString, message); } catch (Exception e) { logger.catching(e); + } finally { + astLock.unlock(); } } } @@ -199,20 +208,20 @@ public class MqttHandler { throwIf(error); // actually establish the connection - connection.connect(new org.fusesource.mqtt.client.Callback<Void>() { + connection.connect(new org.fusesource.mqtt.client.Callback<>() { @Override public void onSuccess(Void value) { if (MqttHandler.this.sendWelcomeMessage) { connection.publish("components", - (name + " is connected").getBytes(), - org.fusesource.mqtt.client.QoS.AT_LEAST_ONCE, - false, - new org.fusesource.mqtt.client.Callback<Void>() { - @Override - public void onSuccess(Void value) { - logger.debug("success sending welcome message"); - setReady(); - } + (name + " is connected").getBytes(), + org.fusesource.mqtt.client.QoS.AT_LEAST_ONCE, + false, + new org.fusesource.mqtt.client.Callback<>() { + @Override + public void onSuccess(Void value) { + logger.debug("success sending welcome message"); + setReady(); + } @Override public void onFailure(Throwable value) { @@ -233,6 +242,20 @@ public class MqttHandler { return this; } + private java.util.List<java.util.function.BiConsumer<String, byte[]>> callbacksFor(String topicString) { + java.util.List<java.util.function.BiConsumer<String, byte[]>> result = new java.util.ArrayList<>(); + java.util.List<java.util.function.BiConsumer<String, byte[]>> normalCallbackList = normalCallbacks.get(topicString); + if (normalCallbackList != null) { + result.addAll(normalCallbackList); + } + wildcardCallbacks.forEach(pair -> { + if (pair.pattern.matcher(topicString).matches()) { + result.addAll(pair.callbacks); + } + }); + return result; + } + public java.net.URI getHost() { return host; } @@ -251,61 +274,165 @@ public class MqttHandler { this.qos = qos; } + /** + * Establish a new connection for some topic. + * @param topic the topic to create a connection for, may contain the wildcards "*" and "#" + * @param callback the callback to run if a new message arrives for this topic + * @return true if successful stored this connection, false otherwise (e.g., on failed subscribe) + */ public boolean newConnection(String topic, java.util.function.Consumer<byte[]> callback) { + return newConnection(topic, (ignoredTopicString, bytes) -> callback.accept(bytes)); + } + + /** + * Establish a new connection for some topic. + * @param topic the topic to create a connection for, may contain the wildcards "*" and "#" + * @param callback the callback to run if a new message arrives for this topic + * @return true if successful stored this connection, false otherwise (e.g., on failed subscribe) + */ + public boolean newConnection(String topic, java.util.function.BiConsumer<String, byte[]> callback) { if (readyLatch.getCount() > 0) { System.err.println("Handler not ready"); return false; } // register callback logger.debug("new connection for {}", topic); - if (callbacks.get(topic) == null || callbacks.get(topic).isEmpty()) { - callbacks.put(topic, new java.util.ArrayList<>()); - + final boolean needSubscribe; + if (isWildcardTopic(topic)) { + String regex = regexForWildcardTopic(topic); + PatternCallbackListPair pairToAddTo = null; + for (PatternCallbackListPair pair : wildcardCallbacks) { + if (pair.pattern.pattern().equals(regex)) { + pairToAddTo = pair; + break; + } + } + if (pairToAddTo == null) { + pairToAddTo = new PatternCallbackListPair(); + pairToAddTo.pattern = java.util.regex.Pattern.compile(regex); + pairToAddTo.callbacks = new java.util.ArrayList<>(); + wildcardCallbacks.add(pairToAddTo); + } + needSubscribe = pairToAddTo.callbacks.isEmpty(); + pairToAddTo.callbacks.add(callback); + } else { // normal topic + java.util.List<java.util.function.BiConsumer<String, byte[]>> callbacksForTopic = normalCallbacks.get(topic); + if (callbacksForTopic == null) { + callbacksForTopic = new java.util.ArrayList<>(); + normalCallbacks.put(topic, callbacksForTopic); + } + needSubscribe = callbacksForTopic.isEmpty(); + callbacksForTopic.add(callback); + } + if (needSubscribe) { // subscribe at broker + java.util.concurrent.CountDownLatch operationFinished = new java.util.concurrent.CountDownLatch(1); + java.util.concurrent.atomic.AtomicReference<Boolean> success = new java.util.concurrent.atomic.AtomicReference<>(true); org.fusesource.mqtt.client.Topic[] topicArray = { new org.fusesource.mqtt.client.Topic(topic, this.qos) }; connection.getDispatchQueue().execute(() -> { - connection.subscribe(topicArray, new org.fusesource.mqtt.client.Callback<byte[]>() { + connection.subscribe(topicArray, new org.fusesource.mqtt.client.Callback<>() { @Override public void onSuccess(byte[] qoses) { logger.debug("Subscribed to {}, qoses: {}", topic, qoses); + operationFinished.countDown(); } @Override public void onFailure(Throwable cause) { logger.error("Could not subscribe to {}", topic, cause); + success.set(false); + operationFinished.countDown(); } }); }); + try { + operationFinished.await(2, java.util.concurrent.TimeUnit.SECONDS); + return success.get(); + } catch (InterruptedException e) { + return false; + } + } else { + return true; } - callbacks.get(topic).add(callback); - return true; } - public boolean disconnect(String topic, Object callback) { - java.util.List<java.util.function.Consumer<byte[]>> callbackList = callbacks.get(topic); - if (callbackList == null) { + private boolean isWildcardTopic(String topic) { + return topic.contains("*") || topic.contains("#"); + } + + private String regexForWildcardTopic(String topic) { + return topic.replace("*", "[^/]*").replace("#", ".*"); + } + + public boolean disconnect(String topic, java.util.function.BiConsumer<String, byte[]> callback) { + boolean needUnsubscribe = false; + java.util.concurrent.atomic.AtomicReference<Boolean> success = new java.util.concurrent.atomic.AtomicReference<>(true); + + final String topicToUnsubscribe; + + // check if wildcard is to be removed + if (isWildcardTopic(topic)) { + boolean topicRegistered = false; + String topicRegex = regexForWildcardTopic(topic); + for (PatternCallbackListPair pair : wildcardCallbacks) { + if (pair.pattern.pattern().equals(topicRegex)) { + topicRegistered = true; + // if still successful, update with whether callback could be removed + success.compareAndSet(true, pair.callbacks.remove(callback)); + // if no more callbacks left, unsubscribe and remove from list + if (pair.callbacks.isEmpty()) { + needUnsubscribe = true; + wildcardCallbacks.remove(pair.pattern); + } + break; + } + } + topicToUnsubscribe = topicRegistered ? topicRegex : null; + } else if (normalCallbacks.containsKey(topic)) { + topicToUnsubscribe = topic; + var normalCallbackList = normalCallbacks.get(topic); + // if still successful, update with whether callback could be removed + success.compareAndSet(true, normalCallbackList.remove(callback)); + // if no more callbacks left, unsubscribe and remove from list + if (normalCallbackList.isEmpty()) { + needUnsubscribe = true; + normalCallbacks.remove(topic); + } + } else { + topicToUnsubscribe = null; + } + + if (topicToUnsubscribe == null) { logger.warn("Disconnect for not connected topic '{}'", topic); return false; } - java.util.concurrent.atomic.AtomicReference<Boolean> success = new java.util.concurrent.atomic.AtomicReference<>(); - success.set(callbackList.remove(callback)); - if (callbackList.isEmpty()) { + + if (needUnsubscribe) { + java.util.concurrent.CountDownLatch operationFinished = new java.util.concurrent.CountDownLatch(1); // no callbacks anymore for this topic, unsubscribe from mqtt connection.getDispatchQueue().execute(() -> { - org.fusesource.hawtbuf.UTF8Buffer topicBuffer = org.fusesource.hawtbuf.Buffer.utf8(topic); + org.fusesource.hawtbuf.UTF8Buffer topicBuffer = org.fusesource.hawtbuf.Buffer.utf8(topicToUnsubscribe); org.fusesource.hawtbuf.UTF8Buffer[] topicArray = new org.fusesource.hawtbuf.UTF8Buffer[]{topicBuffer}; - connection.unsubscribe(topicArray, new org.fusesource.mqtt.client.Callback<Void>() { + connection.unsubscribe(topicArray, new org.fusesource.mqtt.client.Callback<>() { @Override public void onSuccess(Void value) { - // empty, all good + operationFinished.countDown(); } @Override public void onFailure(Throwable cause) { success.set(false); + logger.warn("Could not disconnect from {}", topic, cause); + operationFinished.countDown(); } }); }); + try { + operationFinished.await(2, java.util.concurrent.TimeUnit.SECONDS); + } catch (InterruptedException e) { + logger.catching(e); + success.set(false); + } } return success.get(); } @@ -334,7 +461,7 @@ public class MqttHandler { return; } connection.getDispatchQueue().execute(() -> { - connection.disconnect(new org.fusesource.mqtt.client.Callback<Void>() { + connection.disconnect(new org.fusesource.mqtt.client.Callback<>() { @Override public void onSuccess(Void value) { logger.info("Disconnected {} from {}", name, host); @@ -357,19 +484,24 @@ public class MqttHandler { } public void publish(String topic, byte[] bytes, org.fusesource.mqtt.client.QoS qos, boolean retain) { - connection.getDispatchQueue().execute(() -> { - connection.publish(topic, bytes, qos, retain, new org.fusesource.mqtt.client.Callback<Void>() { - @Override - public void onSuccess(Void value) { - logger.debug("Published some bytes to {}", topic); - } + try { + astLock.lock(); + connection.getDispatchQueue().execute(() -> { + connection.publish(topic, bytes, qos, retain, new org.fusesource.mqtt.client.Callback<>() { + @Override + public void onSuccess(Void value) { + logger.debug("Published some bytes to {}", topic); + } - @Override - public void onFailure(Throwable value) { - logger.warn("Could not publish on topic '{}'", topic, value); - } + @Override + public void onFailure(Throwable value) { + logger.warn("Could not publish on topic '{}'", topic, value); + } + }); }); - }); + } finally { + astLock.unlock(); + } } } } diff --git a/ragconnect.base/src/main/resources/RestHandler.jadd b/ragconnect.base/src/main/resources/RestHandler.jadd index b69bf71b73ba9d4c5a9f1998ee2dd92ede77561d..9187afdab7a15c0bd7dc10679991505874c8af61 100644 --- a/ragconnect.base/src/main/resources/RestHandler.jadd +++ b/ragconnect.base/src/main/resources/RestHandler.jadd @@ -2,7 +2,7 @@ import java.util.concurrent.TimeUnit;aspect RestHandler { public class RestServerHandler { private static final int DEFAULT_PORT = 4567; private final java.util.Map<Integer, RestHandler> handlers = new java.util.HashMap<>(); - private final java.util.Map<ConnectToken, Object> tokensForRemoval = new java.util.HashMap<>(); + private final java.util.Map<RagConnectToken, Object> tokensForRemoval = new java.util.HashMap<>(); private String name; public RestServerHandler() { @@ -25,21 +25,19 @@ public class RestServerHandler { return handler; } - public ConnectToken newPUTConnection(java.net.URI uri, java.util.function.Consumer<String> callback) { - ConnectToken connectToken = new ConnectToken(uri); - resolveHandler(uri).newPUTConnection(uri.getPath(), callback); + public boolean newPUTConnection(RagConnectToken connectToken, java.util.function.Consumer<String> callback) { tokensForRemoval.put(connectToken, callback); - return connectToken; + resolveHandler(connectToken.uri).newPUTConnection(connectToken.uri.getPath(), callback); + return true; } - public ConnectToken newGETConnection(java.net.URI uri, SupplierWithException<String> supplier) { - ConnectToken connectToken = new ConnectToken(uri); - resolveHandler(uri).newGETConnection(uri.getPath(), supplier); + public boolean newGETConnection(RagConnectToken connectToken, SupplierWithException<String> supplier) { tokensForRemoval.put(connectToken, supplier); - return connectToken; + resolveHandler(connectToken.uri).newGETConnection(connectToken.uri.getPath(), supplier); + return true; } - public boolean disconnect(ConnectToken connectToken) { + public boolean disconnect(RagConnectToken connectToken) { RestHandler handler = resolveHandler(connectToken.uri); return handler != null ? handler.disconnect(connectToken.uri.getPath(), tokensForRemoval.get(connectToken)) : false; } @@ -127,9 +125,15 @@ public class RestHandler { } public boolean disconnect(String path, Object callbackOrSupplier) { - // only one will succeed (or false will be returned) - return callbacks.getOrDefault(path, java.util.Collections.emptyList()).remove(callbackOrSupplier) || - suppliers.remove(path, callbackOrSupplier); + if (callbacks.getOrDefault(path, java.util.Collections.emptyList()).remove(callbackOrSupplier)) { + return true; + } + if (suppliers.remove(path, callbackOrSupplier)) { + // unmap the route + return spark.Spark.unmap(path); + } + System.err.println("Disconnect for not connected path '" + path + "'!"); + return false; } private String makeError(spark.Response response, int statusCode, String message) { diff --git a/ragconnect.base/src/main/resources/handleUri.mustache b/ragconnect.base/src/main/resources/handleUri.mustache index aa4176ef0b067bca3c54ca754096f633cadcfa71..ff0ea7af7f920ac09a14de75a4afe1882540ac94 100644 --- a/ragconnect.base/src/main/resources/handleUri.mustache +++ b/ragconnect.base/src/main/resources/handleUri.mustache @@ -4,7 +4,7 @@ try { uri = new java.net.URI({{connectParameterName}}); scheme = uri.getScheme(); host = uri.getHost(); - path = uri.getPath(); + path = uri.getPath() + (uri.getFragment() != null ? "#" : ""); } catch (java.net.URISyntaxException e) { System.err.println(e.getMessage()); // Maybe re-throw error? return false; diff --git a/ragconnect.base/src/main/resources/handler.mustache b/ragconnect.base/src/main/resources/handler.mustache index 41e42387f69239f054b8ddaafa80680b13ca7591..fe8d9459a0d1b977f7416da85ba3fb1c4d3e6b8f 100644 --- a/ragconnect.base/src/main/resources/handler.mustache +++ b/ragconnect.base/src/main/resources/handler.mustache @@ -14,15 +14,74 @@ aspect RagConnectHandler { {{#InUse}}{{FieldName}}.close();{{/InUse}} {{/Handlers}} } - class ConnectToken { + class RagConnectToken { static java.util.concurrent.atomic.AtomicLong counter = new java.util.concurrent.atomic.AtomicLong(0); final long id; final java.net.URI uri; - public ConnectToken(java.net.URI uri) { + final String entityName; + public RagConnectToken(java.net.URI uri, String entityName) { this.id = counter.incrementAndGet(); this.uri = uri; + this.entityName = entityName; } + } + class RagConnectTokenMap { + java.util.Map<ASTNode, java.util.List<RagConnectToken>> connectTokensSend = new java.util.HashMap<>(); + java.util.Map<ASTNode, java.util.List<RagConnectToken>> connectTokensReceive = new java.util.HashMap<>(); + void add(ASTNode node, boolean isReceive, RagConnectToken token) { + java.util.Map<ASTNode, java.util.List<RagConnectToken>> mapOfTokens = (isReceive ? connectTokensReceive : connectTokensSend); + mapOfTokens.computeIfAbsent(node, n -> new java.util.ArrayList<>()).add(token); + } + java.util.List<RagConnectToken> removeAll(ASTNode node, boolean isReceive, java.net.URI uri, String entityName) { + java.util.List<RagConnectToken> listOfTokens = (isReceive ? connectTokensReceive : connectTokensSend).get(node); + if (listOfTokens == null) { + return java.util.Collections.emptyList(); + } + java.util.List<RagConnectToken> tokensToRemove = listOfTokens.stream() + .filter(token -> token.uri.equals(uri) && token.entityName.equals(entityName)) + .collect(java.util.stream.Collectors.toList()); + listOfTokens.removeAll(tokensToRemove); + return tokensToRemove; + } + } + static RagConnectTokenMap ASTNode.connectTokenMap = new RagConnectTokenMap(); + + interface RagConnectDisconnectHandlerMethod { + boolean call(RagConnectToken token) throws java.io.IOException; + } + class RagConnectPublisher { + java.util.List<Runnable> senders = new java.util.ArrayList<>(); + java.util.Map<RagConnectToken, Runnable> tokenToSender; + byte[] lastValue; + + void add(Runnable sender, RagConnectToken connectToken) { + if (tokenToSender == null) { + tokenToSender = new java.util.HashMap<>(); + } + senders.add(sender); + tokenToSender.put(connectToken, sender); + } + + boolean remove(RagConnectToken token) { + if (tokenToSender == null) { + System.err.println("Removing sender before first addition for " + token.entityName + " at " + token.uri); + return false; + } + Runnable sender = tokenToSender.remove(token); + if (sender == null) { + System.err.println("Could not find connected sender for " + token.entityName + " at " + token.uri); + return false; + } + boolean success = senders.remove(sender); + if (senders.isEmpty()) { + lastValue = null; + } + return success; + } + + void run() { + senders.forEach(Runnable::run); + } } - static java.util.Map<ASTNode, java.util.Map<java.net.URI, ConnectToken>> ASTNode.connectTokens = new java.util.HashMap<>(); } diff --git a/ragconnect.base/src/main/resources/mappingApplication.mustache b/ragconnect.base/src/main/resources/mappingApplication.mustache index 47ec31ee58b27485a51e2b7e3a7de196cbe130f3..bc59e0b318dcd430b5f21fd0a67d157b35a4bb7c 100644 --- a/ragconnect.base/src/main/resources/mappingApplication.mustache +++ b/ragconnect.base/src/main/resources/mappingApplication.mustache @@ -1,7 +1,7 @@ -{{lastDefinitionToType}} {{lastResult}}; +{{{lastDefinitionToType}}} {{lastResult}}; try { {{#InnerMappingDefinitions}} - {{^last}}{{toType}} {{/last}}{{outputVarName}} = {{methodName}}({{inputVarName}}); + {{^last}}{{{toType}}} {{/last}}{{outputVarName}} = {{methodName}}({{inputVarName}}); {{/InnerMappingDefinitions}} } catch (RagConnectRejectMappingException e) { // do not print message in case of rejection diff --git a/ragconnect.base/src/main/resources/mappingDefinition.mustache b/ragconnect.base/src/main/resources/mappingDefinition.mustache index 47a9381e2e017b8cc7b7264db6784f2bab1227d2..09f161f37c3742850b09969d4eb85c7798d2d346 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 static {{{toType}}} ASTNode.{{methodName}}({{{fromType}}} {{fromVariableName}}) throws Exception { {{{content}}} } diff --git a/ragconnect.base/src/main/resources/ragConnectVersion.properties b/ragconnect.base/src/main/resources/ragConnectVersion.properties index cc1f011163b2d40f2569f47f8c4151154d2fab63..3f322c60cc672a9603f285be04dc8a2d53f8f03b 100644 --- a/ragconnect.base/src/main/resources/ragConnectVersion.properties +++ b/ragconnect.base/src/main/resources/ragConnectVersion.properties @@ -1,2 +1,2 @@ -#Thu Jun 03 11:17:05 CEST 2021 -version=0.3.1 +#Wed Nov 24 10:32:52 CET 2021 +version=0.3.2 diff --git a/ragconnect.base/src/main/resources/ragconnect.mustache b/ragconnect.base/src/main/resources/ragconnect.mustache index 50cd5cece4429e3529c64f5896548e50bbe564b0..dd509aa53b34e56f425777c16f5f9f3d34c2fc7a 100644 --- a/ragconnect.base/src/main/resources/ragconnect.mustache +++ b/ragconnect.base/src/main/resources/ragconnect.mustache @@ -35,6 +35,9 @@ aspect RagConnect { {{#TokenComponents}} {{> tokenComponent}} {{/TokenComponents}} + + {{> ListAspect}} + public void {{rootNodeName}}.ragconnectCheckIncremental() { {{#incrementalOptionActive}} // check if --tracing is active @@ -55,12 +58,12 @@ aspect RagConnectObserver { class RagConnectObserver implements ASTState.Trace.Receiver { class RagConnectObserverEntry { - final ConnectToken connectToken; + final RagConnectToken connectToken; final ASTNode node; final String attributeString; final Runnable attributeCall; - RagConnectObserverEntry(ConnectToken connectToken, ASTNode node, String attributeString, Runnable attributeCall) { + RagConnectObserverEntry(RagConnectToken connectToken, ASTNode node, String attributeString, Runnable attributeCall) { this.connectToken = connectToken; this.node = node; this.attributeString = attributeString; @@ -96,13 +99,13 @@ aspect RagConnectObserver { node.trace().setReceiver(this); } - void add(ConnectToken connectToken, ASTNode node, String attributeString, Runnable attributeCall) { + void add(RagConnectToken connectToken, ASTNode node, String attributeString, Runnable attributeCall) { {{#loggingEnabledForIncremental}} System.out.println("** observer add: " + node + " on " + attributeString); {{/loggingEnabledForIncremental}} observedNodes.add(new RagConnectObserverEntry(connectToken, node, attributeString, attributeCall)); } - void remove(ConnectToken connectToken) { + void remove(RagConnectToken connectToken) { observedNodes.removeIf(entry -> entry.connectToken.equals(connectToken)); } @Override diff --git a/ragconnect.base/src/main/resources/receiveDefinition.mustache b/ragconnect.base/src/main/resources/receiveDefinition.mustache index 10f519129c8dec8abe587a1bcb5876c4c0224745..c29eff1372604f7e302deaeaebb3bb0b83d21827 100644 --- a/ragconnect.base/src/main/resources/receiveDefinition.mustache +++ b/ragconnect.base/src/main/resources/receiveDefinition.mustache @@ -1,55 +1,144 @@ -public boolean {{parentTypeName}}.{{connectMethod}}(String {{connectParameterName}}) throws java.io.IOException { - {{>handleUri}} - java.util.function.Consumer<byte[]> consumer = message -> { +{{#typeIsList}} +{{^UseList}} +private int {{parentTypeName}}.{{resolveInListMethodName}}(String topic) { + for (int index = 0; index < getNum{{entityName}}(); index++) { + if (get{{entityName}}(index).get{{idTokenName}}().equals(topic)) { + return index; + } + } + return -1; +} +{{/UseList}} +{{/typeIsList}} + +/** + * Connects the receive endpoint {{entityName}}. +{{#typeIsList}}{{#isWithAdd}} + * New values are appended to the end of the list. +{{/isWithAdd}}{{/typeIsList}} + * @param {{connectParameterName}} string describing protocol and path as an URI +{{#typeIsList}}{{^UseList}}{{^isWithAdd}} + * @param index index of node in list to connect (the list is expected to have enough elements) +{{/isWithAdd}}{{/UseList}}{{/typeIsList}} + * @return true if connect was successful, false otherwise + * @throws java.io.IOException if connect failed + */ +public boolean {{parentTypeName}}.{{connectMethod}}(String {{connectParameterName}}{{#typeIsList}}{{^UseList}}{{^isWithAdd}}, int index{{/isWithAdd}}{{/UseList}}{{/typeIsList}}) throws java.io.IOException { + java.util.function.BiConsumer<String, byte[]> consumer = (topic, message) -> { {{> mappingApplication}} - {{#loggingEnabledForReads}} +{{#loggingEnabledForReads}} System.out.println("[Receive] " + {{connectParameterName}} + " -> {{entityName}} = " + {{lastResult}}); - {{/loggingEnabledForReads}} - {{#isTypeEndpointDefinition}} +{{/loggingEnabledForReads}} +{{#isTypeEndpointDefinition}} {{lastResult}}.treeResolveAll(); - {{/isTypeEndpointDefinition}} + {{#typeIsList}} + {{#UseList}} + {{#isWithAdd}} + {{getterMethod}}().addAll({{lastResult}}); + {{/isWithAdd}} + {{^isWithAdd}} set{{entityName}}({{lastResult}}); + {{/isWithAdd}} + {{/UseList}} + {{^UseList}} + {{lastResult}}.set{{idTokenName}}(topic); + {{#isWithAdd}} + {{getterMethod}}().add({{lastResult}}); + {{/isWithAdd}} + {{^isWithAdd}} + set{{entityName}}({{lastResult}}, index); + {{/isWithAdd}} + {{/UseList}} + {{/typeIsList}} + {{^typeIsList}} + set{{entityName}}({{lastResult}}); + {{/typeIsList}} +{{/isTypeEndpointDefinition}} +{{^isTypeEndpointDefinition}} + set{{entityName}}({{lastResult}}); +{{/isTypeEndpointDefinition}} + }; + return {{internalConnectMethod}}({{connectParameterName}}, consumer); +} + +{{#typeIsList}}{{^UseList}}{{^isWithAdd}} +/** + * Connects the receive endpoint {{entityName}} using a "wildcard" URI (if supported by the chosen protocol). + * @param {{connectParameterName}} string describing protocol and path as an URI + * @return true if connect was successful, false otherwise + * @throws java.io.IOException if connect failed +*/ +public boolean {{parentTypeName}}.{{connectMethod}}(String {{connectParameterName}}) throws java.io.IOException { + java.util.function.BiConsumer<String, byte[]> consumer = (topic, message) -> { + {{> mappingApplication}} +{{#loggingEnabledForReads}} + System.out.println("[Receive] " + {{connectParameterName}} + " (" + topic + ") -> {{entityName}} = " + {{lastResult}}); +{{/loggingEnabledForReads}} + {{lastResult}}.set{{idTokenName}}(topic); + int resolvedIndex = {{resolveInListMethodName}}(topic); + if (resolvedIndex == -1) { + add{{entityName}}({{lastResult}}); + } else { + set{{entityName}}({{lastResult}}, resolvedIndex); + } }; - ConnectToken connectToken; + return {{internalConnectMethod}}({{connectParameterName}}, consumer); +} +{{/isWithAdd}}{{/UseList}}{{/typeIsList}} + +private boolean {{parentTypeName}}.{{internalConnectMethod}}(String {{connectParameterName}}, + java.util.function.BiConsumer<String, byte[]> consumer) throws java.io.IOException { + {{>handleUri}} + RagConnectToken connectToken = new RagConnectToken(uri, "{{entityName}}"); + boolean success; switch (scheme) { {{#usesMqtt}} case "mqtt": - connectToken = {{mqttHandlerAttribute}}().newConnection(uri, consumer); - if (connectToken == null) { - return false; - } + success = {{mqttHandlerAttribute}}().newConnection(connectToken, consumer); break; {{/usesMqtt}} {{#usesRest}} case "rest": - connectToken = {{restHandlerAttribute}}().newPUTConnection(uri, input -> { - consumer.accept(input.getBytes()); + success = {{restHandlerAttribute}}().newPUTConnection(connectToken, input -> { + // TODO wildcard-topic not supported yet + consumer.accept("", input.getBytes()); }); - if (connectToken == null) { - return false; - } break; {{/usesRest}} default: System.err.println("Unknown protocol '" + scheme + "'."); - return false; + success = false; + } + if (success) { + connectTokenMap.add(this, true, connectToken); } - connectTokens.computeIfAbsent(this, astNode -> new java.util.HashMap<java.net.URI, ConnectToken>()) - .put(uri, connectToken); - return true; + return success; } public boolean {{parentTypeName}}.{{disconnectMethod}}(String {{connectParameterName}}) throws java.io.IOException { {{>handleUri}} + java.util.List<RagConnectToken> connectTokens = connectTokenMap.removeAll(this, true, uri, "{{entityName}}"); + if (connectTokens.isEmpty()) { + System.err.println("Disconnect called without connection for receiving " + this + ".{{entityName}} to '" + {{connectParameterName}} + "'!"); + return false; + } + RagConnectDisconnectHandlerMethod disconnectingMethod; switch (scheme) { {{#usesMqtt}} - case "mqtt": return {{mqttHandlerAttribute}}().disconnect(connectTokens.get(this).get(uri)); + case "mqtt": disconnectingMethod = {{mqttHandlerAttribute}}()::disconnect; + break; {{/usesMqtt}} {{#usesRest}} - case "rest": return {{restHandlerAttribute}}().disconnect(connectTokens.get(this).get(uri)); + case "rest": disconnectingMethod = {{restHandlerAttribute}}()::disconnect; + break; {{/usesRest}} default: - System.err.println("Unknown protocol '" + scheme + "'."); + System.err.println("Unknown protocol '" + scheme + "' in '" + {{connectParameterName}} + "' for disconnecting {{parentTypeName}}.{{entityName}}"); return false; } + boolean success = true; + for (RagConnectToken connectToken : connectTokens) { + success &= disconnectingMethod.call(connectToken); + } + return success; } diff --git a/ragconnect.base/src/main/resources/sendDefinition.mustache b/ragconnect.base/src/main/resources/sendDefinition.mustache index 62cfb7d5016fe5ed6ac2abadab45e9fdc3a45228..5cee6a483cda29519905918a505337db7779fce6 100644 --- a/ragconnect.base/src/main/resources/sendDefinition.mustache +++ b/ragconnect.base/src/main/resources/sendDefinition.mustache @@ -1,85 +1,83 @@ -private Runnable {{parentTypeName}}.{{sender}} = null; -private byte[] {{parentTypeName}}.{{lastValue}} = null; +private RagConnectPublisher {{parentTypeName}}.{{sender}} = new RagConnectPublisher(); public boolean {{parentTypeName}}.{{connectMethod}}(String {{connectParameterName}}, boolean writeCurrentValue) throws java.io.IOException { {{>handleUri}} - ConnectToken connectToken; - if (connectTokens.computeIfAbsent(this, astNode -> new java.util.HashMap<java.net.URI, ConnectToken>()) - .get(uri) != null) { - System.err.println("Already connected for " + uri + " on " + this + "!"); - return true; - } + RagConnectToken connectToken = new RagConnectToken(uri, "{{entityName}}"); + boolean success; switch (scheme) { {{#usesMqtt}} case "mqtt": final MqttHandler handler = {{mqttHandlerAttribute}}().resolveHandler(uri); final String topic = {{mqttHandlerAttribute}}().extractTopic(uri); - {{sender}} = () -> { + {{sender}}.add(() -> { {{#loggingEnabledForWrites}} - System.out.println("[Send] {{entityName}} = " + get{{entityName}}() + " -> " + {{connectParameterName}}); + System.out.println("[Send] {{entityName}} = " + {{getterMethod}}() + " -> " + {{connectParameterName}}); {{/loggingEnabledForWrites}} handler.publish(topic, {{lastValue}}); - }; + }, connectToken); {{updateMethod}}(); if (writeCurrentValue) { {{writeMethod}}(); } - connectToken = new ConnectToken(uri); + success = true; break; {{/usesMqtt}} {{#usesRest}} case "rest": - connectToken = {{restHandlerAttribute}}().newGETConnection(uri, () -> { + success = {{restHandlerAttribute}}().newGETConnection(connectToken, () -> { {{updateMethod}}(); return new String({{lastValue}}); }); - if (connectToken == null) { - return false; - } break; {{/usesRest}} default: System.err.println("Unknown protocol '" + scheme + "'."); - return false; + success = false; } - connectTokens.computeIfAbsent(this, astNode -> new java.util.HashMap<java.net.URI, ConnectToken>()) - .put(uri, connectToken); - {{#incrementalOptionActive}} - _ragConnectObserver().add(connectToken, this, "get{{entityName}}", () -> { - if (this.{{updateMethod}}()) { - this.{{writeMethod}}(); - } - }); - {{/incrementalOptionActive}} - return true; + if (success) { + connectTokenMap.add(this, false, connectToken); + {{#incrementalOptionActive}} + _ragConnectObserver().add(connectToken, this, "{{getterMethod}}", () -> { + if (this.{{updateMethod}}()) { + this.{{writeMethod}}(); + } + }); + {{/incrementalOptionActive}} + } + return success; } public boolean {{parentTypeName}}.{{disconnectMethod}}(String {{connectParameterName}}) throws java.io.IOException { {{>handleUri}} - ConnectToken connectToken = connectTokens.get(this).remove(uri); - if (connectToken == null) { - System.err.println("Disconnect without connect for " + uri + " on " + this + "!"); + java.util.List<RagConnectToken> connectTokens = connectTokenMap.removeAll(this, false, uri, "{{entityName}}"); + if (connectTokens.isEmpty()) { + System.err.println("Disconnect called without connection for sending " + this + ".{{entityName}} to '" + {{connectParameterName}} + "'!"); + return false; } {{#incrementalOptionActive}} - _ragConnectObserver().remove(connectToken); + connectTokens.forEach(token -> _ragConnectObserver().remove(token)); {{/incrementalOptionActive}} + RagConnectDisconnectHandlerMethod disconnectingMethod; switch (scheme) { {{#usesMqtt}} case "mqtt": - {{sender}} = null; - {{lastValue}} = null; + disconnectingMethod = {{sender}}::remove; break; {{/usesMqtt}} {{#usesRest}} case "rest": - {{restHandlerAttribute}}().disconnect(connectToken); + disconnectingMethod = {{restHandlerAttribute}}()::disconnect; break; {{/usesRest}} default: - System.err.println("Unknown protocol '" + scheme + "'."); + System.err.println("Unknown protocol '" + scheme + "' in '" + {{connectParameterName}} + "' for disconnecting {{parentTypeName}}.{{entityName}}"); return false; } - return true; + boolean success = true; + for (RagConnectToken connectToken : connectTokens) { + success &= disconnectingMethod.call(connectToken); + } + return success; } protected boolean {{parentTypeName}}.{{updateMethod}}() { diff --git a/ragconnect.tests/build.gradle b/ragconnect.tests/build.gradle index 485a59d61efefe9f77592c1f62ca485db087da98..ee6da58c40c1bb690258d3958ac53a6ec44ac362 100644 --- a/ragconnect.tests/build.gradle +++ b/ragconnect.tests/build.gradle @@ -9,7 +9,7 @@ buildscript { } dependencies { classpath 'org.jastadd:jastaddgradle:1.13.3' - classpath 'org.jastadd.preprocessor:testing:0.2.8' + classpath 'org.jastadd.preprocessor:testing:0.2.10' } } @@ -51,7 +51,7 @@ dependencies { testImplementation group: 'org.fusesource.mqtt-client', name: 'mqtt-client', version: '1.15' // rest and client - testImplementation group: 'com.sparkjava', name: 'spark-core', version: '2.9.2' + testImplementation group: 'com.sparkjava', name: 'spark-core', version: '2.9.3' testImplementation group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: '2.11.2' testImplementation group: 'org.glassfish.jersey.core', name: 'jersey-client', version: '2.31' testImplementation group: 'org.glassfish.jersey.inject', name: 'jersey-hk2', version: '2.31' @@ -411,3 +411,160 @@ task compileTreeAllowedTokensIncremental(type: RagConnectTest) { '--flush=full'] } } + +// --- Test: list-manual --- +task compileListManual(type: RagConnectTest, dependsOn: ':ragconnect.base:jar') { + ragconnect { + outputDir = file('src/test/02-after-ragconnect/list') + inputFiles = [file('src/test/01-input/list/Test.relast'), + file('src/test/01-input/list/Test.connect'), + file('src/test/01-input/list/TestDependencies.connect')] + rootNode = 'Root' + } + relast { + useJastAddNames = true + grammarName = 'src/test/03-after-relast/list/list' + serializer = 'jackson' + } + jastadd { + jastAddList = 'JastAddList' + packageName = 'list.ast' + inputFiles = [file('src/test/01-input/list/Test.jadd')] + } +} + +// --- Test: list-incremental --- +task compileListIncremental(type: RagConnectTest, dependsOn: ':ragconnect.base:jar') { + ragconnect { + outputDir = file('src/test/02-after-ragconnect/listInc') + inputFiles = [file('src/test/01-input/list/Test.relast'), + file('src/test/01-input/list/Test.connect')] + rootNode = 'Root' + } + relast { + useJastAddNames = true + grammarName = 'src/test/03-after-relast/listInc/listInc' + serializer = 'jackson' + } + jastadd { + jastAddList = 'JastAddList' + packageName = 'listInc.ast' + inputFiles = [file('src/test/01-input/list/Test.jadd')] + extraOptions = ['--tracing=cache,flush', + '--incremental=param', + '--cache=all', + '--rewrite=cnta', + '--flush=full'] + } +} + +// --- Test: singleList-manual --- +task compileSingleListManual(type: RagConnectTest, dependsOn: ':ragconnect.base:jar') { + ragconnect { + outputDir = file('src/test/02-after-ragconnect/singleList') + inputFiles = [file('src/test/01-input/singleList/Test.relast'), + file('src/test/01-input/singleList/Test.connect'), + file('src/test/01-input/singleList/TestDependencies.connect')] + rootNode = 'Root' + } + relast { + useJastAddNames = true + grammarName = 'src/test/03-after-relast/singleList/singleList' + serializer = 'jackson' + } + jastadd { + jastAddList = 'JastAddList' + packageName = 'singleList.ast' + inputFiles = [file('src/test/01-input/singleList/Test.jadd')] + } +} + +// --- Test: singleList-incremental --- +task compileSingleListIncremental(type: RagConnectTest, dependsOn: ':ragconnect.base:jar') { + ragconnect { + outputDir = file('src/test/02-after-ragconnect/singleListInc') + inputFiles = [file('src/test/01-input/singleList/Test.relast'), + file('src/test/01-input/singleList/Test.connect')] + rootNode = 'Root' + } + relast { + useJastAddNames = true + grammarName = 'src/test/03-after-relast/singleListInc/singleListInc' + serializer = 'jackson' + } + jastadd { + jastAddList = 'JastAddList' + packageName = 'singleListInc.ast' + inputFiles = [file('src/test/01-input/singleList/Test.jadd')] + extraOptions = ['--tracing=cache,flush', + '--incremental=param', + '--cache=all', + '--rewrite=cnta', + '--flush=full'] + } +} + +// --- Test: singleListVariant-manual --- +task compileSingleListVariantManual(type: RagConnectTest, dependsOn: ':ragconnect.base:jar') { + ragconnect { + outputDir = file('src/test/02-after-ragconnect/singleListVariant') + inputFiles = [file('src/test/01-input/singleListVariant/Test.relast'), + file('src/test/01-input/singleListVariant/Test.connect'), + file('src/test/01-input/singleListVariant/TestDependencies.connect')] + rootNode = 'Root' + } + relast { + useJastAddNames = true + grammarName = 'src/test/03-after-relast/singleListVariant/singleListVariant' + serializer = 'jackson' + } + jastadd { + jastAddList = 'JastAddList' + packageName = 'singleListVariant.ast' + inputFiles = [file('src/test/01-input/singleListVariant/Test.jadd')] + } +} + +// --- Test: singleListVariant-incremental --- +task compileSingleListVariantIncremental(type: RagConnectTest, dependsOn: ':ragconnect.base:jar') { + ragconnect { + outputDir = file('src/test/02-after-ragconnect/singleListVariantInc') + inputFiles = [file('src/test/01-input/singleListVariant/Test.relast'), + file('src/test/01-input/singleListVariant/Test.connect')] + rootNode = 'Root' + } + relast { + useJastAddNames = true + grammarName = 'src/test/03-after-relast/singleListVariantInc/singleListVariantInc' + serializer = 'jackson' + } + jastadd { + jastAddList = 'JastAddList' + packageName = 'singleListVariantInc.ast' + inputFiles = [file('src/test/01-input/singleListVariant/Test.jadd')] + extraOptions = ['--tracing=cache,flush', + '--incremental=param', + '--cache=all', + '--rewrite=cnta', + '--flush=full'] + } +} + +task cleanCurrentManualTest(type: Delete) { +// delete "src/test/02-after-ragconnect/singleListVariant" +// delete "src/test/03-after-relast/singleListVariant" +// delete "src/test/java-gen/singleListVariant/ast" + delete "src/test/02-after-ragconnect/singleList" + delete "src/test/03-after-relast/singleList" + delete "src/test/java-gen/singleList/ast" +} +task cleanCurrentIncrementalTest(type: Delete) { +// delete "src/test/02-after-ragconnect/singleListVariantInc" +// delete "src/test/03-after-relast/singleListVariantInc" +// delete "src/test/java-gen/singleListVariantInc/ast" + delete "src/test/02-after-ragconnect/singleListInc" + delete "src/test/03-after-relast/singleListInc" + delete "src/test/java-gen/singleListInc/ast" +} +compileSingleListManual.dependsOn cleanCurrentManualTest +compileSingleListIncremental.dependsOn cleanCurrentIncrementalTest diff --git a/ragconnect.tests/src/test/01-input/list/README.md b/ragconnect.tests/src/test/01-input/list/README.md new file mode 100644 index 0000000000000000000000000000000000000000..7634a3d2069a10b5c07bef272f757bd5ee2df8c9 --- /dev/null +++ b/ragconnect.tests/src/test/01-input/list/README.md @@ -0,0 +1,34 @@ +# List + +Idea: send and receive lists of subtrees. +Once without incremental evaluation (i.e., using manual dependencies), and the other time with incremental evaluation + +## Execution-Model + +``` +SenderRoot ReceiverRoot +|- A* ---( mqtt: a ) ---+------> A* --------------------| +| \------> WidthAddFromA:A* ------| +|- SingleA:A* ,-> FromSingleA:A* --------| + \---( mqtt: single-a ) -+-> WithAddFromSingleA:A* -| +``` + +## Execution-Trace (SendInitialValue) + +| Input | # | A* | WidthAddFromA | FromSingleA | WithAddFromSingleA:A | +|---|---|---|---|---|---| +| 0 | 1 | [] | [0] | [] | [0] | +| 1 | 2 | [1] | [1] | [1] | [0,1] | +| 1 | 2 | [1] | [1] | [1] | [0,1] | +| 2 | 3 | [1,2] | [2] | [1,1,2] | [0,1,2] | +| 3 | 4 | [1,2,3] | [3] | [1,1,2,1,2,3] | [0,1,2,3] | + +## Execution-Trace (OnlyUpdate) + +| Input | # | A* | WidthAddFromA | FromSingleA | WithAddFromSingleA:A | +|---|---|---|---|---|---| +| - | 0 | [] | [] | [] | [] | +| 1 | 1 | [1] | [1] | [1] | [1] | +| 1 | 1 | [1] | [1] | [1] | [1] | +| 2 | 2 | [1,2] | [2] | [1,1,2] | [1,2] | +| 3 | 3 | [1,2,3] | [3] | [1,1,2,1,2,3] | [1,2,3] | diff --git a/ragconnect.tests/src/test/01-input/list/Test.connect b/ragconnect.tests/src/test/01-input/list/Test.connect new file mode 100644 index 0000000000000000000000000000000000000000..c0efaea9d5ad307c0c43b7d577b1cff5884c0d8b --- /dev/null +++ b/ragconnect.tests/src/test/01-input/list/Test.connect @@ -0,0 +1,6 @@ +send list SenderRoot.A ; +send list SenderRoot.SingleA ; +receive list ReceiverRoot.A ; +receive list ReceiverRoot.FromSingleA ; +receive list with add ReceiverRoot.WithAddFromA ; +receive list with add ReceiverRoot.WithAddFromSingleA ; diff --git a/ragconnect.tests/src/test/01-input/list/Test.jadd b/ragconnect.tests/src/test/01-input/list/Test.jadd new file mode 100644 index 0000000000000000000000000000000000000000..9d8ab5395a6c58e790023f902e53bae5763de76e --- /dev/null +++ b/ragconnect.tests/src/test/01-input/list/Test.jadd @@ -0,0 +1,37 @@ +aspect Computation { + syn JastAddList<A> SenderRoot.getAList() { + var result = new JastAddList<A>(); + for (int i = 1; i <= getInput(); i++) { + A a = new A().setID(i); + B b = new B().setID(i + 1); + a.addB(b); + result.addChild(a); + } + return result; + } + syn JastAddList<A> SenderRoot.getSingleAList() { + var result = new JastAddList<A>(); + A a = new A().setID(getInput()); + result.addChild(a); + return result; + } + + syn boolean ASTNode.isNameable() = false; + eq Nameable.isNameable() = true; +} + +aspect Testing { + class ReceiverRoot implements org.jastadd.ragconnect.tests.list.AbstractListTest.TestWrapperReceiverRoot {} + class A implements org.jastadd.ragconnect.tests.list.AbstractListTest.TestWrapperA {} + class B implements org.jastadd.ragconnect.tests.list.AbstractListTest.TestWrapperB {} + class JastAddList<T> implements org.jastadd.ragconnect.tests.list.AbstractListTest.TestWrapperJastAddList<T> {} +} + +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 Nameable.customID() { + return getClass().getSimpleName() + getID(); + } +} diff --git a/ragconnect.tests/src/test/01-input/list/Test.relast b/ragconnect.tests/src/test/01-input/list/Test.relast new file mode 100644 index 0000000000000000000000000000000000000000..2680b5cd43fc68d144386fc4494a52cc1ed5e002 --- /dev/null +++ b/ragconnect.tests/src/test/01-input/list/Test.relast @@ -0,0 +1,8 @@ +Root ::= SenderRoot* ReceiverRoot* ; + +Nameable ::= <ID:int> ; +SenderRoot : Nameable ::= <Input:int> /A*/ /SingleA:A*/ ; + +ReceiverRoot : Nameable ::= A* FromSingleA:A* WithAddFromA:A* WithAddFromSingleA:A* ; +A : Nameable ::= B* ; +B : Nameable ; diff --git a/ragconnect.tests/src/test/01-input/list/TestDependencies.connect b/ragconnect.tests/src/test/01-input/list/TestDependencies.connect new file mode 100644 index 0000000000000000000000000000000000000000..bce44247ea8db263a29ce66f08bb10346c3c4245 --- /dev/null +++ b/ragconnect.tests/src/test/01-input/list/TestDependencies.connect @@ -0,0 +1,2 @@ +SenderRoot.A canDependOn SenderRoot.Input as InputDependencyToA ; +SenderRoot.SingleA canDependOn SenderRoot.Input as InputDependencyToSingleA ; diff --git a/ragconnect.tests/src/test/01-input/singleList/README.md b/ragconnect.tests/src/test/01-input/singleList/README.md new file mode 100644 index 0000000000000000000000000000000000000000..6a76bb4d8457d1f246edaeb96d6d72048ef4ced1 --- /dev/null +++ b/ragconnect.tests/src/test/01-input/singleList/README.md @@ -0,0 +1,47 @@ +# Single List + +Idea: send and receive single values for lists of subtrees. +Once without incremental evaluation (i.e., using manual dependencies), and the other time with incremental evaluation + +## Execution-Model + +``` +SenderRoot ReceiverRoot +|- A1 --( a/1 ) --\ | +|- A2 --( a/2 ) --+=\ /--> A* --------------| +|- A3 --( a/3 ) ----+==+--> WithAdd:A* ------| +|- A4 --( a/4 ) --+=/ | +|- IO --( a/5 ) --/ | + /--> UsingWc:A* ------| + ( a/# ) -+--> UsingWcWithA:A* -| +``` + +## Computation + +A _n_ = Input _n_ + 1, e.g., A1 = Input1 + 1 + +## Execution-Trace (SendInitialValue) + +| Input | [A1,A2,A3,A4,IO] | # | A* | UsingWcA | WithAddA | UsingWcWithAddA:A | +|---|---|---|---|---|---|---| +| * | [1,2,3,4,0] | 5 | [1,2,3,4,0] | [1,2,3,4,0] | [1,2,3,4,0] | [1,2,3,4,0] | +| I1:1 | [2,2,3,4,0] | 6 | [2,2,3,4,0] | [2,2,3,4,0] | [1,2,3,4,0,2] | [1,2,3,4,0,2] | +| I1:1 | [2,2,3,4,0] | 6 | [2,2,3,4,0] | [2,2,3,4,0] | [1,2,3,4,0,2] | [1,2,3,4,0,2] | +| I1:2 | [3,2,3,4,0] | 7 | [3,2,3,4,0] | [3,2,3,4,0] | [1,2,3,4,0,2,3] | [1,2,3,4,0,2,3] | +| IO:5 | [3,2,3,4,5] | 8 | [3,2,3,4,5] | [3,2,3,4,5] | [1,2,3,4,0,2,3,5] | [1,2,3,4,0,2,3,5] +| I3:4 | [3,2,7,4,5] | 9 | [3,2,7,4,5] | [3,2,7,4,5] | [1,2,3,4,0,2,3,5,7] | [1,2,3,4,0,2,3,5,7] | + +*: (1:0, 2:0, 3:0, 4:0, 5:0) + +## Execution-Trace (OnlyUpdate) + +| Input | [A1,A2,A3,A4,IO] | # | A* | UsingWcA | WithAddA | UsingWcWithAddA:A | +|---|---|---|---|---|---|---| +| * | [-,-,-,-,-] | 0 | [0,0,0,0,0] | [] | [] | [] | +| I1:1 | [2,-,-,-,-] | 1 | [2,0,0,0,0] | [2] | [2] | [2] | +| I1:1 | [2,-,-,-,-] | 1 | [2,0,0,0,0] | [2] | [2] | [2] | +| I1:2 | [3,-,-,-,-] | 2 | [3,0,0,0,0] | [3] | [2,3] | [2,3] | +| IO:5 | [2,-,-,-,5] | 3 | [3,0,0,0,5] | [3,5] | [2,3,5] | [2,3,5] +| I3:4 | [2,-,7,-,5] | 4 | [3,0,7,0,5] | [3,5,7] | [2,3,5,7] | [2,3,5,7] | + +*: (1:0, 2:0, 3:0, 4:0, 5:0) diff --git a/ragconnect.tests/src/test/01-input/singleList/Test.connect b/ragconnect.tests/src/test/01-input/singleList/Test.connect new file mode 100644 index 0000000000000000000000000000000000000000..21b2796544ae65e96da5b03f088e5f7966620a7f --- /dev/null +++ b/ragconnect.tests/src/test/01-input/singleList/Test.connect @@ -0,0 +1,14 @@ +send tree SenderRoot.A1 ; +send tree SenderRoot.A2 ; +send tree SenderRoot.A3 ; +send tree SenderRoot.A4 ; +send SenderRoot.InOutput using IntToA ; + +receive tree ReceiverRoot.A ; +receive tree ReceiverRoot.UsingWildcardA ; +receive tree with add ReceiverRoot.WithAddA ; +receive tree with add ReceiverRoot.UsingWildcardWithAddA ; + +IntToA maps int i to A {: + return new A().setID(i); +:} diff --git a/ragconnect.tests/src/test/01-input/singleList/Test.jadd b/ragconnect.tests/src/test/01-input/singleList/Test.jadd new file mode 100644 index 0000000000000000000000000000000000000000..005b419bb1f625a460e219079dcd3a5361b5844d --- /dev/null +++ b/ragconnect.tests/src/test/01-input/singleList/Test.jadd @@ -0,0 +1,25 @@ +aspect Computation { + syn A SenderRoot.getA1() = new A().setID(getInput1() + 1); + syn A SenderRoot.getA2() = new A().setID(getInput2() + 2); + syn A SenderRoot.getA3() = new A().setID(getInput3() + 3); + syn A SenderRoot.getA4() = new A().setID(getInput4() + 4); + + syn boolean ASTNode.isNameable() = false; + eq Nameable.isNameable() = true; +} + +aspect Testing { + class SenderRoot implements org.jastadd.ragconnect.tests.singleList.AbstractSingleListTest.TestWrapperSenderRoot {} + class ReceiverRoot implements org.jastadd.ragconnect.tests.singleList.AbstractSingleListTest.TestWrapperReceiverRoot {} + class A implements org.jastadd.ragconnect.tests.singleList.AbstractSingleListTest.TestWrapperA {} + class JastAddList<T> implements org.jastadd.ragconnect.tests.singleList.AbstractSingleListTest.TestWrapperJastAddList<T> {} +} + +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 Nameable.customID() { + return getClass().getSimpleName() + getID(); + } +} diff --git a/ragconnect.tests/src/test/01-input/singleList/Test.relast b/ragconnect.tests/src/test/01-input/singleList/Test.relast new file mode 100644 index 0000000000000000000000000000000000000000..0d97bb6c6b2fa4be6a4628b5e7e45926817364d0 --- /dev/null +++ b/ragconnect.tests/src/test/01-input/singleList/Test.relast @@ -0,0 +1,11 @@ +Root ::= SenderRoot* ReceiverRoot* ; + +Nameable ::= <ID:int> ; +SenderRoot : Nameable ::= <Input1:int> /A1:A/ + <Input2:int> /A2:A/ + <Input3:int> /A3:A/ + <Input4:int> /A4:A/ + <InOutput:int> ; + +ReceiverRoot : Nameable ::= A* UsingWildcardA:A* WithAddA:A* UsingWildcardWithAddA:A* ; +A : Nameable ; diff --git a/ragconnect.tests/src/test/01-input/singleList/TestDependencies.connect b/ragconnect.tests/src/test/01-input/singleList/TestDependencies.connect new file mode 100644 index 0000000000000000000000000000000000000000..f1429b6aa1cee28ca3d2a1421060af74a28fbcf8 --- /dev/null +++ b/ragconnect.tests/src/test/01-input/singleList/TestDependencies.connect @@ -0,0 +1,4 @@ +SenderRoot.A1 canDependOn SenderRoot.Input1 as InputDependencyToA1 ; +SenderRoot.A2 canDependOn SenderRoot.Input2 as InputDependencyToA2 ; +SenderRoot.A3 canDependOn SenderRoot.Input3 as InputDependencyToA3 ; +SenderRoot.A4 canDependOn SenderRoot.Input4 as InputDependencyToA4 ; diff --git a/ragconnect.tests/src/test/01-input/singleListVariant/README.md b/ragconnect.tests/src/test/01-input/singleListVariant/README.md new file mode 100644 index 0000000000000000000000000000000000000000..4004ac42c136c25f2447efe5c1161c98754f32c1 --- /dev/null +++ b/ragconnect.tests/src/test/01-input/singleListVariant/README.md @@ -0,0 +1,61 @@ +# Single List + +Idea: send and receive single values for lists of subtrees. +Test different variants of the structure/shape of the send/received value. + +## Execution-Model + +TODO: check again (old model copied from `singleList`) + +``` +SenderRoot/ReceiverRoot + |- T_Empty ::= /* empty */ ; + |- T_Token ::= <Value:String> ; + |- T_OneChild ::= Other ; + |- T_OneOpt ::= [Other] ; + |- T_OneList ::= Other* ; + |- T_TwoChildren ::= Left:Other Right:Other ; + |- T_OneOfEach ::= First:Other [Second:Other] Third:Other* <Fourth:String> ; + |- abstract T_Abstract ::= <ValueAbstract> ; +``` + +## Computation + +``` +T.ID = Input +T.token = Input +T.Other.ID = Input + 1 +``` + +## Execution-Trace (SendInitialValue) + +Inputs: + +- 1 +- 1 +- 2 +- 3 + +| Input | [A1,A2,A3,A4,IO] | # | A* | UsingWcA | WithAddA | UsingWcWithAddA:A | +|---|---|---|---|---|---|---| +| * | [1,2,3,4,0] | 5 | [1,2,3,4,0] | [1,2,3,4,0] | [1,2,3,4,0] | [1,2,3,4,0] | +| I1:1 | [2,2,3,4,0] | 6 | [2,2,3,4,0] | [2,2,3,4,0] | [1,2,3,4,0,2] | [1,2,3,4,0,2] | +| I1:1 | [2,2,3,4,0] | 6 | [2,2,3,4,0] | [2,2,3,4,0] | [1,2,3,4,0,2] | [1,2,3,4,0,2] | +| I1:2 | [3,2,3,4,0] | 7 | [3,2,3,4,0] | [3,2,3,4,0] | [1,2,3,4,0,2,3] | [1,2,3,4,0,2,3] | +| IO:5 | [3,2,3,4,5] | 8 | [3,2,3,4,5] | [3,2,3,4,5] | [1,2,3,4,0,2,3,5] | [1,2,3,4,0,2,3,5] +| I3:4 | [3,2,7,4,5] | 9 | [3,2,7,4,5] | [3,2,7,4,5] | [1,2,3,4,0,2,3,5,7] | [1,2,3,4,0,2,3,5,7] | + +*: (1:0, 2:0, 3:0, 4:0, 5:0) + +## Execution-Trace (OnlyUpdate) + +| Input | [A1,A2,A3,A4,IO] | # | A* | UsingWcA | WithAddA | UsingWcWithAddA:A | +|---|---|---|---|---|---|---| +| * | [-,-,-,-,-] | 0 | [0,0,0,0,0] | [] | [] | [] | +| I1:1 | [2,-,-,-,-] | 1 | [2,0,0,0,0] | [2] | [2] | [2] | +| I1:1 | [2,-,-,-,-] | 1 | [2,0,0,0,0] | [2] | [2] | [2] | +| I1:2 | [3,-,-,-,-] | 2 | [3,0,0,0,0] | [3] | [2,3] | [2,3] | +| IO:5 | [2,-,-,-,5] | 3 | [3,0,0,0,5] | [3,5] | [2,3,5] | [2,3,5] +| I3:4 | [2,-,7,-,5] | 4 | [3,0,7,0,5] | [3,5,7] | [2,3,5,7] | [2,3,5,7] | + +*: (1:0, 2:0, 3:0, 4:0, 5:0) diff --git a/ragconnect.tests/src/test/01-input/singleListVariant/Test.connect b/ragconnect.tests/src/test/01-input/singleListVariant/Test.connect new file mode 100644 index 0000000000000000000000000000000000000000..23365109901425e8a6060c3ef3c2d8fe4cfc7656 --- /dev/null +++ b/ragconnect.tests/src/test/01-input/singleListVariant/Test.connect @@ -0,0 +1,28 @@ +send tree SenderRoot.T_Empty ; +send tree SenderRoot.T_Token ; +send tree SenderRoot.T_OneChild ; +send tree SenderRoot.T_OneOpt ; +send tree SenderRoot.T_OneList ; +send tree SenderRoot.T_TwoChildren ; +send tree SenderRoot.T_OneOfEach ; +send tree SenderRoot.T_Abstract ; + +receive tree ReceiverRoot.T_Empty ; +receive tree ReceiverRoot.T_Token ; +receive tree ReceiverRoot.T_OneChild ; +receive tree ReceiverRoot.T_OneOpt ; +receive tree ReceiverRoot.T_OneList ; +receive tree ReceiverRoot.T_TwoChildren ; +receive tree ReceiverRoot.T_OneOfEach ; +receive tree ReceiverRoot.T_Abstract ; + +receive tree ReceiverRoot.MyEmpty ; + +receive tree with add ReceiverRoot.EmptyWithAdd ; +receive tree with add ReceiverRoot.TokenWithAdd ; +receive tree with add ReceiverRoot.OneChildWithAdd ; +receive tree with add ReceiverRoot.OneOptWithAdd ; +receive tree with add ReceiverRoot.OneListWithAdd ; +receive tree with add ReceiverRoot.TwoChildrenWithAdd ; +receive tree with add ReceiverRoot.OneOfEachWithAdd ; +receive tree with add ReceiverRoot.AbstractWithAdd ; diff --git a/ragconnect.tests/src/test/01-input/singleListVariant/Test.jadd b/ragconnect.tests/src/test/01-input/singleListVariant/Test.jadd new file mode 100644 index 0000000000000000000000000000000000000000..10ed1174a4cbb99e46cd5ba582df829d547bd446 --- /dev/null +++ b/ragconnect.tests/src/test/01-input/singleListVariant/Test.jadd @@ -0,0 +1,75 @@ +aspect Computation { + syn T_Empty SenderRoot.getT_Empty() = new T_Empty().setID(getInput()); + syn T_Token SenderRoot.getT_Token() = new T_Token().setID(getInput()) + .setValue(Integer.toString(getInput())); + syn T_OneChild SenderRoot.getT_OneChild() { + T_OneChild result = new T_OneChild().setID(getInput()); + result.setOther(createOther()); + return result; + } + syn T_OneOpt SenderRoot.getT_OneOpt() { + T_OneOpt result = new T_OneOpt().setID(getInput()); + if (getShouldSetOptAndList()) { + result.setOther(createOther()); + } + return result; + } + syn T_OneList SenderRoot.getT_OneList() { + T_OneList result = new T_OneList().setID(getInput()); + if (getShouldSetOptAndList()) { + result.addOther(createOther()); + } + return result; + } + syn T_TwoChildren SenderRoot.getT_TwoChildren() { + T_TwoChildren result = new T_TwoChildren().setID(getInput()); + result.setLeft(createOther()); + result.setRight(createOther()); + return result; + } + syn T_OneOfEach SenderRoot.getT_OneOfEach() { + T_OneOfEach result = new T_OneOfEach().setID(getInput()); + result.setFirst(createOther()); + if (getShouldSetOptAndList()) { + result.setSecond(createOther()); + result.addThird(createOther()); + } + result.setFourth(Integer.toString(getInput())); + return result; + } + syn T_Abstract SenderRoot.getT_Abstract() = new T_SubClass() + .setValueSub(Integer.toString(getInput())) + .setID(getInput()) + .setValueAbstract(Integer.toString(getInput())); + + private Other SenderRoot.createOther() { + return new Other().setID(getInput() + 1); + } + + syn boolean ASTNode.isNameable() = false; + eq Nameable.isNameable() = true; +} + +aspect Testing { + class SenderRoot implements org.jastadd.ragconnect.tests.singleListVariant.AbstractSingleListVariantTest.TestWrapperSenderRoot {} + class ReceiverRoot implements org.jastadd.ragconnect.tests.singleListVariant.AbstractSingleListVariantTest.TestWrapperReceiverRoot {} + class Other implements org.jastadd.ragconnect.tests.singleListVariant.AbstractSingleListVariantTest.TestWrapperOther {} + class T_Empty implements org.jastadd.ragconnect.tests.singleListVariant.AbstractSingleListVariantTest.TestWrapperT_Empty {} + class T_Token implements org.jastadd.ragconnect.tests.singleListVariant.AbstractSingleListVariantTest.TestWrapperT_Token {} + class T_OneChild implements org.jastadd.ragconnect.tests.singleListVariant.AbstractSingleListVariantTest.TestWrapperT_OneChild {} + class T_OneOpt implements org.jastadd.ragconnect.tests.singleListVariant.AbstractSingleListVariantTest.TestWrapperT_OneOpt {} + class T_OneList implements org.jastadd.ragconnect.tests.singleListVariant.AbstractSingleListVariantTest.TestWrapperT_OneList {} + class T_TwoChildren implements org.jastadd.ragconnect.tests.singleListVariant.AbstractSingleListVariantTest.TestWrapperT_TwoChildren {} + class T_OneOfEach implements org.jastadd.ragconnect.tests.singleListVariant.AbstractSingleListVariantTest.TestWrapperT_OneOfEach {} + class T_Abstract implements org.jastadd.ragconnect.tests.singleListVariant.AbstractSingleListVariantTest.TestWrapperT_Abstract {} + class JastAddList<T> implements org.jastadd.ragconnect.tests.singleListVariant.AbstractSingleListVariantTest.TestWrapperJastAddList<T> {} +} + +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 Nameable.customID() { + return getClass().getSimpleName() + getID(); + } +} diff --git a/ragconnect.tests/src/test/01-input/singleListVariant/Test.relast b/ragconnect.tests/src/test/01-input/singleListVariant/Test.relast new file mode 100644 index 0000000000000000000000000000000000000000..4ac99ae124e19d182d9ad23b4c4ae65980272013 --- /dev/null +++ b/ragconnect.tests/src/test/01-input/singleListVariant/Test.relast @@ -0,0 +1,44 @@ +Root ::= SenderRoot* ReceiverRoot* ; + +Nameable ::= <ID:int> ; +SenderRoot : Nameable ::= <Input:int> <ShouldSetOptAndList:boolean> +/T_Empty/ +/T_Token/ +/T_OneChild/ +/T_OneOpt/ +/T_OneList/ +/T_TwoChildren/ +/T_OneOfEach/ +/T_Abstract/ +; + +ReceiverRoot : Nameable ::= +T_Empty* +T_Token* +T_OneChild* +T_OneOpt* +T_OneList* +T_TwoChildren* +T_OneOfEach* +T_Abstract* +MyEmpty:T_Empty* +EmptyWithAdd:T_Empty* +TokenWithAdd:T_Token* +OneChildWithAdd:T_OneChild* +OneOptWithAdd:T_OneOpt* +OneListWithAdd:T_OneList* +TwoChildrenWithAdd:T_TwoChildren* +OneOfEachWithAdd:T_OneOfEach* +AbstractWithAdd:T_Abstract* +; + +T_Empty : Nameable ::= /* empty */ ; +T_Token : Nameable ::= <Value:String> ; +T_OneChild : Nameable ::= Other ; +T_OneOpt : Nameable ::= [Other] ; +T_OneList : Nameable ::= Other* ; +T_TwoChildren : Nameable ::= Left:Other Right:Other ; +T_OneOfEach : Nameable ::= First:Other [Second:Other] Third:Other* <Fourth:String> ; +abstract T_Abstract : Nameable ::= <ValueAbstract>; +T_SubClass : T_Abstract ::= <ValueSub> ; +Other : Nameable ; diff --git a/ragconnect.tests/src/test/01-input/singleListVariant/TestDependencies.connect b/ragconnect.tests/src/test/01-input/singleListVariant/TestDependencies.connect new file mode 100644 index 0000000000000000000000000000000000000000..2ac87895268cfa631fcfee65a8c2c90f4c13b3c7 --- /dev/null +++ b/ragconnect.tests/src/test/01-input/singleListVariant/TestDependencies.connect @@ -0,0 +1,16 @@ +SenderRoot.T_Empty canDependOn SenderRoot.Input as InputDependencyToT_Empty ; +SenderRoot.T_Token canDependOn SenderRoot.Input as InputDependencyToT_Token ; +SenderRoot.T_OneChild canDependOn SenderRoot.Input as InputDependencyToT_OneChild ; + +SenderRoot.T_OneOpt canDependOn SenderRoot.ShouldSetOptAndList as ShouldSetOptAndListDependencyToT_OneOpt ; +SenderRoot.T_OneOpt canDependOn SenderRoot.Input as InputDependencyToT_OneOpt ; + +SenderRoot.T_OneList canDependOn SenderRoot.ShouldSetOptAndList as ShouldSetOptAndListDependencyToT_OneList ; +SenderRoot.T_OneList canDependOn SenderRoot.Input as InputDependencyToT_OneList ; + +SenderRoot.T_TwoChildren canDependOn SenderRoot.Input as InputDependencyToT_TwoChildren ; + +SenderRoot.T_OneOfEach canDependOn SenderRoot.ShouldSetOptAndList as ShouldSetOptAndListDependencyToT_OneOfEach ; +SenderRoot.T_OneOfEach canDependOn SenderRoot.Input as InputDependencyToT_OneOfEach ; + +SenderRoot.T_Abstract canDependOn SenderRoot.Input as InputDependencyToT_Abstract ; diff --git a/ragconnect.tests/src/test/01-input/tree/Test.jadd b/ragconnect.tests/src/test/01-input/tree/Test.jadd index c9e47ed7d9c0402992882e3d60a78f08044bf063..475e20f88bbe93daadb59f153b49f4e8d5ac99b4 100644 --- a/ragconnect.tests/src/test/01-input/tree/Test.jadd +++ b/ragconnect.tests/src/test/01-input/tree/Test.jadd @@ -66,11 +66,11 @@ aspect Computation { } aspect Testing { - class ReceiverRoot implements org.jastadd.ragconnect.tests.AbstractTreeTest.TestWrapperReceiverRoot {} - class Alfa implements org.jastadd.ragconnect.tests.AbstractTreeTest.TestWrapperAlfa {} - class Bravo implements org.jastadd.ragconnect.tests.AbstractTreeTest.TestWrapperBravo {} - class Charlie implements org.jastadd.ragconnect.tests.AbstractTreeTest.TestWrapperCharlie {} - class Delta implements org.jastadd.ragconnect.tests.AbstractTreeTest.TestWrapperDelta {} + class ReceiverRoot implements org.jastadd.ragconnect.tests.tree.AbstractTreeTest.TestWrapperReceiverRoot {} + class Alfa implements org.jastadd.ragconnect.tests.tree.AbstractTreeTest.TestWrapperAlfa {} + class Bravo implements org.jastadd.ragconnect.tests.tree.AbstractTreeTest.TestWrapperBravo {} + class Charlie implements org.jastadd.ragconnect.tests.tree.AbstractTreeTest.TestWrapperCharlie {} + class Delta implements org.jastadd.ragconnect.tests.tree.AbstractTreeTest.TestWrapperDelta {} } aspect NameResolution { diff --git a/ragconnect.tests/src/test/01-input/treeAllowedTokens/Test.jadd b/ragconnect.tests/src/test/01-input/treeAllowedTokens/Test.jadd index e5f8750c50a076498fea4398899fbebb5999620e..5ac99ca749c7f962416f4c34bb8544a2fdd2e38f 100644 --- a/ragconnect.tests/src/test/01-input/treeAllowedTokens/Test.jadd +++ b/ragconnect.tests/src/test/01-input/treeAllowedTokens/Test.jadd @@ -35,8 +35,8 @@ aspect Enum { public enum MyEnum { FALSE, TRUE; } } aspect Testing { - class ReceiverRoot implements org.jastadd.ragconnect.tests.AbstractTreeAllowedTokensTest.TestWrapperReceiverRoot {} - class Alfa implements org.jastadd.ragconnect.tests.AbstractTreeAllowedTokensTest.TestWrapperAlfa {} + class ReceiverRoot implements org.jastadd.ragconnect.tests.treeAllowedTokens.AbstractTreeAllowedTokensTest.TestWrapperReceiverRoot {} + class Alfa implements org.jastadd.ragconnect.tests.treeAllowedTokens.AbstractTreeAllowedTokensTest.TestWrapperAlfa {} } aspect NameResolution { 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 f2f5ddee691854ef40f8b3ede6820a38e0262c2b..db48fdf9a525e242bed1c39f7adf54260668a08a 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,12 +14,12 @@ import java.util.concurrent.TimeUnit; @Tag("mqtt") public abstract class AbstractMqttTest { - static boolean checkDone = false; - static boolean checkResult; - static MqttHandler publisher; + private static boolean checkDone = false; + protected static MqttHandler publisher; @BeforeAll public static void createPublishAndOnceCheckMqttConnection() { + boolean checkResult; try { publisher = new MqttHandler("Publisher") .dontSendWelcomeMessage() @@ -55,7 +55,7 @@ public abstract class AbstractMqttTest { * Actual test code for communication when sending initial value. * @throws InterruptedException because of TestUtils.waitForMqtt() */ - protected abstract void communicateSendInitialValue() throws InterruptedException; + protected abstract void communicateSendInitialValue() throws IOException, InterruptedException; @Tag("mqtt") @Test @@ -70,7 +70,7 @@ public abstract class AbstractMqttTest { * Actual test code for communication without sending any value upon connecting. * @throws InterruptedException because of TestUtils.waitForMqtt() */ - protected abstract void communicateOnlyUpdatedValue() throws InterruptedException; + protected abstract void communicateOnlyUpdatedValue() throws IOException, InterruptedException; /** * Create the model, and set required default values. @@ -90,7 +90,7 @@ public abstract class AbstractMqttTest { * and finally call generated connect* methods on model elements. * @param writeCurrentValue if the initial/current value shall be sent upon connecting */ - protected abstract void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException; + protected abstract void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException, InterruptedException; @AfterEach public void alwaysCloseConnections() { diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/IncrementalDependencyTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/IncrementalDependencyTest.java index 580c4fc81a2952f0042eb273ffcb9be35b0a3df5..132317701c73cfa9a06fa136b985393c065823ba 100644 --- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/IncrementalDependencyTest.java +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/IncrementalDependencyTest.java @@ -9,8 +9,7 @@ import java.io.IOException; import java.util.concurrent.TimeUnit; import static org.jastadd.ragconnect.tests.TestUtils.mqttUri; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; /** * Testcase "Incremental Dependency". @@ -80,7 +79,7 @@ public class IncrementalDependencyTest extends AbstractMqttTest { } @Override - protected void communicateSendInitialValue() throws InterruptedException { + protected void communicateSendInitialValue() throws InterruptedException, IOException { // check initial value TestUtils.waitForMqtt(); checkData(1, "aStart", @@ -104,10 +103,34 @@ public class IncrementalDependencyTest extends AbstractMqttTest { checkData(3, "a201", "b201Postfix", "b201Postfix"); + + // send and check new value (b1 should not change) + assertTrue(b1.disconnectOutputOnB(mqttUri(TOPIC_OUT_B1))); + sendData("301"); + checkData(4, "a301", + 3, "b201Postfix", + 4, "b301Postfix"); + + // disconnecting again should yield false + assertFalse(b1.disconnectOutputOnB(mqttUri(TOPIC_OUT_B1))); + + // send and check new value (b1 and b2 should not change) + assertTrue(b2.disconnectOutputOnB(mqttUri(TOPIC_OUT_B2))); + sendData("401"); + checkData(5, "a401", + 3, "b201Postfix", + 4, "b301Postfix"); + + // send and check new value (nothing should not change) + assertTrue(model.disconnectOutputOnA(mqttUri(TOPIC_OUT_A))); + sendData("501"); + checkData(5, "a401", + 3, "b201Postfix", + 4, "b301Postfix"); } @Override - protected void communicateOnlyUpdatedValue() throws InterruptedException { + protected void communicateOnlyUpdatedValue() throws InterruptedException, IOException { // check initial value TestUtils.waitForMqtt(); checkData(0, null, @@ -132,6 +155,29 @@ public class IncrementalDependencyTest extends AbstractMqttTest { "b202Postfix", "b202Postfix"); + // send and check new value (b1 should not change) + assertTrue(b1.disconnectOutputOnB(mqttUri(TOPIC_OUT_B1))); + sendData("302"); + checkData(3, "a302", + 2, "b202Postfix", + 3, "b302Postfix"); + + // disconnecting again should yield false + assertFalse(b1.disconnectOutputOnB(mqttUri(TOPIC_OUT_B1))); + + // send and check new value (b1 and b2 should not change) + assertTrue(b2.disconnectOutputOnB(mqttUri(TOPIC_OUT_B2))); + sendData("402"); + checkData(4, "a402", + 2, "b202Postfix", + 3, "b302Postfix"); + + // send and check new value (nothing should not change) + assertTrue(model.disconnectOutputOnA(mqttUri(TOPIC_OUT_A))); + sendData("502"); + checkData(4, "a402", + 2, "b202Postfix", + 3, "b302Postfix"); } @Override @@ -151,9 +197,17 @@ public class IncrementalDependencyTest extends AbstractMqttTest { private void checkData(int expectedNumberOfValues, String expectedLastAValue, String expectedLastB1Value, String expectedLastB2Value) { - dataA.assertEqualData(expectedNumberOfValues, expectedLastAValue); - dataB1.assertEqualData(expectedNumberOfValues, expectedLastB1Value); - dataB2.assertEqualData(expectedNumberOfValues, expectedLastB2Value); + checkData(expectedNumberOfValues, expectedLastAValue, + expectedNumberOfValues, expectedLastB1Value, + expectedNumberOfValues, expectedLastB2Value); + } + + private void checkData(int expectedNumberOfAValues, String expectedLastAValue, + int expectedNumberOfB1Values, String expectedLastB1Value, + int expectedNumberOfB2Values, String expectedLastB2Value) { + dataA.assertEqualData(expectedNumberOfAValues, expectedLastAValue); + dataB1.assertEqualData(expectedNumberOfB1Values, expectedLastB1Value); + dataB2.assertEqualData(expectedNumberOfB2Values, expectedLastB2Value); } private static class ReceiverData { 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 c09059c8a66ca1cba262843ce30d4ce8ad0bc663..bb557f6f88783c00f3bb2115758223bae0dc6f9c 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 @@ -14,8 +14,12 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import static java.lang.Math.abs; +import static java.util.Collections.addAll; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.util.Lists.newArrayList; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -132,12 +136,31 @@ public class TestUtils { return new String(encoded, encoding); } - static void waitForMqtt() throws InterruptedException { + public static void waitForMqtt() throws InterruptedException { TimeUnit.MILLISECONDS.sleep(1500); } + public static class IntList { + private final List<Integer> integers = newArrayList(); + public IntList(Integer... values) { + addAll(integers, values); + } + + public List<Integer> toList() { + return integers; + } + + public List<Integer> toAbsList() { + return integers.stream().map(Math::abs).collect(Collectors.toList()); + } + + public static IntList list(Integer... values) { + return new IntList(values); + } + } + @SuppressWarnings({"unused", "rawtypes"}) - static class DefaultMappings { + public static class DefaultMappings { static class ReadNode extends defaultOnlyRead.ast.ASTNode { public static boolean _apply__DefaultBytesToBooleanMapping(byte[] input) throws Exception { return defaultOnlyRead.ast.ASTNode._apply__DefaultBytesToBooleanMapping(input); diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TokenValueSendTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TokenValueSendTest.java index 6d99149688b33d8401037fb358f736c2509b0921..da4d70308afd1efc12e10d4dbfa1a84c04852628 100644 --- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TokenValueSendTest.java +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TokenValueSendTest.java @@ -6,8 +6,7 @@ import java.io.IOException; import java.util.concurrent.TimeUnit; import static org.jastadd.ragconnect.tests.TestUtils.mqttUri; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; /** * Test case "tokenValueSend". @@ -15,6 +14,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; * @author rschoene - Initial contribution */ public class TokenValueSendTest extends AbstractMqttTest { + // TODO split into incremental and manual test private static final String TOPIC_SEND_ONE = "one/value/out"; @@ -96,9 +96,8 @@ public class TokenValueSendTest extends AbstractMqttTest { } @Override - protected void communicateSendInitialValue() throws InterruptedException { + protected void communicateSendInitialValue() throws InterruptedException, IOException { // check initial value - TestUtils.waitForMqtt(); checkData(1, "Start-Post", 1, "Start-Post", 1, "Start-Post", @@ -106,9 +105,6 @@ public class TokenValueSendTest extends AbstractMqttTest { // send new value sendData("200", "300"); - - // check new value - TestUtils.waitForMqtt(); checkData(1, "Start-Post", 2, "Pre-200-Post", 2, "Pre-300-Post", @@ -116,9 +112,6 @@ public class TokenValueSendTest extends AbstractMqttTest { // set new value setData("101", "201", "301"); - - // check new value - TestUtils.waitForMqtt(); checkData(2, "101-Post", 3, "201-Post", 3, "301-Post", @@ -126,9 +119,6 @@ public class TokenValueSendTest extends AbstractMqttTest { // send the same values (will not be sent again) setData("101", "201", "301"); - - // check new value - TestUtils.waitForMqtt(); checkData(2, "101-Post", 3, "201-Post", 3, "301-Post", @@ -136,9 +126,6 @@ public class TokenValueSendTest extends AbstractMqttTest { // send values with prefixes imitating receiving setData("102", "Pre-202", "Pre-302"); - - // check new value - TestUtils.waitForMqtt(); checkData(3, "102-Post", 4, "Pre-202-Post", 4, "Pre-302-Post", @@ -146,19 +133,49 @@ public class TokenValueSendTest extends AbstractMqttTest { // send the same values (will not be sent again, because previously prefixed) sendData("202", "302"); - - // check new value - TestUtils.waitForMqtt(); checkData(3, "102-Post", 4, "Pre-202-Post", 4, "Pre-302-Post", 4, "Pre-302-T-Post"); + + // new values for two and three, but two will not send updated value + assertTrue(two.disconnectSendValue(mqttUri(TOPIC_SEND_TWO))); + sendData("203", "303"); + checkData(3, "102-Post", + 4, "Pre-202-Post", + 5, "Pre-303-Post", + 5, "Pre-303-T-Post"); + assertEquals("Pre-203", two.getValue()); + + // can not disconnect again, and also not for different topic + assertFalse(two.disconnectSendValue(mqttUri(TOPIC_SEND_TWO))); + assertFalse(two.disconnectSendValue(mqttUri(TOPIC_RECEIVE_TWO))); + + // new values for two and three, but two will neither receive nor send updated value + assertTrue(two.disconnectReceiveValue(mqttUri(TOPIC_RECEIVE_TWO))); + sendData("204", "304"); + checkData(3, "102-Post", + 4, "Pre-202-Post", + 6, "Pre-304-Post", + 6, "Pre-304-T-Post"); + assertEquals("Pre-203", two.getValue()); + + // new values for three, but it will not receive updated value, and, thus, not send it either + assertTrue(three.disconnectReceiveValue(mqttUri(TOPIC_RECEIVE_THREE_VALUE))); + sendData("204", "305"); + checkData(3, "102-Post", + 4, "Pre-202-Post", + 6, "Pre-304-Post", + 6, "Pre-304-T-Post"); + assertEquals("Pre-203", two.getValue()); + + // disconnect send is possible + assertTrue(three.disconnectSendValue(mqttUri(TOPIC_SEND_THREE_VALUE))); } @Override - protected void communicateOnlyUpdatedValue() throws InterruptedException { + protected void communicateOnlyUpdatedValue() throws InterruptedException, IOException { // check initial value - TestUtils.waitForMqtt(); checkData(0, null, 0, null, 0, null, @@ -166,9 +183,6 @@ public class TokenValueSendTest extends AbstractMqttTest { // send new value sendData("210", "310"); - - // check new value - TestUtils.waitForMqtt(); checkData(0, null, 1, "Pre-210-Post", 1, "Pre-310-Post", @@ -176,9 +190,6 @@ public class TokenValueSendTest extends AbstractMqttTest { // set new value setData("111", "211", "311"); - - // check new value - TestUtils.waitForMqtt(); checkData(1, "111-Post", 2, "211-Post", 2, "311-Post", @@ -186,9 +197,6 @@ public class TokenValueSendTest extends AbstractMqttTest { // send the same values (will not be sent again) setData("111", "211", "311"); - - // check new value - TestUtils.waitForMqtt(); checkData(1, "111-Post", 2, "211-Post", 2, "311-Post", @@ -196,9 +204,6 @@ public class TokenValueSendTest extends AbstractMqttTest { // send values with prefixes imitating receiving setData("112", "Pre-212", "Pre-312"); - - // check new value - TestUtils.waitForMqtt(); checkData(2, "112-Post", 3, "Pre-212-Post", 3, "Pre-312-Post", @@ -206,13 +211,44 @@ public class TokenValueSendTest extends AbstractMqttTest { // send the same values (will not be sent again, because previously prefixed) sendData("212", "312"); - - // check new value - TestUtils.waitForMqtt(); checkData(2, "112-Post", 3, "Pre-212-Post", 3, "Pre-312-Post", 3, "Pre-312-T-Post"); + + // new values for two and three, but two will not send updated value + assertTrue(two.disconnectSendValue(mqttUri(TOPIC_SEND_TWO))); + sendData("213", "313"); + checkData(2, "112-Post", + 3, "Pre-212-Post", + 4, "Pre-313-Post", + 4, "Pre-313-T-Post"); + assertEquals("Pre-213", two.getValue()); + + // can not disconnect again, and also not for different topic + assertFalse(two.disconnectSendValue(mqttUri(TOPIC_SEND_TWO))); + assertFalse(two.disconnectSendValue(mqttUri(TOPIC_RECEIVE_TWO))); + + // new values for two and three, but two will neither receive nor send updated value + assertTrue(two.disconnectReceiveValue(mqttUri(TOPIC_RECEIVE_TWO))); + sendData("214", "314"); + checkData(2, "112-Post", + 3, "Pre-212-Post", + 5, "Pre-314-Post", + 5, "Pre-314-T-Post"); + assertEquals("Pre-213", two.getValue()); + + // new values for three, but it will not receive updated value, and, thus, not send it either + assertTrue(three.disconnectReceiveValue(mqttUri(TOPIC_RECEIVE_THREE_VALUE))); + sendData("214", "315"); + checkData(2, "112-Post", + 3, "Pre-212-Post", + 5, "Pre-314-Post", + 5, "Pre-314-T-Post"); + assertEquals("Pre-213", two.getValue()); + + // disconnect send is possible + assertTrue(three.disconnectSendValue(mqttUri(TOPIC_SEND_THREE_VALUE))); } @Override @@ -240,7 +276,8 @@ public class TokenValueSendTest extends AbstractMqttTest { int numberOfTwoValues, String lastTwoStringValue, int numberOfThreeValues, String lastThreeStringValue, int numberOfOtherValues, String lastOtherStringValue - ) { + ) throws InterruptedException { + TestUtils.waitForMqtt(); dataOne.assertEqualData(numberOfOneValues, lastOneStringValue); dataTwo.assertEqualData(numberOfTwoValues, lastTwoStringValue); dataThree.assertEqualData(numberOfThreeValues, lastThreeStringValue); diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ViaTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ViaTest.java index 7ab46f7616a1cf90737c2e0b3ff752f553d38292..ded2990b5e997a18ff834379258689c359e4a465 100644 --- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ViaTest.java +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ViaTest.java @@ -43,6 +43,8 @@ public class ViaTest extends AbstractMqttTest { private static final String REST_SERVER_BASE_URL = "http://localhost:" + REST_PORT + "/"; + private static final String NOT_MAPPED = "<html><body><h2>404 Not found</h2></body></html>"; + private MqttHandler handler; private A model; private ReceiverData dataMqtt2Mqtt; @@ -121,9 +123,8 @@ public class ViaTest extends AbstractMqttTest { } @Override - protected void communicateSendInitialValue() throws InterruptedException { + protected void communicateSendInitialValue() throws InterruptedException, IOException { // check initial value - TestUtils.waitForMqtt(); checkData(1, "100-M2M-ToMqtt", "200-R2R-ToRest", "300-M2R-ToRest", @@ -135,7 +136,6 @@ public class ViaTest extends AbstractMqttTest { sendDataForBoth("501", true); // check new value - TestUtils.waitForMqtt(); checkData(2, "FromMqtt-101-M2M-ToMqtt", "FromRest-201-R2R-ToRest", "FromMqtt-301-M2R-ToRest", @@ -147,7 +147,6 @@ public class ViaTest extends AbstractMqttTest { sendDataForBoth("502", false); // check this value - TestUtils.waitForMqtt(); checkData(2, "FromMqtt-101-M2M-ToMqtt", "FromRest-201-R2R-ToRest", "FromMqtt-301-M2R-ToRest", @@ -159,7 +158,6 @@ public class ViaTest extends AbstractMqttTest { sendDataForBoth("502", true); // check this value - TestUtils.waitForMqtt(); checkData(2, "FromMqtt-101-M2M-ToMqtt", "FromRest-201-R2R-ToRest", "FromMqtt-301-M2R-ToRest", @@ -171,7 +169,6 @@ public class ViaTest extends AbstractMqttTest { sendData("102", "202", "302", "402"); // check this value - TestUtils.waitForMqtt(); checkData(3, "FromMqtt-102-M2M-ToMqtt", "FromRest-202-R2R-ToRest", "FromMqtt-302-M2R-ToRest", @@ -183,19 +180,57 @@ public class ViaTest extends AbstractMqttTest { sendData("102", "202", "302", "402"); // check this value - TestUtils.waitForMqtt(); checkData(3, "FromMqtt-102-M2M-ToMqtt", "FromRest-202-R2R-ToRest", "FromMqtt-302-M2R-ToRest", 3, "FromRest-402-R2M-ToMqtt", 3, "502-B2M-ToMqtt", "502-B2R-ToRest"); + + // send 503 over mqtt while disconnected should not change anything + assertTrue(model.disconnectBoth2BothInput(mqttUri(TOPIC_BOTH_MQTT_RECEIVE))); + sendDataForBoth("503", true); + checkData(3, "FromMqtt-102-M2M-ToMqtt", + "FromRest-202-R2R-ToRest", + "FromMqtt-302-M2R-ToRest", + 3, "FromRest-402-R2M-ToMqtt", + 3, "502-B2M-ToMqtt", + "502-B2R-ToRest"); + + // send 504 over rest while still connected should update + sendDataForBoth("504", false); + checkData(3, "FromMqtt-102-M2M-ToMqtt", + "FromRest-202-R2R-ToRest", + "FromMqtt-302-M2R-ToRest", + 3, "FromRest-402-R2M-ToMqtt", + 4, "504-B2M-ToMqtt", + "504-B2R-ToRest"); + + // send 505 over rest while also disconnected should not change anything + assertTrue(model.disconnectBoth2BothInput(restUri(PATH_BOTH_REST_RECEIVE, REST_PORT))); + sendDataForBoth("505", false); + checkData(3, "FromMqtt-102-M2M-ToMqtt", + "FromRest-202-R2R-ToRest", + "FromMqtt-302-M2R-ToRest", + 3, "FromRest-402-R2M-ToMqtt", + 4, "504-B2M-ToMqtt", + "504-B2R-ToRest"); + + // send new values. value over rest while sender disconnected does not provide a value anymore + assertTrue(model.disconnectRest2RestOutput(restUri(PATH_REST_2_REST_SEND, REST_PORT))); + assertTrue(model.disconnectMqtt2RestOutput(restUri(PATH_MQTT_2_REST_SEND, REST_PORT))); + sendData("103", "203", "303", "403"); + checkData(4, "FromMqtt-103-M2M-ToMqtt", + NOT_MAPPED, + NOT_MAPPED, + 4, "FromRest-403-R2M-ToMqtt", + 4, "504-B2M-ToMqtt", + "504-B2R-ToRest"); } @Override - protected void communicateOnlyUpdatedValue() throws InterruptedException { + protected void communicateOnlyUpdatedValue() throws InterruptedException, IOException { // check initial value - TestUtils.waitForMqtt(); checkData(0, null, "200-R2R-ToRest", "300-M2R-ToRest", @@ -205,9 +240,6 @@ public class ViaTest extends AbstractMqttTest { sendData("111", "211", "311", "411"); sendDataForBoth("511", true); - - // check new value - TestUtils.waitForMqtt(); checkData(1, "FromMqtt-111-M2M-ToMqtt", "FromRest-211-R2R-ToRest", "FromMqtt-311-M2R-ToRest", @@ -217,9 +249,6 @@ public class ViaTest extends AbstractMqttTest { // send value only for bothInput via REST sendDataForBoth("512", false); - - // check this value - TestUtils.waitForMqtt(); checkData(1, "FromMqtt-111-M2M-ToMqtt", "FromRest-211-R2R-ToRest", "FromMqtt-311-M2R-ToRest", @@ -229,9 +258,6 @@ public class ViaTest extends AbstractMqttTest { // send same value only for bothInput via MQTT sendDataForBoth("512", true); - - // check this value - TestUtils.waitForMqtt(); checkData(1, "FromMqtt-111-M2M-ToMqtt", "FromRest-211-R2R-ToRest", "FromMqtt-311-M2R-ToRest", @@ -241,9 +267,6 @@ public class ViaTest extends AbstractMqttTest { // send values for other things sendData("112", "212", "312", "412"); - - // check this value - TestUtils.waitForMqtt(); checkData(2, "FromMqtt-112-M2M-ToMqtt", "FromRest-212-R2R-ToRest", "FromMqtt-312-M2R-ToRest", @@ -253,15 +276,52 @@ public class ViaTest extends AbstractMqttTest { // send same values again for other things sendData("112", "212", "312", "412"); + checkData(2, "FromMqtt-112-M2M-ToMqtt", + "FromRest-212-R2R-ToRest", + "FromMqtt-312-M2R-ToRest", + 2, "FromRest-412-R2M-ToMqtt", + 2, "512-B2M-ToMqtt", + "512-B2R-ToRest"); - // check this value - TestUtils.waitForMqtt(); + // send 503 over mqtt while disconnected should not change anything + assertTrue(model.disconnectBoth2BothInput(mqttUri(TOPIC_BOTH_MQTT_RECEIVE))); + sendDataForBoth("513", true); checkData(2, "FromMqtt-112-M2M-ToMqtt", "FromRest-212-R2R-ToRest", "FromMqtt-312-M2R-ToRest", 2, "FromRest-412-R2M-ToMqtt", 2, "512-B2M-ToMqtt", "512-B2R-ToRest"); + + // send 514 over rest while still connected should update + sendDataForBoth("514", false); + checkData(2, "FromMqtt-112-M2M-ToMqtt", + "FromRest-212-R2R-ToRest", + "FromMqtt-312-M2R-ToRest", + 2, "FromRest-412-R2M-ToMqtt", + 3, "514-B2M-ToMqtt", + "514-B2R-ToRest"); + + // send 515 over rest while also disconnected should not change anything + assertTrue(model.disconnectBoth2BothInput(restUri(PATH_BOTH_REST_RECEIVE, REST_PORT))); + sendDataForBoth("515", false); + checkData(2, "FromMqtt-112-M2M-ToMqtt", + "FromRest-212-R2R-ToRest", + "FromMqtt-312-M2R-ToRest", + 2, "FromRest-412-R2M-ToMqtt", + 3, "514-B2M-ToMqtt", + "514-B2R-ToRest"); + + // send new values. value over rest while sender disconnected does not provide a value anymore + assertTrue(model.disconnectRest2RestOutput(restUri(PATH_REST_2_REST_SEND, REST_PORT))); + assertTrue(model.disconnectMqtt2RestOutput(restUri(PATH_MQTT_2_REST_SEND, REST_PORT))); + sendData("113", "213", "313", "413"); + checkData(3, "FromMqtt-113-M2M-ToMqtt", + NOT_MAPPED, + NOT_MAPPED, + 3, "FromRest-413-R2M-ToMqtt", + 3, "514-B2M-ToMqtt", + "514-B2R-ToRest"); } @Override @@ -289,7 +349,8 @@ public class ViaTest extends AbstractMqttTest { } } - private void checkData(int numberOfMqtt2MqttValues, String mqtt2MqttValue, String rest2RestValue, String mqtt2RestValue, int numberOfRest2MqttValues, String rest2MqttValue, int numberOfBoth2MqttValues, String both2MqttValue, String both2RestValue) { + private void checkData(int numberOfMqtt2MqttValues, String mqtt2MqttValue, String rest2RestValue, String mqtt2RestValue, int numberOfRest2MqttValues, String rest2MqttValue, int numberOfBoth2MqttValues, String both2MqttValue, String both2RestValue) throws InterruptedException { + TestUtils.waitForMqtt(); dataMqtt2Mqtt.assertEqualData(numberOfMqtt2MqttValues, mqtt2MqttValue); dataRest2Mqtt.assertEqualData(numberOfRest2MqttValues, rest2MqttValue); dataBoth2Mqtt.assertEqualData(numberOfBoth2MqttValues, both2MqttValue); diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/list/AbstractListTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/list/AbstractListTest.java new file mode 100644 index 0000000000000000000000000000000000000000..5e26ba01360aaad05681c6ee3c045be0a6ca835d --- /dev/null +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/list/AbstractListTest.java @@ -0,0 +1,183 @@ +package org.jastadd.ragconnect.tests.list; + +import org.jastadd.ragconnect.tests.AbstractMqttTest; +import org.jastadd.ragconnect.tests.TestUtils; +import org.jastadd.ragconnect.tests.TestUtils.IntList; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.file.Paths; +import java.util.List; +import java.util.function.Function; + +import static org.jastadd.ragconnect.tests.TestUtils.IntList.list; +import static org.jastadd.ragconnect.tests.TestUtils.testJaddContainReferenceToJackson; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Base class for test cases "list manual" and "list incremental". + * + * @author rschoene - Initial contribution + */ +@Tag("List") +public abstract class AbstractListTest extends AbstractMqttTest { + + public interface TestWrapperJastAddList<T> extends Iterable<T> { + int getNumChild(); + } + public interface TestWrapperReceiverRoot { + TestWrapperJastAddList<? extends TestWrapperA> getAList(); + TestWrapperJastAddList<? extends TestWrapperA> getAs(); + int getNumA(); + TestWrapperA getA(int index); + + TestWrapperJastAddList<? extends TestWrapperA> getFromSingleAList(); + TestWrapperJastAddList<? extends TestWrapperA> getFromSingleAs(); + + TestWrapperJastAddList<? extends TestWrapperA> getWithAddFromAList(); + TestWrapperJastAddList<? extends TestWrapperA> getWithAddFromAs(); + + TestWrapperJastAddList<? extends TestWrapperA> getWithAddFromSingleAList(); + TestWrapperJastAddList<? extends TestWrapperA> getWithAddFromSingleAs(); + + boolean connectAList(String mqttUri) throws IOException; + boolean connectFromSingleAList(String mqttUri) throws IOException; + boolean connectWithAddFromAList(String mqttUri) throws IOException; + boolean connectWithAddFromSingleAList(String mqttUri) throws IOException; + } + public interface TestWrapperA { + AbstractListTest.TestWrapperB getB(int i); + int getNumB(); + int getID(); + } + public interface TestWrapperB { + int getID(); + } + + AbstractListTest(String shortName) { + this.shortName = shortName; + } + + protected static final String TOPIC_A = "a"; + protected static final String TOPIC_SINGLE_A = "single-a"; + + protected TestWrapperReceiverRoot receiverRoot; + protected ReceiverData data; + protected ReceiverData dataSingle; + + private final String shortName; + + @Test + public void checkJacksonReference() { + testJaddContainReferenceToJackson( + Paths.get("src", "test", + "02-after-ragconnect", shortName, "RagConnect.jadd"), true); + } + + @Override + protected void communicateSendInitialValue() throws InterruptedException, IOException { + checkTree(1, list(), list(0), list(), list(0)); + + setInput(1); + checkTree(2, list(1), list(1), list(1), list(0, 1)); + + setInput(1); + checkTree(2, list(1), list(1), list(1), list(0, 1)); + + setInput(2); + checkTree(3, list(1, 2), list(2), list(1, 1, 2), list(0, 1, 2)); + + setInput(3); + checkTree(4, list(1, 2, 3), list(3), list(1, 1, 2, 1, 2, 3), list(0, 1, 2, 3)); + + disconnectReceive(); + setInput(4); + checkTree(5, list(1, 2, 3), list(3), list(1, 1, 2, 1, 2, 3), list(0, 1, 2, 3)); + + disconnectSend(); + setInput(5); + checkTree(5, list(1, 2, 3), list(3), list(1, 1, 2, 1, 2, 3), list(0, 1, 2, 3)); + } + + @Override + protected void communicateOnlyUpdatedValue() throws InterruptedException, IOException { + checkTree(0, list(), list(), list(), list()); + + setInput(1); + checkTree(1, list(1), list(1), list(1), list(1)); + + setInput(1); + checkTree(1, list(1), list(1), list(1), list(1)); + + setInput(2); + checkTree(2, list(1, 2), list(2), list(1, 1, 2), list(1, 2)); + + setInput(3); + checkTree(3, list(1, 2, 3), list(3), list(1, 1, 2, 1, 2, 3), list(1, 2, 3)); + + disconnectReceive(); + setInput(4); + checkTree(4, list(1, 2, 3), list(3), list(1, 1, 2, 1, 2, 3), list(1, 2, 3)); + + disconnectSend(); + setInput(5); + checkTree(4, list(1, 2, 3), list(3), list(1, 1, 2, 1, 2, 3), list(1, 2, 3)); + } + + protected abstract void disconnectReceive() throws IOException; + + protected abstract void disconnectSend() throws IOException; + + protected abstract void setInput(int input); + + private void checkTree(int expectedTransmissions, IntList normalA, IntList fromSingleA, IntList withAddA, IntList withAddFromSingleA) throws InterruptedException { + TestUtils.waitForMqtt(); + + assertEquals(expectedTransmissions, data.numberOfElements, "transmissions for normal"); + assertEquals(expectedTransmissions, dataSingle.numberOfElements, "transmissions for single"); + + checkList(normalA.toList(), receiverRoot.getNumA(), receiverRoot::getA, true); + checkList(normalA.toList(), receiverRoot.getAList(), true); + checkList(normalA.toList(), receiverRoot.getAs(), true); + + checkList(fromSingleA.toList(), receiverRoot.getFromSingleAList(), false); + checkList(fromSingleA.toList(), receiverRoot.getFromSingleAs(), false); + + checkList(withAddA.toList(), receiverRoot.getWithAddFromAList(), true); + checkList(withAddA.toList(), receiverRoot.getWithAddFromAs(), true); + + checkList(withAddFromSingleA.toList(), receiverRoot.getWithAddFromSingleAList(), false); + checkList(withAddFromSingleA.toList(), receiverRoot.getWithAddFromSingleAs(), false); + } + + private void checkList(List<Integer> expectedList, int numChildren, Function<Integer, TestWrapperA> getA, boolean expectB) { + assertEquals(expectedList.size(), numChildren, "same list size"); + for (int index = 0; index < expectedList.size(); index++) { + TestWrapperA a = getA.apply(index); + assertEquals(expectedList.get(index), a.getID(), "correct ID for A"); + if (expectB) { + assertEquals(1, a.getNumB(), "one B child"); + assertEquals(expectedList.get(index) + 1, a.getB(0).getID(), "correct ID for B child"); + } + } + } + + private void checkList(List<Integer> expectedList, TestWrapperJastAddList<? extends TestWrapperA> actualList, boolean expectB) { + assertEquals(expectedList.size(), actualList.getNumChild(), "same list size"); + int index = 0; + for (TestWrapperA a : actualList) { + assertEquals(expectedList.get(index), a.getID(), "correct ID for A"); + if (expectB) { + assertEquals(1, a.getNumB(), "one B child"); + assertEquals(expectedList.get(index) + 1, a.getB(0).getID(), "correct ID for B child"); + } + index++; + } + } + + protected static class ReceiverData { + int numberOfElements = 0; + } + +} diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/list/ListIncrementalTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/list/ListIncrementalTest.java new file mode 100644 index 0000000000000000000000000000000000000000..ac8b9415523c2a1e9121b5647e1d886fd2efba09 --- /dev/null +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/list/ListIncrementalTest.java @@ -0,0 +1,100 @@ +package org.jastadd.ragconnect.tests.list; + +import listInc.ast.*; +import org.jastadd.ragconnect.tests.TestUtils; +import org.junit.jupiter.api.Tag; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import static org.jastadd.ragconnect.tests.TestUtils.mqttUri; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Test case "list incremental". + * + * @author rschoene - Initial contribution + */ +@Tag("Incremental") +public class ListIncrementalTest extends AbstractListTest { + + private Root model; + private SenderRoot senderRoot; + private MqttHandler handler; + + ListIncrementalTest() { + super("listInc"); + } + + @Override + protected void createModel() { + model = new Root(); + senderRoot = new SenderRoot(); + senderRoot.setInput(0); + model.addSenderRoot(senderRoot); + + receiverRoot = new ReceiverRoot(); + model.addReceiverRoot((ReceiverRoot) receiverRoot); + } + + @Override + protected void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException { + model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS); + + handler = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost()); + assertTrue(handler.waitUntilReady(2, TimeUnit.SECONDS)); + + // no dependencies + + data = new ReceiverData(); + dataSingle = new ReceiverData(); + handler.newConnection(TOPIC_A, bytes -> data.numberOfElements += 1); + handler.newConnection(TOPIC_SINGLE_A, bytes -> dataSingle.numberOfElements += 1); + + // connect. important: first receivers, then senders. to not miss initial value. + assertTrue(receiverRoot.connectAList(mqttUri(TOPIC_A))); + assertTrue(receiverRoot.connectFromSingleAList(mqttUri(TOPIC_SINGLE_A))); + assertTrue(receiverRoot.connectWithAddFromAList(mqttUri(TOPIC_A))); + assertTrue(receiverRoot.connectWithAddFromSingleAList(mqttUri(TOPIC_SINGLE_A))); + + assertTrue(senderRoot.connectAList(mqttUri(TOPIC_A), writeCurrentValue)); + assertTrue(senderRoot.connectSingleAList(mqttUri(TOPIC_SINGLE_A), writeCurrentValue)); + } + + @Override + protected void setInput(int input) { + senderRoot.setInput(input); + assertEquals(input, senderRoot.getNumA(), "size of normal NTA"); + if (input > 0) { + assertEquals(input, senderRoot.getA(input - 1).getID(), "ID value of last A in normal list"); + } + assertEquals(1, senderRoot.getNumSingleA(), "size of single NTA"); + assertEquals(input, senderRoot.getSingleA(0).getID(), "ID value of single A"); + } + + @Override + protected void disconnectReceive() throws IOException { + ReceiverRoot receiverRootWithLocalType = (ReceiverRoot) receiverRoot; + assertTrue(receiverRootWithLocalType.disconnectAList(mqttUri(TOPIC_A))); + assertTrue(receiverRootWithLocalType.disconnectFromSingleAList(mqttUri(TOPIC_SINGLE_A))); + assertTrue(receiverRootWithLocalType.disconnectWithAddFromAList(mqttUri(TOPIC_A))); + assertTrue(receiverRootWithLocalType.disconnectWithAddFromSingleAList(mqttUri(TOPIC_SINGLE_A))); + } + + @Override + protected void disconnectSend() throws IOException { + assertTrue(senderRoot.disconnectAList(mqttUri(TOPIC_A))); + assertTrue(senderRoot.disconnectSingleAList(mqttUri(TOPIC_SINGLE_A))); + } + + @Override + protected void closeConnections() { + if (handler != null) { + handler.close(); + } + if (model != null) { + model.ragconnectCloseConnections(); + } + } +} diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/list/ListManualTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/list/ListManualTest.java new file mode 100644 index 0000000000000000000000000000000000000000..978359f772ba91187cae829ea0952807e1892ce2 --- /dev/null +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/list/ListManualTest.java @@ -0,0 +1,99 @@ +package org.jastadd.ragconnect.tests.list; + +import list.ast.MqttHandler; +import list.ast.ReceiverRoot; +import list.ast.Root; +import list.ast.SenderRoot; +import org.jastadd.ragconnect.tests.TestUtils; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import static org.jastadd.ragconnect.tests.TestUtils.mqttUri; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Test case "list manual". + * + * @author rschoene - Initial contribution + */ +public class ListManualTest extends AbstractListTest { + + private Root model; + private SenderRoot senderRoot; + private MqttHandler handler; + + ListManualTest() { + super("list"); + } + + @Override + protected void createModel() { + model = new Root(); + senderRoot = new SenderRoot(); + senderRoot.setInput(0); + model.addSenderRoot(senderRoot); + + receiverRoot = new ReceiverRoot(); + model.addReceiverRoot((ReceiverRoot) receiverRoot); + } + + @Override + protected void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException { + model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS); + + handler = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost()); + assertTrue(handler.waitUntilReady(2, TimeUnit.SECONDS)); + + // add dependencies + senderRoot.addInputDependencyToA(senderRoot); + senderRoot.addInputDependencyToSingleA(senderRoot); + + data = new ReceiverData(); + dataSingle = new ReceiverData(); + handler.newConnection(TOPIC_A, bytes -> data.numberOfElements += 1); + handler.newConnection(TOPIC_SINGLE_A, bytes -> dataSingle.numberOfElements += 1); + + // connect. important: first receivers, then senders. to not miss initial value. + assertTrue(receiverRoot.connectAList(mqttUri(TOPIC_A))); + assertTrue(receiverRoot.connectFromSingleAList(mqttUri(TOPIC_SINGLE_A))); + assertTrue(receiverRoot.connectWithAddFromAList(mqttUri(TOPIC_A))); + assertTrue(receiverRoot.connectWithAddFromSingleAList(mqttUri(TOPIC_SINGLE_A))); + + assertTrue(senderRoot.connectAList(mqttUri(TOPIC_A), writeCurrentValue)); + assertTrue(senderRoot.connectSingleAList(mqttUri(TOPIC_SINGLE_A), writeCurrentValue)); + } + + @Override + protected void setInput(int input) { + senderRoot.setInput(input); + assertEquals(input, senderRoot.getNumA(), "size of normal NTA"); + assertEquals(1, senderRoot.getNumSingleA(), "size of single NTA"); + } + + @Override + protected void disconnectReceive() throws IOException { + ReceiverRoot receiverRootWithLocalType = (ReceiverRoot) receiverRoot; + assertTrue(receiverRootWithLocalType.disconnectAList(mqttUri(TOPIC_A))); + assertTrue(receiverRootWithLocalType.disconnectFromSingleAList(mqttUri(TOPIC_SINGLE_A))); + assertTrue(receiverRootWithLocalType.disconnectWithAddFromAList(mqttUri(TOPIC_A))); + assertTrue(receiverRootWithLocalType.disconnectWithAddFromSingleAList(mqttUri(TOPIC_SINGLE_A))); + } + + @Override + protected void disconnectSend() throws IOException { + assertTrue(senderRoot.disconnectAList(mqttUri(TOPIC_A))); + assertTrue(senderRoot.disconnectSingleAList(mqttUri(TOPIC_SINGLE_A))); + } + + @Override + protected void closeConnections() { + if (handler != null) { + handler.close(); + } + if (model != null) { + model.ragconnectCloseConnections(); + } + } +} diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleList/AbstractSingleListTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleList/AbstractSingleListTest.java new file mode 100644 index 0000000000000000000000000000000000000000..ff1e655370b85e3c75a69d7b5b4cab0a44fb8329 --- /dev/null +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleList/AbstractSingleListTest.java @@ -0,0 +1,342 @@ +package org.jastadd.ragconnect.tests.singleList; + +import org.jastadd.ragconnect.tests.AbstractMqttTest; +import org.jastadd.ragconnect.tests.TestUtils; +import org.jastadd.ragconnect.tests.TestUtils.IntList; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import singleList.ast.MqttHandler; + +import java.io.IOException; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; + +import static org.jastadd.ragconnect.tests.TestUtils.IntList.list; +import static org.jastadd.ragconnect.tests.TestUtils.mqttUri; +import static org.jastadd.ragconnect.tests.TestUtils.testJaddContainReferenceToJackson; +import static org.junit.jupiter.api.Assertions.*; + +/** + * Base class for test cases "singleList manual" and "singleList incremental". + * + * @author rschoene - Initial contribution + */ +@Tag("List") +@Tag("SingleList") +public abstract class AbstractSingleListTest extends AbstractMqttTest { + + public interface TestWrapperJastAddList<T> extends Iterable<T> { + int getNumChild(); + } + public interface TestWrapperReceiverRoot { + TestWrapperJastAddList<? extends TestWrapperA> getAList(); + TestWrapperJastAddList<? extends TestWrapperA> getAs(); + int getNumA(); + int getNumWithAddA(); + TestWrapperA getA(int index); + + TestWrapperJastAddList<? extends TestWrapperA> getWithAddAList(); + TestWrapperJastAddList<? extends TestWrapperA> getUsingWildcardAList(); + TestWrapperJastAddList<? extends TestWrapperA> getUsingWildcardWithAddAList(); + + @SuppressWarnings("unused") boolean connectA(String mqttUri) throws IOException; + boolean connectA(String mqttUri, int index) throws IOException; + boolean connectUsingWildcardA(String mqttUri) throws IOException; + @SuppressWarnings("unused") boolean connectUsingWildcardA(String mqttUri, int index) throws IOException; + boolean connectWithAddA(String mqttUri) throws IOException; + boolean connectUsingWildcardWithAddA(String mqttUri) throws IOException; + + boolean disconnectA(String mqttUri) throws IOException; + boolean disconnectUsingWildcardA(String mqttUri) throws IOException; + boolean disconnectWithAddA(String mqttUri) throws IOException; + boolean disconnectUsingWildcardWithAddA(String mqttUri) throws IOException; + } + @SuppressWarnings("UnusedReturnValue") + public interface TestWrapperSenderRoot { + boolean connectA1(String mqttUri, boolean writeCurrentValue) throws IOException; + boolean connectA2(String mqttUri, boolean writeCurrentValue) throws IOException; + boolean connectA3(String mqttUri, boolean writeCurrentValue) throws IOException; + boolean connectA4(String mqttUri, boolean writeCurrentValue) throws IOException; + boolean connectInOutput(String mqttUri, boolean writeCurrentValue) throws IOException; + + boolean disconnectA1(String mqttUri) throws IOException; + boolean disconnectA2(String mqttUri) throws IOException; + boolean disconnectA3(String mqttUri) throws IOException; + boolean disconnectA4(String mqttUri) throws IOException; + boolean disconnectInOutput(String mqttUri) throws IOException; + + TestWrapperSenderRoot setInput1(int input); + TestWrapperSenderRoot setInput2(int input); + TestWrapperSenderRoot setInput3(int input); + TestWrapperSenderRoot setInput4(int input); + TestWrapperSenderRoot setInOutput(int input); + + TestWrapperA getA1(); + TestWrapperA getA2(); + TestWrapperA getA3(); + TestWrapperA getA4(); + } + public interface TestWrapperA { + int getID(); + } + + AbstractSingleListTest(String shortName) { + this.shortName = shortName; + } + + protected static final String TOPIC_A_1 = "a/first"; + protected static final String TOPIC_A_2 = "a/second"; + protected static final String TOPIC_A_3 = "a/third"; + protected static final String TOPIC_A_4 = "a/fourth"; + protected static final String TOPIC_A_5_INOUT = "a/special"; + protected static final String TOPIC_A_WILDCARD = "a/#"; + + protected TestWrapperSenderRoot senderRoot; + protected TestWrapperReceiverRoot receiverRoot; + protected ReceiverData data; + + private final String shortName; + + @Test + public void checkJacksonReference() { + testJaddContainReferenceToJackson( + Paths.get("src", "test", + "02-after-ragconnect", shortName, "RagConnect.jadd"), true); + } + + @Override + protected void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException, InterruptedException { + // late model initialization + senderRoot.setInput1(0); + senderRoot.setInput2(0); + senderRoot.setInput3(0); + senderRoot.setInput4(0); + senderRoot.setInOutput(0); + + setupReceiverAndConnectPart(); + + // connect. important: first receivers, then senders. to not miss initial value. + + // receive: explicit topic subscription + assertTrue(receiverRoot.connectA(mqttUri(TOPIC_A_1), 0)); + assertTrue(receiverRoot.connectA(mqttUri(TOPIC_A_2), 1)); + assertTrue(receiverRoot.connectA(mqttUri(TOPIC_A_3), 2)); + assertTrue(receiverRoot.connectA(mqttUri(TOPIC_A_4), 3)); + assertTrue(receiverRoot.connectA(mqttUri(TOPIC_A_5_INOUT), 4)); + + assertTrue(receiverRoot.connectWithAddA(mqttUri(TOPIC_A_1))); + assertTrue(receiverRoot.connectWithAddA(mqttUri(TOPIC_A_2))); + assertTrue(receiverRoot.connectWithAddA(mqttUri(TOPIC_A_3))); + assertTrue(receiverRoot.connectWithAddA(mqttUri(TOPIC_A_4))); + assertTrue(receiverRoot.connectWithAddA(mqttUri(TOPIC_A_5_INOUT))); + + // receive: wildcard subscription + assertTrue(receiverRoot.connectUsingWildcardA(mqttUri(TOPIC_A_WILDCARD))); + assertTrue(receiverRoot.connectUsingWildcardWithAddA(mqttUri(TOPIC_A_WILDCARD))); + + // send: explicit topics, wait between connections to ensure correct arrival at receiver + MqttHandler checkArrivalHandler = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost()); + Map<String, CountDownLatch> arrived = new HashMap<>() {{ + put(TOPIC_A_1, new CountDownLatch(1)); + put(TOPIC_A_2, new CountDownLatch(1)); + put(TOPIC_A_3, new CountDownLatch(1)); + put(TOPIC_A_4, new CountDownLatch(1)); + }}; + checkArrivalHandler.waitUntilReady(2, TimeUnit.SECONDS); + checkArrivalHandler.newConnection("#", (topic, bytes) -> + Optional.ofNullable(arrived.get(topic)).ifPresent(CountDownLatch::countDown)); + + assertTrue(senderRoot.connectA4(mqttUri(TOPIC_A_4), writeCurrentValue)); + if (writeCurrentValue) { + assertTrue(arrived.get(TOPIC_A_4).await(2, TimeUnit.SECONDS)); + } + + assertTrue(senderRoot.connectA3(mqttUri(TOPIC_A_3), writeCurrentValue)); + if (writeCurrentValue) { + assertTrue(arrived.get(TOPIC_A_3).await(2, TimeUnit.SECONDS)); + } + + assertTrue(senderRoot.connectA2(mqttUri(TOPIC_A_2), writeCurrentValue)); + if (writeCurrentValue) { + assertTrue(arrived.get(TOPIC_A_2).await(2, TimeUnit.SECONDS)); + } + + assertTrue(senderRoot.connectA1(mqttUri(TOPIC_A_1), writeCurrentValue)); + if (writeCurrentValue) { + assertTrue(arrived.get(TOPIC_A_1).await(2, TimeUnit.SECONDS)); + } + + assertTrue(senderRoot.connectInOutput(mqttUri(TOPIC_A_5_INOUT), writeCurrentValue)); + // no need to wait here, because first "checkTree" will wait anyway + checkArrivalHandler.close(); + } + + abstract protected void setupReceiverAndConnectPart() throws IOException; + + @Override + protected void communicateSendInitialValue() throws InterruptedException, IOException { + checkTree(5, list(1, 2, 3, 4, 0), list(4, 3, 2, 1, 0), // normal: (normal / wildcard) + list(4, 3, 2, 1, 0), list(4, 3, 2, 1, 0)); // withAdd: (normal / wildcard) + + // A1 will be 2 (1+1, previously 1) + setInput(1, 1); + checkTree(6, list(2, 2, 3, 4, 0), list(4, 3, 2, 2, 0), // normal: (normal / wildcard) + list(4, 3, 2, 1, 0, 2), list(4, 3, 2, 1, 0, 2)); // withAdd: (normal / wildcard) + + // A1 should stay at 2 + setInput(1, 1); + checkTree(6, list(2, 2, 3, 4, 0), list(4, 3, 2, 2, 0), // normal: (normal / wildcard) + list(4, 3, 2, 1, 0, 2), list(4, 3, 2, 1, 0, 2)); // withAdd: (normal / wildcard) + + // A1 will be 3 (2+1, previously 2) + setInput(1, 2); + checkTree(7, list(3, 2, 3, 4, 0), list(4, 3, 2, 3, 0), // normal: (normal / wildcard) + list(4, 3, 2, 1, 0, 2, 3), list(4, 3, 2, 1, 0, 2, 3)); // withAdd: (normal / wildcard) + + // InOut will be 5 (previously 0) + setInput(5, 5); + checkTree(8, list(3, 2, 3, 4, 5), list(4, 3, 2, 3, 5), // normal: (normal / wildcard) + list(4, 3, 2, 1, 0, 2, 3, 5), list(4, 3, 2, 1, 0, 2, 3, 5)); // withAdd: (normal / wildcard) + + // A3 will be 7 (4+3, previously 3) + setInput(3, 4); + checkTree(9, list(3, 2, 7, 4, 5), list(4, 7, 2, 3, 5), // normal: (normal / wildcard) + list(4, 3, 2, 1, 0, 2, 3, 5, 7), list(4, 3, 2, 1, 0, 2, 3, 5, 7)); // withAdd: (normal / wildcard) + + // A2 will be send, but not received + disconnectReceive(); + setInput(2, 5); + checkTree(10, list(3, 2, 7, 4, 5), list(4, 7, 2, 3, 5), // normal: (normal / wildcard) + list(4, 3, 2, 1, 0, 2, 3, 5, 7), list(4, 3, 2, 1, 0, 2, 3, 5, 7)); // withAdd: (normal / wildcard) + + // A2 will not be send + disconnectSend(); + setInput(2, 7); + checkTree(10, list(3, 2, 7, 4, 5), list(4, 7, 2, 3, 5), // normal: (normal / wildcard) + list(4, 3, 2, 1, 0, 2, 3, 5, 7), list(4, 3, 2, 1, 0, 2, 3, 5, 7)); // withAdd: (normal / wildcard) + } + + @Override + protected void communicateOnlyUpdatedValue() throws InterruptedException, IOException { + checkTree(0, list(0, 0, 0, 0, 0), list(), // normal + list(), list()); // withAdd + + // A1 will be 2 (1+1, previously 1) + setInput(1, 1); + checkTree(1, list(2, 0, 0, 0, 0), list(2), // normal + list(2), list(2)); // withAdd + + // A1 should stay at 2 + setInput(1, 1); + checkTree(1, list(2, 0, 0, 0, 0), list(2), // normal + list(2), list(2)); // withAdd + + // A1 will be 3 (2+1, previously 2) + setInput(1, 2); + checkTree(2, list(3, 0, 0, 0, 0), list(3), // normal + list(2, 3), list(2, 3)); // withAdd + + // InOut will be 5 (previously 0) + setInput(5, 5); + checkTree(3, list(3, 0, 0, 0, 5), list(3, 5), // normal + list(2, 3, 5), list(2, 3, 5)); // withAdd + + // A3 will be 7 (4+3, previously 3) + setInput(3, 4); + checkTree(4, list(3, 0, 7, 0, 5), list(3,5,7), // normal + list(2, 3, 5, 7), list(2, 3, 5, 7)); // withAdd + + // A2 will be send, but not received + disconnectReceive(); + setInput(2, 5); + checkTree(5, list(3, 0, 7, 0, 5), list(3,5,7), // normal + list(2, 3, 5, 7), list(2, 3, 5, 7)); // withAdd + + // A2 will not be send + disconnectSend(); + setInput(2, 7); + checkTree(5, list(3, 0, 7, 0, 5), list(3,5,7), // normal + list(2, 3, 5, 7), list(2, 3, 5, 7)); // withAdd + } + + protected void disconnectReceive() throws IOException { + assertTrue(receiverRoot.disconnectA(mqttUri(TOPIC_A_1))); + assertTrue(receiverRoot.disconnectA(mqttUri(TOPIC_A_2))); + assertTrue(receiverRoot.disconnectA(mqttUri(TOPIC_A_3))); + assertTrue(receiverRoot.disconnectA(mqttUri(TOPIC_A_4))); + assertTrue(receiverRoot.disconnectA(mqttUri(TOPIC_A_5_INOUT))); + + assertTrue(receiverRoot.disconnectWithAddA(mqttUri(TOPIC_A_1))); + assertTrue(receiverRoot.disconnectWithAddA(mqttUri(TOPIC_A_2))); + assertTrue(receiverRoot.disconnectWithAddA(mqttUri(TOPIC_A_3))); + assertTrue(receiverRoot.disconnectWithAddA(mqttUri(TOPIC_A_4))); + assertTrue(receiverRoot.disconnectWithAddA(mqttUri(TOPIC_A_5_INOUT))); + + // receive: wildcard subscription + assertTrue(receiverRoot.disconnectUsingWildcardA(mqttUri(TOPIC_A_WILDCARD))); + assertTrue(receiverRoot.disconnectUsingWildcardWithAddA(mqttUri(TOPIC_A_WILDCARD))); + } + + protected void disconnectSend() throws IOException { + assertTrue(senderRoot.disconnectA4(mqttUri(TOPIC_A_4))); + assertTrue(senderRoot.disconnectA3(mqttUri(TOPIC_A_3))); + assertTrue(senderRoot.disconnectA2(mqttUri(TOPIC_A_2))); + assertTrue(senderRoot.disconnectA1(mqttUri(TOPIC_A_1))); + assertTrue(senderRoot.disconnectInOutput(mqttUri(TOPIC_A_5_INOUT))); + } + + protected void setInput(int index, int input) { + int actualComputedValue; + switch (index) { + case 1: senderRoot.setInput1(input); actualComputedValue = senderRoot.getA1().getID(); break; + case 2: senderRoot.setInput2(input); actualComputedValue = senderRoot.getA2().getID(); break; + case 3: senderRoot.setInput3(input); actualComputedValue = senderRoot.getA3().getID(); break; + case 4: senderRoot.setInput4(input); actualComputedValue = senderRoot.getA4().getID(); break; + case 5: senderRoot.setInOutput(input); return; + default: fail("Wrong index " + index); return; + } + assertEquals(input + index, actualComputedValue, "ID value of single A"); + } + + private void checkTree(int expectedTransmissions, IntList normalA, IntList usingWildcardA, IntList withAddA, IntList usingWildcardWithAddA) throws InterruptedException { + TestUtils.waitForMqtt(); + assertEquals(expectedTransmissions, data.numberOfElements, "transmissions for any A"); + + checkList(normalA.toList(), receiverRoot.getNumA(), receiverRoot::getA); + checkList(normalA.toList(), receiverRoot.getAList()); + + checkList(usingWildcardA.toList(), receiverRoot.getUsingWildcardAList()); + + checkList(withAddA.toList(), receiverRoot.getWithAddAList()); + + checkList(usingWildcardWithAddA.toList(), receiverRoot.getUsingWildcardWithAddAList()); + } + + private void checkList(List<Integer> expectedList, int numChildren, Function<Integer, TestWrapperA> getA) { + assertEquals(expectedList.size(), numChildren, "same list size"); + for (int index = 0; index < expectedList.size(); index++) { + TestWrapperA a = getA.apply(index); + assertEquals(expectedList.get(index), a.getID(), "correct ID for A"); + } + } + + private void checkList(List<Integer> expectedList, TestWrapperJastAddList<? extends TestWrapperA> actualList) { + assertEquals(expectedList.size(), actualList.getNumChild(), "same list size"); + int index = 0; + for (TestWrapperA a : actualList) { + assertEquals(expectedList.get(index), a.getID(), "correct ID for A"); + index++; + } + } + + protected static class ReceiverData { + int numberOfElements = 0; + } + +} diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleList/SingleListIncrementalTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleList/SingleListIncrementalTest.java new file mode 100644 index 0000000000000000000000000000000000000000..54bbf675b2ad4c594a5c001cfaca7342248fbfbc --- /dev/null +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleList/SingleListIncrementalTest.java @@ -0,0 +1,67 @@ +package org.jastadd.ragconnect.tests.singleList; + +import org.jastadd.ragconnect.tests.TestUtils; +import org.junit.jupiter.api.Tag; +import singleListInc.ast.*; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Test case "single list incremental". + * + * @author rschoene - Initial contribution + */ +@Tag("Incremental") +public class SingleListIncrementalTest extends AbstractSingleListTest { + + private Root model; + private MqttHandler handler; + + SingleListIncrementalTest() { + super("singleListInc"); + } + + @Override + protected void createModel() { + model = new Root(); + senderRoot = new SenderRoot(); + model.addSenderRoot((SenderRoot) senderRoot); + + ReceiverRoot localReceiverRoot = new ReceiverRoot(); + model.addReceiverRoot(localReceiverRoot); + + // first prepare non-wildcard lists + for (int i = 0; i < 5; i++) { + localReceiverRoot.addA(new A()); + } + receiverRoot = localReceiverRoot; + assertEquals(5, receiverRoot.getNumA()); + } + + @Override + protected void setupReceiverAndConnectPart() throws IOException { + model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS); + + handler = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost()); + assertTrue(handler.waitUntilReady(2, TimeUnit.SECONDS)); + + // no dependencies + + data = new ReceiverData(); + handler.newConnection(TOPIC_A_WILDCARD, bytes -> data.numberOfElements += 1); + } + + @Override + protected void closeConnections() { + if (handler != null) { + handler.close(); + } + if (model != null) { + model.ragconnectCloseConnections(); + } + } +} diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleList/SingleListManualTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleList/SingleListManualTest.java new file mode 100644 index 0000000000000000000000000000000000000000..9a8bf0a0d36d5a2f6e370bb7563453f00e0b5600 --- /dev/null +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleList/SingleListManualTest.java @@ -0,0 +1,69 @@ +package org.jastadd.ragconnect.tests.singleList; + +import org.jastadd.ragconnect.tests.TestUtils; +import singleList.ast.*; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Test case "single list manual". + * + * @author rschoene - Initial contribution + */ +public class SingleListManualTest extends AbstractSingleListTest { + + private Root model; + private MqttHandler handler; + + SingleListManualTest() { + super("singleList"); + } + + @Override + protected void createModel() { + model = new Root(); + senderRoot = new SenderRoot(); + model.addSenderRoot((SenderRoot) senderRoot); + + ReceiverRoot localReceiverRoot = new ReceiverRoot(); + model.addReceiverRoot(localReceiverRoot); + + // first prepare non-wildcard lists + for (int i = 0; i < 5; i++) { + localReceiverRoot.addA(new A()); + } + receiverRoot = localReceiverRoot; + assertEquals(5, receiverRoot.getNumA()); + } + + @Override + protected void setupReceiverAndConnectPart() throws IOException { + model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS); + + handler = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost()); + assertTrue(handler.waitUntilReady(2, TimeUnit.SECONDS)); + + // add dependencies + ((SenderRoot) senderRoot).addInputDependencyToA1((SenderRoot) senderRoot); + ((SenderRoot) senderRoot).addInputDependencyToA2((SenderRoot) senderRoot); + ((SenderRoot) senderRoot).addInputDependencyToA3((SenderRoot) senderRoot); + ((SenderRoot) senderRoot).addInputDependencyToA4((SenderRoot) senderRoot); + + data = new ReceiverData(); + handler.newConnection(TOPIC_A_WILDCARD, bytes -> data.numberOfElements += 1); + } + + @Override + protected void closeConnections() { + if (handler != null) { + handler.close(); + } + if (model != null) { + model.ragconnectCloseConnections(); + } + } +} diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleListVariant/AbstractSingleListVariantTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleListVariant/AbstractSingleListVariantTest.java new file mode 100644 index 0000000000000000000000000000000000000000..d4d5f8397b9d8478435c2c8431dd6069800e5a36 --- /dev/null +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleListVariant/AbstractSingleListVariantTest.java @@ -0,0 +1,477 @@ +package org.jastadd.ragconnect.tests.singleListVariant; + +import org.assertj.core.api.Assertions; +import org.jastadd.ragconnect.tests.AbstractMqttTest; +import org.jastadd.ragconnect.tests.TestUtils; +import org.jastadd.ragconnect.tests.TestUtils.IntList; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.file.Paths; +import java.util.List; +import java.util.function.BiConsumer; + +import static java.lang.Math.abs; +import static org.jastadd.ragconnect.tests.TestUtils.IntList.list; +import static org.jastadd.ragconnect.tests.TestUtils.mqttUri; +import static org.jastadd.ragconnect.tests.TestUtils.testJaddContainReferenceToJackson; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Base class for test cases "singleList variant manual" and "singleList variant incremental". + * + * @author rschoene - Initial contribution + */ +@Tag("List") +@Tag("SingleList") +public abstract class AbstractSingleListVariantTest extends AbstractMqttTest { + + public interface TestWrapperJastAddList<T> extends Iterable<T> { + int getNumChild(); + } + public interface TestWrapperReceiverRoot { + TestWrapperJastAddList<? extends TestWrapperT_Empty> getT_EmptyList(); + TestWrapperJastAddList<? extends TestWrapperT_Token> getT_TokenList(); + TestWrapperJastAddList<? extends TestWrapperT_OneChild> getT_OneChildList(); + TestWrapperJastAddList<? extends TestWrapperT_OneOpt> getT_OneOptList(); + TestWrapperJastAddList<? extends TestWrapperT_OneList> getT_OneListList(); + TestWrapperJastAddList<? extends TestWrapperT_TwoChildren> getT_TwoChildrenList(); + TestWrapperJastAddList<? extends TestWrapperT_OneOfEach> getT_OneOfEachList(); + TestWrapperJastAddList<? extends TestWrapperT_Abstract> getT_AbstractList(); + + TestWrapperJastAddList<? extends TestWrapperT_Empty> getMyEmptyList(); + + TestWrapperJastAddList<? extends TestWrapperT_Empty> getEmptyWithAddList(); + TestWrapperJastAddList<? extends TestWrapperT_Token> getTokenWithAddList(); + TestWrapperJastAddList<? extends TestWrapperT_OneChild> getOneChildWithAddList(); + TestWrapperJastAddList<? extends TestWrapperT_OneOpt> getOneOptWithAddList(); + TestWrapperJastAddList<? extends TestWrapperT_OneList> getOneListWithAddList(); + TestWrapperJastAddList<? extends TestWrapperT_TwoChildren> getTwoChildrenWithAddList(); + TestWrapperJastAddList<? extends TestWrapperT_OneOfEach> getOneOfEachWithAddList(); + TestWrapperJastAddList<? extends TestWrapperT_Abstract> getAbstractWithAddList(); + + boolean connectT_Empty(String uriString) throws IOException; + boolean connectT_Token(String uriString) throws IOException; + boolean connectT_OneChild(String uriString) throws IOException; + boolean connectT_OneOpt(String uriString) throws IOException; + boolean connectT_OneList(String uriString) throws IOException; + boolean connectT_TwoChildren(String uriString) throws IOException; + boolean connectT_OneOfEach(String uriString) throws IOException; + boolean connectT_Abstract(String uriString) throws IOException; + + boolean connectMyEmpty(String uriString) throws IOException; + + boolean connectEmptyWithAdd(String uriString) throws IOException; + boolean connectTokenWithAdd(String uriString) throws IOException; + boolean connectOneChildWithAdd(String uriString) throws IOException; + boolean connectOneOptWithAdd(String uriString) throws IOException; + boolean connectOneListWithAdd(String uriString) throws IOException; + boolean connectTwoChildrenWithAdd(String uriString) throws IOException; + boolean connectOneOfEachWithAdd(String uriString) throws IOException; + boolean connectAbstractWithAdd(String uriString) throws IOException; + + boolean disconnectT_Empty(String uriString) throws IOException; + boolean disconnectT_Token(String uriString) throws IOException; + boolean disconnectT_OneChild(String uriString) throws IOException; + boolean disconnectT_OneOpt(String uriString) throws IOException; + boolean disconnectT_OneList(String uriString) throws IOException; + boolean disconnectT_TwoChildren(String uriString) throws IOException; + boolean disconnectT_OneOfEach(String uriString) throws IOException; + boolean disconnectT_Abstract(String uriString) throws IOException; + + boolean disconnectMyEmpty(String uriString) throws IOException; + + boolean disconnectEmptyWithAdd(String uriString) throws IOException; + boolean disconnectTokenWithAdd(String uriString) throws IOException; + boolean disconnectOneChildWithAdd(String uriString) throws IOException; + boolean disconnectOneOptWithAdd(String uriString) throws IOException; + boolean disconnectOneListWithAdd(String uriString) throws IOException; + boolean disconnectTwoChildrenWithAdd(String uriString) throws IOException; + boolean disconnectOneOfEachWithAdd(String uriString) throws IOException; + boolean disconnectAbstractWithAdd(String uriString) throws IOException; + } + public interface TestWrapperSenderRoot { + boolean connectT_Empty(String uriString, boolean writeCurrentValue) throws IOException; + boolean connectT_Token(String uriString, boolean writeCurrentValue) throws IOException; + boolean connectT_OneChild(String uriString, boolean writeCurrentValue) throws IOException; + boolean connectT_OneOpt(String uriString, boolean writeCurrentValue) throws IOException; + boolean connectT_OneList(String uriString, boolean writeCurrentValue) throws IOException; + boolean connectT_TwoChildren(String uriString, boolean writeCurrentValue) throws IOException; + boolean connectT_OneOfEach(String uriString, boolean writeCurrentValue) throws IOException; + boolean connectT_Abstract(String uriString, boolean writeCurrentValue) throws IOException; + + boolean disconnectT_Empty(String uriString) throws IOException; + boolean disconnectT_Token(String uriString) throws IOException; + boolean disconnectT_OneChild(String uriString) throws IOException; + boolean disconnectT_OneOpt(String uriString) throws IOException; + boolean disconnectT_OneList(String uriString) throws IOException; + boolean disconnectT_TwoChildren(String uriString) throws IOException; + boolean disconnectT_OneOfEach(String uriString) throws IOException; + boolean disconnectT_Abstract(String uriString) throws IOException; + + TestWrapperSenderRoot setInput(int input); + @SuppressWarnings("UnusedReturnValue") + TestWrapperSenderRoot setShouldSetOptAndList(boolean shouldSetOptAndList); + TestWrapperT_Empty getT_Empty(); + TestWrapperT_OneOpt getT_OneOpt(); + } + public interface TestWrapperNameable { + int getID(); + } + public interface TestWrapperOther extends TestWrapperNameable {} + public interface TestWrapperT_Empty extends TestWrapperNameable {} + public interface TestWrapperT_Token extends TestWrapperNameable { + String getValue(); + } + public interface TestWrapperT_OneChild extends TestWrapperNameable { + TestWrapperNameable getOther(); + } + public interface TestWrapperT_OneOpt extends TestWrapperNameable { + boolean hasOther(); + TestWrapperNameable getOther(); + } + public interface TestWrapperT_OneList extends TestWrapperNameable { + int getNumOther(); + TestWrapperNameable getOther(int index); + } + public interface TestWrapperT_TwoChildren extends TestWrapperNameable { + TestWrapperNameable getLeft(); + TestWrapperNameable getRight(); + } + public interface TestWrapperT_OneOfEach extends TestWrapperNameable { + TestWrapperNameable getFirst(); + boolean hasSecond(); + TestWrapperNameable getSecond(); + int getNumThird(); + TestWrapperNameable getThird(int index); + String getFourth(); + } + public interface TestWrapperT_Abstract extends TestWrapperNameable { + String getValueAbstract(); + String getValueSub(); + } + + AbstractSingleListVariantTest(String shortName) { + this.shortName = shortName; + } + + protected static final String TOPIC_T_Empty = "t/Empty"; + protected static final String TOPIC_T_Token = "t/Token"; + protected static final String TOPIC_T_OneChild = "t/OneChild"; + protected static final String TOPIC_T_OneOpt = "t/OneOpt"; + protected static final String TOPIC_T_OneList = "t/OneList"; + protected static final String TOPIC_T_TwoChildren = "t/TwoChildren"; + protected static final String TOPIC_T_OneOfEach = "t/OneOfEach"; + protected static final String TOPIC_T_Abstract = "t/Abstract"; + protected static final String TOPIC_T_all = "t/#"; + + protected TestWrapperSenderRoot senderRoot; + protected TestWrapperReceiverRoot receiverRoot; + protected ReceiverData data; + + private final String shortName; + + @Test + public void checkJacksonReference() { + testJaddContainReferenceToJackson( + Paths.get("src", "test", + "02-after-ragconnect", shortName, "RagConnect.jadd"), true); + } + + @Override + protected void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException, InterruptedException { + // late model initialization + setInput(0); + setShouldSetOptAndList(false); + + setupReceiverAndConnectPart(); + + // connect. important: first receivers, then senders. to not miss initial value. + + // receive: unnamed + assertTrue(receiverRoot.connectT_Empty(mqttUri(TOPIC_T_Empty))); + assertTrue(receiverRoot.connectT_Token(mqttUri(TOPIC_T_Token))); + assertTrue(receiverRoot.connectT_OneChild(mqttUri(TOPIC_T_OneChild))); + assertTrue(receiverRoot.connectT_OneOpt(mqttUri(TOPIC_T_OneOpt))); + assertTrue(receiverRoot.connectT_OneList(mqttUri(TOPIC_T_OneList))); + assertTrue(receiverRoot.connectT_TwoChildren(mqttUri(TOPIC_T_TwoChildren))); + assertTrue(receiverRoot.connectT_OneOfEach(mqttUri(TOPIC_T_OneOfEach))); + assertTrue(receiverRoot.connectT_Abstract(mqttUri(TOPIC_T_Abstract))); + + // receive: named + assertTrue(receiverRoot.connectMyEmpty(mqttUri(TOPIC_T_Empty))); + + // receive: with add + assertTrue(receiverRoot.connectEmptyWithAdd(mqttUri(TOPIC_T_Empty))); + assertTrue(receiverRoot.connectTokenWithAdd(mqttUri(TOPIC_T_Token))); + assertTrue(receiverRoot.connectOneChildWithAdd(mqttUri(TOPIC_T_OneChild))); + assertTrue(receiverRoot.connectOneOptWithAdd(mqttUri(TOPIC_T_OneOpt))); + assertTrue(receiverRoot.connectOneListWithAdd(mqttUri(TOPIC_T_OneList))); + assertTrue(receiverRoot.connectTwoChildrenWithAdd(mqttUri(TOPIC_T_TwoChildren))); + assertTrue(receiverRoot.connectOneOfEachWithAdd(mqttUri(TOPIC_T_OneOfEach))); + assertTrue(receiverRoot.connectAbstractWithAdd(mqttUri(TOPIC_T_Abstract))); + + // send + assertTrue(senderRoot.connectT_Empty(mqttUri(TOPIC_T_Empty), writeCurrentValue)); + assertTrue(senderRoot.connectT_Token(mqttUri(TOPIC_T_Token), writeCurrentValue)); + assertTrue(senderRoot.connectT_OneChild(mqttUri(TOPIC_T_OneChild), writeCurrentValue)); + assertTrue(senderRoot.connectT_OneOpt(mqttUri(TOPIC_T_OneOpt), writeCurrentValue)); + assertTrue(senderRoot.connectT_OneList(mqttUri(TOPIC_T_OneList), writeCurrentValue)); + assertTrue(senderRoot.connectT_TwoChildren(mqttUri(TOPIC_T_TwoChildren), writeCurrentValue)); + assertTrue(senderRoot.connectT_OneOfEach(mqttUri(TOPIC_T_OneOfEach), writeCurrentValue)); + assertTrue(senderRoot.connectT_Abstract(mqttUri(TOPIC_T_Abstract), writeCurrentValue)); + } + + abstract protected void setupReceiverAndConnectPart() throws IOException; + + @Override + protected void communicateSendInitialValue() throws IOException, InterruptedException { + // transmissions: 8 * 1 = 8 + checkTree(8, list(-0), list(0), list(-0)); + + setInput(1); + // transmissions: 8 + 8 = 16 + checkTree(16, list(-1), list(0, 1), list(-0, -1)); + + setInput(1); + // transmissions: 16 + checkTree(16, list(-1), list(0, 1), list(-0, -1)); + + setShouldSetOptAndList(true); + // transmissions: 16 + 3 = 19 + checkTree(19, list(1), list(0, 1), list(-0, -1, 1)); + + setShouldSetOptAndList(true); + // transmissions: 19 + checkTree(19, list(1), list(0, 1), list(-0, -1, 1)); + + setInput(2); + // transmissions: 19 + 8 = 27 + checkTree(27, list(2), list(0, 1, 2), list(-0, -1, 1, 2)); + + setInput(5); + // transmissions: 27 + 8 = 35 + checkTree(35, list(5), list(0, 1, 2, 5), list(-0, -1, 1, 2, 5)); + + disconnectReceive(); + setInput(6); // does not affect receive + // transmissions: 35 + 8 = 43 + checkTree(43, list(5), list(0, 1, 2, 5), list(-0, -1, 1, 2, 5)); + + disconnectSend(); + setInput(7); // not sent + // transmissions: 43 + checkTree(43, list(5), list(0, 1, 2, 5), list(-0, -1, 1, 2, 5)); + } + + @Override + protected void communicateOnlyUpdatedValue() throws IOException, InterruptedException { + // transmissions: 0 + checkTree(0, list(), list(), list()); + + setInput(1); + // transmissions: 8 * 1 = 0 + checkTree(8, list(-1), list(1), list(-1)); + + setInput(1); + // transmissions: 8 + checkTree(8, list(-1), list(1), list(-1)); + + setShouldSetOptAndList(true); + // transmissions: 8 + 3 = 11 + checkTree(11, list(1), list(1), list(-1, 1)); + + setShouldSetOptAndList(true); + // transmissions: 11 + checkTree(11, list(1), list(1), list(-1, 1)); + + setInput(2); + // transmissions: 11 + 8 = 19 + checkTree(19, list(2), list(1, 2), list(-1, 1, 2)); + + setInput(5); + // transmissions: 19 + 8 = 27 + checkTree(27, list(5), list(1, 2, 5), list(-1, 1, 2, 5)); + + disconnectReceive(); + setInput(6); // does not affect receive + // transmissions: 27 + 8 = 35 + checkTree(35, list(5), list(1, 2, 5), list(-1, 1, 2, 5)); + + disconnectSend(); + setInput(7); // not sent + // transmissions: 35 + checkTree(35, list(5), list(1, 2, 5), list(-1, 1, 2, 5)); + } + + private void disconnectReceive() throws IOException { + // receive: unnamed + assertTrue(receiverRoot.disconnectT_Empty(mqttUri(TOPIC_T_Empty))); + assertTrue(receiverRoot.disconnectT_Token(mqttUri(TOPIC_T_Token))); + assertTrue(receiverRoot.disconnectT_OneChild(mqttUri(TOPIC_T_OneChild))); + assertTrue(receiverRoot.disconnectT_OneOpt(mqttUri(TOPIC_T_OneOpt))); + assertTrue(receiverRoot.disconnectT_OneList(mqttUri(TOPIC_T_OneList))); + assertTrue(receiverRoot.disconnectT_TwoChildren(mqttUri(TOPIC_T_TwoChildren))); + assertTrue(receiverRoot.disconnectT_OneOfEach(mqttUri(TOPIC_T_OneOfEach))); + assertTrue(receiverRoot.disconnectT_Abstract(mqttUri(TOPIC_T_Abstract))); + + // receive: named + assertTrue(receiverRoot.disconnectMyEmpty(mqttUri(TOPIC_T_Empty))); + + // receive: with add + assertTrue(receiverRoot.disconnectEmptyWithAdd(mqttUri(TOPIC_T_Empty))); + assertTrue(receiverRoot.disconnectTokenWithAdd(mqttUri(TOPIC_T_Token))); + assertTrue(receiverRoot.disconnectOneChildWithAdd(mqttUri(TOPIC_T_OneChild))); + assertTrue(receiverRoot.disconnectOneOptWithAdd(mqttUri(TOPIC_T_OneOpt))); + assertTrue(receiverRoot.disconnectOneListWithAdd(mqttUri(TOPIC_T_OneList))); + assertTrue(receiverRoot.disconnectTwoChildrenWithAdd(mqttUri(TOPIC_T_TwoChildren))); + assertTrue(receiverRoot.disconnectOneOfEachWithAdd(mqttUri(TOPIC_T_OneOfEach))); + assertTrue(receiverRoot.disconnectAbstractWithAdd(mqttUri(TOPIC_T_Abstract))); + } + + private void disconnectSend() throws IOException { + // send + assertTrue(senderRoot.disconnectT_Empty(mqttUri(TOPIC_T_Empty))); + assertTrue(senderRoot.disconnectT_Token(mqttUri(TOPIC_T_Token))); + assertTrue(senderRoot.disconnectT_OneChild(mqttUri(TOPIC_T_OneChild))); + assertTrue(senderRoot.disconnectT_OneOpt(mqttUri(TOPIC_T_OneOpt))); + assertTrue(senderRoot.disconnectT_OneList(mqttUri(TOPIC_T_OneList))); + assertTrue(senderRoot.disconnectT_TwoChildren(mqttUri(TOPIC_T_TwoChildren))); + assertTrue(senderRoot.disconnectT_OneOfEach(mqttUri(TOPIC_T_OneOfEach))); + assertTrue(senderRoot.disconnectT_Abstract(mqttUri(TOPIC_T_Abstract))); + } + + protected void setInput(int input) { + senderRoot.setInput(input); + assertEquals(input, senderRoot.getT_Empty().getID(), "ID value of empty"); + } + + protected void setShouldSetOptAndList(boolean shouldSetOptAndList) { + senderRoot.setShouldSetOptAndList(shouldSetOptAndList); + assertEquals(shouldSetOptAndList, senderRoot.getT_OneOpt().hasOther(), "opt is filled or not"); + } + + /** + * Check against expected lists of IDs. + * If an ID is negative, do not check Opts and Lists, but use absolute value for comparison. + * The tests starts with ID 0 and does not use opts and lists at this point, so checking with > 0 is used. + * @param expectedTransmissions expected number of total transmissions + * @param expectedList ids for unnamed and named endpoints without add + * @param expectedWithAddList ids for endpoints with add, but not those with opts and lists + * (only positive numbers can/need to be passed) + * @param expectedWithAddListForOptAndList ids for endpoints with add and with opts and lists + * @throws InterruptedException if interrupted in TestUtils.waitForMqtt + */ + private void checkTree(int expectedTransmissions, + IntList expectedList, + IntList expectedWithAddList, + IntList expectedWithAddListForOptAndList) + throws InterruptedException { + TestUtils.waitForMqtt(); + assertEquals(expectedTransmissions, data.numberOfElements, "transmissions for any element"); + + // check unnamed + checkList(expectedList, receiverRoot.getT_EmptyList(), (e, n) -> {}); + checkList(expectedList, receiverRoot.getT_TokenList(), + (e, n) -> assertEquals(Integer.toString(abs(e)), n.getValue())); + checkList(expectedList, receiverRoot.getT_OneChildList(), + (e, n) -> assertEquals(abs(e) + 1, n.getOther().getID())); + checkList(expectedList, receiverRoot.getT_OneOptList(), + (e, n) -> { + assertEquals(e > 0, n.hasOther()); + if (n.hasOther()) { + assertEquals(abs(e) + 1, n.getOther().getID()); + } + }); + checkList(expectedList, receiverRoot.getT_OneListList(), + (e, n) -> { + assertEquals(e > 0 ? 1 : 0, n.getNumOther()); + if (n.getNumOther() > 0) { + assertEquals(abs(e) + 1, n.getOther(0).getID()); + } + }); + checkList(expectedList, receiverRoot.getT_TwoChildrenList(), + (e, n) -> { + assertEquals(abs(e) + 1, n.getLeft().getID()); + assertEquals(abs(e) + 1, n.getRight().getID()); + }); + checkList(expectedList, receiverRoot.getT_OneOfEachList(), + (e, n) -> { + assertEquals(abs(e) + 1, n.getFirst().getID()); + assertEquals(e > 0, n.hasSecond()); + if (n.hasSecond()) { + assertEquals(abs(e) + 1, n.getSecond().getID()); + } + assertEquals(e > 0 ? 1 : 0, n.getNumThird()); + if (n.getNumThird() > 0) { + assertEquals(abs(e) + 1, n.getThird(0).getID()); + } + assertEquals(Integer.toString(abs(e)), n.getFourth()); + }); + checkList(expectedList, receiverRoot.getT_AbstractList(), + (e, n) -> { + assertEquals(Integer.toString(abs(e)), n.getValueAbstract()); + assertEquals(Integer.toString(abs(e)), n.getValueSub()); + }); + + // check named + checkList(expectedList, receiverRoot.getMyEmptyList(), (e, n) -> {}); + + // check with add + checkList(expectedWithAddList, receiverRoot.getEmptyWithAddList(), (e, n) -> {}); + checkList(expectedWithAddList, receiverRoot.getTokenWithAddList(), + (e, n) -> assertEquals(Integer.toString(abs(e)), n.getValue())); + checkList(expectedWithAddList, receiverRoot.getOneChildWithAddList(), + (e, n) -> assertEquals(abs(e) + 1, n.getOther().getID())); + checkList(expectedWithAddListForOptAndList, receiverRoot.getOneOptWithAddList(), + (e, n) -> { + if (n.hasOther()) { + assertEquals(abs(e) + 1, n.getOther().getID()); + } + }); + checkList(expectedWithAddListForOptAndList, receiverRoot.getOneListWithAddList(), + (e, n) -> { + if (n.getNumOther() > 0) { + assertEquals(abs(e) + 1, n.getOther(0).getID()); + } + }); + checkList(expectedWithAddList, receiverRoot.getTwoChildrenWithAddList(), + (e, n) -> { + assertEquals(abs(e) + 1, n.getLeft().getID()); + assertEquals(abs(e) + 1, n.getRight().getID()); + }); + checkList(expectedWithAddListForOptAndList, receiverRoot.getOneOfEachWithAddList(), + (e, n) -> { + assertEquals(abs(e) + 1, n.getFirst().getID()); + if (n.hasSecond()) { + assertEquals(abs(e) + 1, n.getSecond().getID()); + } + if (n.getNumThird() > 0) { + assertEquals(abs(e) + 1, n.getThird(0).getID()); + } + assertEquals(Integer.toString(abs(e)), n.getFourth()); + }); + checkList(expectedWithAddList, receiverRoot.getAbstractWithAddList(), + (e, n) -> { + assertEquals(Integer.toString(abs(e)), n.getValueAbstract()); + assertEquals(Integer.toString(abs(e)), n.getValueSub()); + }); + } + + private <T extends TestWrapperNameable> void checkList(IntList expectedList, TestWrapperJastAddList<T> actualList, BiConsumer<Integer, T> additionalTest) { + Assertions.assertThat(actualList).extracting("ID").containsExactlyElementsOf(expectedList.toAbsList()); + List<Integer> normalExpectedList = expectedList.toList(); + int index = 0; + for (T element : actualList) { + additionalTest.accept(normalExpectedList.get(index), element); + index++; + } + } + + protected static class ReceiverData { + int numberOfElements = 0; + } + +} diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleListVariant/SingleListVariantIncrementalVariantTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleListVariant/SingleListVariantIncrementalVariantTest.java new file mode 100644 index 0000000000000000000000000000000000000000..174bea2923e2977c93a058dc1408113276e47382 --- /dev/null +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleListVariant/SingleListVariantIncrementalVariantTest.java @@ -0,0 +1,62 @@ +package org.jastadd.ragconnect.tests.singleListVariant; + +import org.jastadd.ragconnect.tests.TestUtils; +import org.junit.jupiter.api.Tag; +import singleListVariantInc.ast.*; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Test case "singleList variant incremental". + * + * @author rschoene - Initial contribution + */ +@Tag("Incremental") +public class SingleListVariantIncrementalVariantTest extends AbstractSingleListVariantTest { + + private Root model; + private MqttHandler handler; + + SingleListVariantIncrementalVariantTest() { + super("singleListVariantInc"); + } + + @Override + protected void createModel() { + model = new Root(); + senderRoot = new SenderRoot(); + model.addSenderRoot((SenderRoot) senderRoot); + + ReceiverRoot localReceiverRoot = new ReceiverRoot(); + model.addReceiverRoot(localReceiverRoot); + receiverRoot = localReceiverRoot; + assertEquals(0, receiverRoot.getT_EmptyList().getNumChild()); + } + + @Override + protected void setupReceiverAndConnectPart() throws IOException { + model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS); + + handler = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost()); + assertTrue(handler.waitUntilReady(2, TimeUnit.SECONDS)); + + // no dependencies + + data = new ReceiverData(); + handler.newConnection(TOPIC_T_all, bytes -> data.numberOfElements += 1); + } + + @Override + protected void closeConnections() { + if (handler != null) { + handler.close(); + } + if (model != null) { + model.ragconnectCloseConnections(); + } + } +} diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleListVariant/SingleListVariantManualVariantTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleListVariant/SingleListVariantManualVariantTest.java new file mode 100644 index 0000000000000000000000000000000000000000..6ae0e75b5d2d35375baf508ce7af8044158370bc --- /dev/null +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleListVariant/SingleListVariantManualVariantTest.java @@ -0,0 +1,72 @@ +package org.jastadd.ragconnect.tests.singleListVariant; + +import org.jastadd.ragconnect.tests.TestUtils; +import singleListVariant.ast.*; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Test case "singleList variant manual". + * + * @author rschoene - Initial contribution + */ +public class SingleListVariantManualVariantTest extends AbstractSingleListVariantTest { + + private Root model; + private MqttHandler handler; + + SingleListVariantManualVariantTest() { + super("singleListVariant"); + } + + @Override + protected void createModel() { + model = new Root(); + senderRoot = new SenderRoot(); + model.addSenderRoot((SenderRoot) senderRoot); + + ReceiverRoot localReceiverRoot = new ReceiverRoot(); + model.addReceiverRoot(localReceiverRoot); + receiverRoot = localReceiverRoot; + assertEquals(0, receiverRoot.getT_EmptyList().getNumChild()); + } + + @Override + protected void setupReceiverAndConnectPart() throws IOException { + model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS); + + handler = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost()); + assertTrue(handler.waitUntilReady(2, TimeUnit.SECONDS)); + + // add dependencies: input + ((SenderRoot) senderRoot).addInputDependencyToT_Empty((SenderRoot) senderRoot); + ((SenderRoot) senderRoot).addInputDependencyToT_Token((SenderRoot) senderRoot); + ((SenderRoot) senderRoot).addInputDependencyToT_OneChild((SenderRoot) senderRoot); + ((SenderRoot) senderRoot).addInputDependencyToT_OneOpt((SenderRoot) senderRoot); + ((SenderRoot) senderRoot).addInputDependencyToT_OneList((SenderRoot) senderRoot); + ((SenderRoot) senderRoot).addInputDependencyToT_TwoChildren((SenderRoot) senderRoot); + ((SenderRoot) senderRoot).addInputDependencyToT_OneOfEach((SenderRoot) senderRoot); + ((SenderRoot) senderRoot).addInputDependencyToT_Abstract((SenderRoot) senderRoot); + // add dependencies: shouldSetOptAndList + ((SenderRoot) senderRoot).addShouldSetOptAndListDependencyToT_OneOpt((SenderRoot) senderRoot); + ((SenderRoot) senderRoot).addShouldSetOptAndListDependencyToT_OneList((SenderRoot) senderRoot); + ((SenderRoot) senderRoot).addShouldSetOptAndListDependencyToT_OneOfEach((SenderRoot) senderRoot); + + data = new ReceiverData(); + handler.newConnection(TOPIC_T_all, bytes -> data.numberOfElements += 1); + } + + @Override + protected void closeConnections() { + if (handler != null) { + handler.close(); + } + if (model != null) { + model.ragconnectCloseConnections(); + } + } +} diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AbstractTreeTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/tree/AbstractTreeTest.java similarity index 90% rename from ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AbstractTreeTest.java rename to ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/tree/AbstractTreeTest.java index 069f622c84cf95508575143b3b9d23288f42bc4a..22cc2c0c4334d86c96fdec48682b7a92842fbc9f 100644 --- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AbstractTreeTest.java +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/tree/AbstractTreeTest.java @@ -1,4 +1,8 @@ -package org.jastadd.ragconnect.tests; +package org.jastadd.ragconnect.tests.tree; + +import org.jastadd.ragconnect.tests.AbstractMqttTest; +import org.jastadd.ragconnect.tests.TestUtils; +import org.junit.jupiter.api.Tag; import java.io.IOException; import java.util.List; @@ -11,6 +15,7 @@ import static org.junit.jupiter.api.Assertions.*; * * @author rschoene - Initial contribution */ +@Tag("Tree") public abstract class AbstractTreeTest extends AbstractMqttTest { protected static final String TOPIC_ALFA = "alfa"; protected ReceiverData data; @@ -19,6 +24,7 @@ public abstract class AbstractTreeTest extends AbstractMqttTest { public interface TestWrapperReceiverRoot { TestWrapperAlfa getAlfa(); boolean connectAlfa(String mqttUri) throws IOException; + boolean disconnectAlfa(String mqttUri) throws IOException; } public interface TestWrapperAlfa { TestWrapperBravo getBravo(int i); @@ -63,7 +69,7 @@ public abstract class AbstractTreeTest extends AbstractMqttTest { } @Override - protected void communicateSendInitialValue() throws InterruptedException { + protected void communicateSendInitialValue() throws InterruptedException, IOException { checkTree(1, 0); setInput(1); @@ -74,10 +80,18 @@ public abstract class AbstractTreeTest extends AbstractMqttTest { setInput(2); checkTree(3, 2); + + disconnectReceive(); + setInput(1); + checkTree(4, 2); + + disconnectSend(); + setInput(2); + checkTree(4, 2); } @Override - protected void communicateOnlyUpdatedValue() throws InterruptedException { + protected void communicateOnlyUpdatedValue() throws InterruptedException, IOException { checkTree(0, null); setInput(1); @@ -88,8 +102,20 @@ public abstract class AbstractTreeTest extends AbstractMqttTest { setInput(2); checkTree(2, 2); + + disconnectReceive(); + setInput(1); + checkTree(3, 2); + + disconnectSend(); + setInput(2); + checkTree(3, 2); } + protected abstract void disconnectReceive() throws IOException; + + protected abstract void disconnectSend() throws IOException; + protected abstract void setInput(int input); protected void checkTree(int expectedCount, Integer expectedInput) throws InterruptedException { diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TreeIncrementalTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/tree/TreeIncrementalTest.java similarity index 85% rename from ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TreeIncrementalTest.java rename to ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/tree/TreeIncrementalTest.java index 9d455b6abe09809c0277c71dd3f8028f05d63da3..96cbadc4fbfabed7685c6a5d4c0f0ff827c4f9b1 100644 --- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TreeIncrementalTest.java +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/tree/TreeIncrementalTest.java @@ -1,5 +1,6 @@ -package org.jastadd.ragconnect.tests; +package org.jastadd.ragconnect.tests.tree; +import org.jastadd.ragconnect.tests.TestUtils; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import treeInc.ast.*; @@ -18,7 +19,6 @@ import static org.junit.jupiter.api.Assertions.*; * * @author rschoene - Initial contribution */ -@Tag("Tree") @Tag("Incremental") public class TreeIncrementalTest extends AbstractTreeTest { @@ -60,6 +60,20 @@ public class TreeIncrementalTest extends AbstractTreeTest { assertTrue(senderRoot.connectAlfa(mqttUri(TOPIC_ALFA), writeCurrentValue)); } + protected void setInput(int input) { + senderRoot.setInput(input); + } + + @Override + protected void disconnectReceive() throws IOException { + assertTrue(receiverRoot.disconnectAlfa(mqttUri(TOPIC_ALFA))); + } + + @Override + protected void disconnectSend() throws IOException { + assertTrue(senderRoot.disconnectAlfa(mqttUri(TOPIC_ALFA))); + } + @Override protected void closeConnections() { if (handler != null) { @@ -69,8 +83,4 @@ public class TreeIncrementalTest extends AbstractTreeTest { model.ragconnectCloseConnections(); } } - - protected void setInput(int input) { - senderRoot.setInput(input); - } } diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TreeManualTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/tree/TreeManualTest.java similarity index 85% rename from ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TreeManualTest.java rename to ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/tree/TreeManualTest.java index 10c73aeff57d6752c8f6b903acbde2dd9d93aa8b..e557c9442be50d04d4b9d1bb2de7d003f749d4f0 100644 --- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TreeManualTest.java +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/tree/TreeManualTest.java @@ -1,5 +1,6 @@ -package org.jastadd.ragconnect.tests; +package org.jastadd.ragconnect.tests.tree; +import org.jastadd.ragconnect.tests.TestUtils; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import tree.ast.MqttHandler; @@ -20,7 +21,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue; * * @author rschoene - Initial contribution */ -@Tag("Tree") public class TreeManualTest extends AbstractTreeTest { private Root model; @@ -62,6 +62,20 @@ public class TreeManualTest extends AbstractTreeTest { assertTrue(senderRoot.connectAlfa(mqttUri(TOPIC_ALFA), writeCurrentValue)); } + protected void setInput(int input) { + senderRoot.setInput(input); + } + + @Override + protected void disconnectReceive() throws IOException { + assertTrue(receiverRoot.disconnectAlfa(mqttUri(TOPIC_ALFA))); + } + + @Override + protected void disconnectSend() throws IOException { + assertTrue(senderRoot.disconnectAlfa(mqttUri(TOPIC_ALFA))); + } + @Override protected void closeConnections() { if (handler != null) { @@ -71,8 +85,4 @@ public class TreeManualTest extends AbstractTreeTest { model.ragconnectCloseConnections(); } } - - protected void setInput(int input) { - senderRoot.setInput(input); - } } diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AbstractTreeAllowedTokensTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/treeAllowedTokens/AbstractTreeAllowedTokensTest.java similarity index 85% rename from ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AbstractTreeAllowedTokensTest.java rename to ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/treeAllowedTokens/AbstractTreeAllowedTokensTest.java index e87a298fe92e0019fa7da2012d6630590d7a348e..7c29538a71609e73a82fe0e22ad6c8bd74582f8c 100644 --- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AbstractTreeAllowedTokensTest.java +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/treeAllowedTokens/AbstractTreeAllowedTokensTest.java @@ -1,4 +1,8 @@ -package org.jastadd.ragconnect.tests; +package org.jastadd.ragconnect.tests.treeAllowedTokens; + +import org.jastadd.ragconnect.tests.AbstractMqttTest; +import org.jastadd.ragconnect.tests.TestUtils; +import org.junit.jupiter.api.Tag; import java.io.IOException; import java.time.Instant; @@ -11,6 +15,7 @@ import static org.junit.jupiter.api.Assertions.*; * * @author rschoene - Initial contribution */ +@Tag("Tree") public abstract class AbstractTreeAllowedTokensTest extends AbstractMqttTest { protected static final String TOPIC_INPUT1TRUE = "input1/true"; @@ -31,6 +36,8 @@ public abstract class AbstractTreeAllowedTokensTest extends AbstractMqttTest { TestWrapperAlfa getAlfaPrimitive(); boolean connectAlfa(String mqttUri) throws IOException; boolean connectAlfaPrimitive(String mqttUri) throws IOException; + boolean disconnectAlfa(String mqttUri) throws IOException; + boolean disconnectAlfaPrimitive(String mqttUri) throws IOException; } public interface TestWrapperAlfa { boolean getBooleanValue(); @@ -46,7 +53,7 @@ public abstract class AbstractTreeAllowedTokensTest extends AbstractMqttTest { } @Override - protected void communicateSendInitialValue() throws InterruptedException { + protected void communicateSendInitialValue() throws InterruptedException, IOException { checkTree(1, false, 0, INSTANT_A, 0); checkPrimitiveTree(1, INSTANT_A); @@ -94,10 +101,22 @@ public abstract class AbstractTreeAllowedTokensTest extends AbstractMqttTest { sendInput3(5.1); checkTree(6, true, 4, INSTANT_B, 5.1); checkPrimitiveTree(2, INSTANT_B); + + // sendInput3(7) -> send, but not receive + disconnectReceive(); + sendInput3(7); + checkTree(7, true, 4, INSTANT_B, 5.1); + checkPrimitiveTree(2, INSTANT_B); + + // sendInput3(8) -> not sent + disconnectSend(); + sendInput3(8); + checkTree(7, true, 4, INSTANT_B, 5.1); + checkPrimitiveTree(2, INSTANT_B); } @Override - protected void communicateOnlyUpdatedValue() throws InterruptedException { + protected void communicateOnlyUpdatedValue() throws InterruptedException, IOException { checkTree(0, false, null, null, 0); checkPrimitiveTree(0, null); @@ -145,8 +164,24 @@ public abstract class AbstractTreeAllowedTokensTest extends AbstractMqttTest { sendInput3(15.1); checkTree(5, true, 14, INSTANT_C, 15.1); checkPrimitiveTree(1, INSTANT_C); + + // sendInput3(7) -> send, but not receive + disconnectReceive(); + sendInput3(7); + checkTree(6, true, 14, INSTANT_C, 15.1); + checkPrimitiveTree(1, INSTANT_C); + + // sendInput3(8) -> not sent + disconnectSend(); + sendInput3(8); + checkTree(6, true, 14, INSTANT_C, 15.1); + checkPrimitiveTree(1, INSTANT_C); } + protected abstract void disconnectReceive() throws IOException; + + protected abstract void disconnectSend() throws IOException; + protected void sendInput1WhenFalse(int value) { publisher.publish(TOPIC_INPUT1FALSE, TestUtils.DefaultMappings.IntToBytes(value)); } diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TreeAllowedTokensIncrementalTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/treeAllowedTokens/TreeAllowedTokensIncrementalTest.java similarity index 77% rename from ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TreeAllowedTokensIncrementalTest.java rename to ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/treeAllowedTokens/TreeAllowedTokensIncrementalTest.java index 0edfbd6bee0b412158a799ef388901e62d398495..851565047c4c8a95284d9b8d58e158863596467f 100644 --- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TreeAllowedTokensIncrementalTest.java +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/treeAllowedTokens/TreeAllowedTokensIncrementalTest.java @@ -1,5 +1,6 @@ -package org.jastadd.ragconnect.tests; +package org.jastadd.ragconnect.tests.treeAllowedTokens; +import org.jastadd.ragconnect.tests.TestUtils; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import treeAllowedTokensInc.ast.*; @@ -19,7 +20,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue; * @author rschoene - Initial contribution */ @Tag("Incremental") -@Tag("Tree") public class TreeAllowedTokensIncrementalTest extends AbstractTreeAllowedTokensTest { private Root model; @@ -71,6 +71,31 @@ public class TreeAllowedTokensIncrementalTest extends AbstractTreeAllowedTokensT assertTrue(senderRoot.connectAlfaPrimitive(mqttUri(TOPIC_ALFA_PRIMITIVE), writeCurrentValue)); } + protected void setFlag(boolean value) { + senderRoot.setFlag(value); + } + + @Override + protected void checkMyEnum(TestWrapperAlfa alfa, boolean expectedBooleanValue) { + assertEquals(expectedBooleanValue ? MyEnum.TRUE : MyEnum.FALSE, ((Alfa) alfa).getEnumValue()); + } + + @Override + protected void disconnectReceive() throws IOException { + assertTrue(receiverRoot.disconnectAlfa(mqttUri(TOPIC_ALFA))); + assertTrue(receiverRoot.disconnectAlfaPrimitive(mqttUri(TOPIC_ALFA_PRIMITIVE))); + } + + @Override + protected void disconnectSend() throws IOException { + assertTrue(senderRoot.disconnectInput1WhenFlagIsFalse(mqttUri(TOPIC_INPUT1FALSE))); + assertTrue(senderRoot.disconnectInput1WhenFlagIsTrue(mqttUri(TOPIC_INPUT1TRUE))); + assertTrue(senderRoot.disconnectInput2(mqttUri(TOPIC_INPUT2))); + assertTrue(senderRoot.disconnectInput3(mqttUri(TOPIC_INPUT3))); + assertTrue(senderRoot.disconnectAlfa(mqttUri(TOPIC_ALFA))); + assertTrue(senderRoot.disconnectAlfaPrimitive(mqttUri(TOPIC_ALFA_PRIMITIVE))); + } + @Override protected void closeConnections() { if (handler != null) { @@ -80,13 +105,4 @@ public class TreeAllowedTokensIncrementalTest extends AbstractTreeAllowedTokensT model.ragconnectCloseConnections(); } } - - protected void setFlag(boolean value) { - senderRoot.setFlag(value); - } - - @Override - protected void checkMyEnum(TestWrapperAlfa alfa, boolean expectedBooleanValue) { - assertEquals(expectedBooleanValue ? MyEnum.TRUE : MyEnum.FALSE, ((Alfa) alfa).getEnumValue()); - } } diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TreeAllowedTokensManualTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/treeAllowedTokens/TreeAllowedTokensManualTest.java similarity index 78% rename from ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TreeAllowedTokensManualTest.java rename to ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/treeAllowedTokens/TreeAllowedTokensManualTest.java index 9780d56daaa807a13082e9776fa45b1ec4eee5a3..8563e0b04808f939cf1070f08c5666f9de2fef00 100644 --- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TreeAllowedTokensManualTest.java +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/treeAllowedTokens/TreeAllowedTokensManualTest.java @@ -1,13 +1,12 @@ -package org.jastadd.ragconnect.tests; +package org.jastadd.ragconnect.tests.treeAllowedTokens; +import org.jastadd.ragconnect.tests.TestUtils; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import treeAllowedTokens.ast.*; import java.io.IOException; import java.nio.file.Paths; -import java.time.Instant; -import java.time.Period; import java.util.concurrent.TimeUnit; import static org.jastadd.ragconnect.tests.TestUtils.mqttUri; @@ -19,7 +18,6 @@ import static org.junit.jupiter.api.Assertions.*; * * @author rschoene - Initial contribution */ -@Tag("Tree") public class TreeAllowedTokensManualTest extends AbstractTreeAllowedTokensTest { private Root model; @@ -75,6 +73,31 @@ public class TreeAllowedTokensManualTest extends AbstractTreeAllowedTokensTest { assertTrue(senderRoot.connectAlfaPrimitive(mqttUri(TOPIC_ALFA_PRIMITIVE), writeCurrentValue)); } + protected void setFlag(boolean value) { + senderRoot.setFlag(value); + } + + @Override + protected void checkMyEnum(TestWrapperAlfa alfa, boolean expectedBooleanValue) { + assertEquals(expectedBooleanValue ? MyEnum.TRUE : MyEnum.FALSE, ((Alfa) alfa).getEnumValue()); + } + + @Override + protected void disconnectReceive() throws IOException { + assertTrue(receiverRoot.disconnectAlfa(mqttUri(TOPIC_ALFA))); + assertTrue(receiverRoot.disconnectAlfaPrimitive(mqttUri(TOPIC_ALFA_PRIMITIVE))); + } + + @Override + protected void disconnectSend() throws IOException { + assertTrue(senderRoot.disconnectInput1WhenFlagIsFalse(mqttUri(TOPIC_INPUT1FALSE))); + assertTrue(senderRoot.disconnectInput1WhenFlagIsTrue(mqttUri(TOPIC_INPUT1TRUE))); + assertTrue(senderRoot.disconnectInput2(mqttUri(TOPIC_INPUT2))); + assertTrue(senderRoot.disconnectInput3(mqttUri(TOPIC_INPUT3))); + assertTrue(senderRoot.disconnectAlfa(mqttUri(TOPIC_ALFA))); + assertTrue(senderRoot.disconnectAlfaPrimitive(mqttUri(TOPIC_ALFA_PRIMITIVE))); + } + @Override protected void closeConnections() { if (handler != null) { @@ -84,13 +107,4 @@ public class TreeAllowedTokensManualTest extends AbstractTreeAllowedTokensTest { model.ragconnectCloseConnections(); } } - - protected void setFlag(boolean value) { - senderRoot.setFlag(value); - } - - @Override - protected void checkMyEnum(TestWrapperAlfa alfa, boolean expectedBooleanValue) { - assertEquals(expectedBooleanValue ? MyEnum.TRUE : MyEnum.FALSE, ((Alfa) alfa).getEnumValue()); - } }