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