diff --git a/pages/docs/changelog.md b/pages/docs/changelog.md index eb0fe4d555048fb90f71cec631baafeaef35a4d7..b2607fdf120d0b44ee8dd4a560cc4ad4047f0524 100644 --- a/pages/docs/changelog.md +++ b/pages/docs/changelog.md @@ -15,3 +15,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/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/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..7e4bde916d565d16bdef34598d638d2080f2f5fa 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,12 +53,14 @@ 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() { @@ -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() + ")"; @@ -80,12 +116,16 @@ aspect AttributesForMustache { } // --- 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 +136,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 +147,37 @@ 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"; + 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.resolveInListAttributeName() = "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"; + 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..a8f065e818c32874703d7f2775c589492fb6e8ee 100644 --- a/ragconnect.base/src/main/resources/MqttHandler.jadd +++ b/ragconnect.base/src/main/resources/MqttHandler.jadd @@ -1,11 +1,10 @@ -import java.io.IOException; -import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; - -aspect MqttHandler { +import java.util.function.BiConsumer;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<ConnectToken, 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 +15,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 +23,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 +39,37 @@ public class MqttServerHandler { return handler; } - public ConnectToken newConnection(java.net.URI uri, java.util.function.Consumer<byte[]> callback) throws IOException { + public ConnectToken newConnection(java.net.URI uri, java.util.function.BiConsumer<String, byte[]> callback) throws java.io.IOException { ConnectToken connectToken = new ConnectToken(uri); resolveHandler(uri).newConnection(extractTopic(uri), callback); tokensForRemoval.put(connectToken, callback); return connectToken; } - public boolean disconnect(ConnectToken connectToken) throws IOException { + public boolean disconnect(ConnectToken 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); } @@ -100,7 +103,8 @@ public class MqttHandler { 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.Map<java.util.regex.Pattern, java.util.List<java.util.function.BiConsumer<String, byte[]>>> wildcardCallbacks; public MqttHandler() { this("RagConnect"); @@ -109,7 +113,8 @@ 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.HashMap<>(); this.readyLatch = new java.util.concurrent.CountDownLatch(1); this.qos = org.fusesource.mqtt.client.QoS.AT_LEAST_ONCE; } @@ -122,21 +127,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,14 +172,14 @@ 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); + callback.accept(topicString, message); } catch (Exception e) { logger.catching(e); } @@ -199,20 +204,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 +238,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<>(); + List<BiConsumer<String, byte[]>> normalCallbackList = normalCallbacks.get(topicString); + if (normalCallbackList != null) { + result.addAll(normalCallbackList); + } + wildcardCallbacks.forEach((topicPattern, callback) -> { + if (topicPattern.matcher(topicString).matches()) { + result.addAll(callback); + } + }); + return result; + } + public java.net.URI getHost() { return host; } @@ -251,61 +270,147 @@ 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 regexForTopic = topic.replace("*", "[^/]*").replace("#", ".*"); + java.util.regex.Pattern pattern = java.util.regex.Pattern.compile(regexForTopic); + wildcardCallbacks.computeIfAbsent(pattern, p -> new java.util.ArrayList<>()) + .add(callback); + needSubscribe = true; + } else { // normal topic + java.util.List<java.util.function.BiConsumer<String, byte[]>> callbacksForTopic = normalCallbacks.get(topic); + if (callbacksForTopic == null || callbacksForTopic.isEmpty()) { + callbacksForTopic = new java.util.ArrayList<>(); + normalCallbacks.put(topic, callbacksForTopic); + needSubscribe = true; + } else { + needSubscribe = false; + } + callbacksForTopic.add(callback); + } + if (needSubscribe) { // subscribe at broker + CountDownLatch operationFinished = new 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, 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("#"); + } + + 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); + + boolean foundTopicInCallbacks = false; + + // check if wildcard is to be removed + if (isWildcardTopic(topic)) { + java.util.regex.Pattern wildcardPatternToRemove = null; + for (java.util.Map.Entry<java.util.regex.Pattern, java.util.List<java.util.function.BiConsumer<String, byte[]>>> entry : wildcardCallbacks.entrySet()) { + if (entry.getKey().pattern().equals(topic)) { + foundTopicInCallbacks = true; + // if still successful, update with whether callback could be removed + success.compareAndSet(true, (entry.getValue().remove(callback))); + if (entry.getValue().isEmpty()) { + wildcardPatternToRemove = entry.getKey(); + needUnsubscribe = true; + } + break; + } + } + ; + if (wildcardPatternToRemove != null) { + wildcardCallbacks.remove(wildcardPatternToRemove); + } + } else if (normalCallbacks.containsKey(topic)) { + foundTopicInCallbacks = true; + // if still successful, update with whether callback could be removed + var normalCallbackList = normalCallbacks.get(topic); + success.compareAndSet(true, normalCallbackList.remove(callback)); + needUnsubscribe |= normalCallbackList.isEmpty(); + } + + if (!foundTopicInCallbacks) { 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[] 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 +439,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); @@ -358,7 +463,7 @@ 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>() { + 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); 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/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..7e24342af2954e314a43f78b900f8921a74678a8 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 +#Thu Jun 24 17:13:44 CEST 2021 +version=0.3.2-alpha diff --git a/ragconnect.base/src/main/resources/ragconnect.mustache b/ragconnect.base/src/main/resources/ragconnect.mustache index 50cd5cece4429e3529c64f5896548e50bbe564b0..c08529a22bfacc7ec5caa3e6e6569e51bc5182a1 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 diff --git a/ragconnect.base/src/main/resources/receiveDefinition.mustache b/ragconnect.base/src/main/resources/receiveDefinition.mustache index 10f519129c8dec8abe587a1bcb5876c4c0224745..4ae8a05c6f97e03f5522160a487f33a710186a2c 100644 --- a/ragconnect.base/src/main/resources/receiveDefinition.mustache +++ b/ragconnect.base/src/main/resources/receiveDefinition.mustache @@ -1,15 +1,104 @@ -public boolean {{parentTypeName}}.{{connectMethod}}(String {{connectParameterName}}) throws java.io.IOException { - {{>handleUri}} - java.util.function.Consumer<byte[]> consumer = message -> { +{{#typeIsList}} +{{^UseList}} +/* first try with resolve to type +syn {{typeName}} {{parentTypeName}}.{{resolveInListAttributeName}}(String topic) { + for ({{typeName}} element : get{{entityName}}()) { + if (element.get{{idTokenName}}().equals(topic)) { + return element; + } + } + return null; +} +*/ +syn int {{parentTypeName}}.{{resolveInListAttributeName}}(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 = {{resolveInListAttributeName}}(topic); + if (resolvedIndex == -1) { + add{{entityName}}({{lastResult}}); + } else { + set{{entityName}}({{lastResult}}, resolvedIndex); + } + }; + 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}} ConnectToken connectToken; switch (scheme) { {{#usesMqtt}} @@ -23,7 +112,8 @@ public boolean {{parentTypeName}}.{{connectMethod}}(String {{connectParameterNam {{#usesRest}} case "rest": connectToken = {{restHandlerAttribute}}().newPUTConnection(uri, input -> { - consumer.accept(input.getBytes()); + // TODO wildcard-topic not supported yet + consumer.accept("", input.getBytes()); }); if (connectToken == null) { return false; diff --git a/ragconnect.base/src/main/resources/sendDefinition.mustache b/ragconnect.base/src/main/resources/sendDefinition.mustache index 62cfb7d5016fe5ed6ac2abadab45e9fdc3a45228..6fd97bf2cc7154cd0d053311f95166347cbccc6a 100644 --- a/ragconnect.base/src/main/resources/sendDefinition.mustache +++ b/ragconnect.base/src/main/resources/sendDefinition.mustache @@ -16,7 +16,7 @@ public boolean {{parentTypeName}}.{{connectMethod}}(String {{connectParameterNam final String topic = {{mqttHandlerAttribute}}().extractTopic(uri); {{sender}} = () -> { {{#loggingEnabledForWrites}} - System.out.println("[Send] {{entityName}} = " + get{{entityName}}() + " -> " + {{connectParameterName}}); + System.out.println("[Send] {{entityName}} = " + {{getterMethod}}() + " -> " + {{connectParameterName}}); {{/loggingEnabledForWrites}} handler.publish(topic, {{lastValue}}); }; @@ -45,7 +45,7 @@ public boolean {{parentTypeName}}.{{connectMethod}}(String {{connectParameterNam connectTokens.computeIfAbsent(this, astNode -> new java.util.HashMap<java.net.URI, ConnectToken>()) .put(uri, connectToken); {{#incrementalOptionActive}} - _ragConnectObserver().add(connectToken, this, "get{{entityName}}", () -> { + _ragConnectObserver().add(connectToken, this, "{{getterMethod}}", () -> { if (this.{{updateMethod}}()) { this.{{writeMethod}}(); } diff --git a/ragconnect.tests/build.gradle b/ragconnect.tests/build.gradle index 485a59d61efefe9f77592c1f62ca485db087da98..61470f5292531d0ec7ca5d3691a3b248bcff1588 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' } } @@ -411,3 +411,154 @@ 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" +//} +//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" +//} +//compileSingleListVariantManual.dependsOn cleanCurrentManualTest +//compileSingleListVariantIncremental.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..330c950b8114cb1364ea30a86cd76c7dc81eb4a2 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() @@ -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/TestUtils.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TestUtils.java index c09059c8a66ca1cba262843ce30d4ce8ad0bc663..ce79190fe184f5b777366c25e9eff07cc8fc3576 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 @@ -132,12 +132,12 @@ public class TestUtils { return new String(encoded, encoding); } - static void waitForMqtt() throws InterruptedException { + public static void waitForMqtt() throws InterruptedException { TimeUnit.MILLISECONDS.sleep(1500); } @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/list/AbstractListTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/list/AbstractListTest.java new file mode 100644 index 0000000000000000000000000000000000000000..18a6e59bcc07ffc5c61006c671d15b8c55a8d501 --- /dev/null +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/list/AbstractListTest.java @@ -0,0 +1,179 @@ +package org.jastadd.ragconnect.tests.list; + +import org.jastadd.ragconnect.tests.AbstractMqttTest; +import org.jastadd.ragconnect.tests.TestUtils; +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 java.util.Collections.addAll; +import static org.assertj.core.util.Lists.newArrayList; +import static org.jastadd.ragconnect.tests.list.AbstractListTest.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 { + 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)); + } + + @Override + protected void communicateOnlyUpdatedValue() throws InterruptedException { + 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)); + } + + 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; + } + + protected static class IntList { + private final List<Integer> integers = newArrayList(); + public IntList(Integer... values) { + addAll(integers, values); + } + + public List<Integer> toList() { + return integers; + } + + public static IntList list(Integer... values) { + return new IntList(values); + } + } + +} 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..e5fbae01e28d2c31eed1511c7ff9a0712cd5e3c4 --- /dev/null +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/list/ListIncrementalTest.java @@ -0,0 +1,85 @@ +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 closeConnections() { + if (handler != null) { + handler.close(); + } + if (model != null) { + model.ragconnectCloseConnections(); + } + } + + @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"); + } +} 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..f3e6eee5c93ea62e6edf939a1e6f5f3ef5419463 --- /dev/null +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/list/ListManualTest.java @@ -0,0 +1,84 @@ +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 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..3c2779f8d4188c0f1fff7f8773d27401e94dd597 --- /dev/null +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleList/AbstractSingleListTest.java @@ -0,0 +1,297 @@ +package org.jastadd.ragconnect.tests.singleList; + +import org.jastadd.ragconnect.tests.AbstractMqttTest; +import org.jastadd.ragconnect.tests.TestUtils; +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 java.util.Collections.addAll; +import static org.assertj.core.util.Lists.newArrayList; +import static org.jastadd.ragconnect.tests.TestUtils.mqttUri; +import static org.jastadd.ragconnect.tests.TestUtils.testJaddContainReferenceToJackson; +import static org.jastadd.ragconnect.tests.singleList.AbstractSingleListTest.IntList.list; +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; + } + @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; + + 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 { + 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) + } + + @Override + protected void communicateOnlyUpdatedValue() throws InterruptedException { + 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 + } + + 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; + } + + protected static class IntList { + private final List<Integer> integers = newArrayList(); + public IntList(Integer... values) { + addAll(integers, values); + } + + public List<Integer> toList() { + return integers; + } + + public static IntList list(Integer... values) { + return new IntList(values); + } + } + +} 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..7a946e44499f71dc0dfb92271ca1fc414275597b --- /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 "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..c2896f633bc299159f0ab74913c3a9c0456d8370 --- /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 "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..465ad8ac024d595f29a87757c776db6c88e72fe4 --- /dev/null +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/singleListVariant/AbstractSingleListVariantTest.java @@ -0,0 +1,406 @@ +package org.jastadd.ragconnect.tests.singleListVariant; + +import org.jastadd.ragconnect.tests.AbstractMqttTest; +import org.jastadd.ragconnect.tests.TestUtils; +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 java.util.Collections.addAll; +import static org.assertj.core.util.Lists.newArrayList; +import static org.jastadd.ragconnect.tests.TestUtils.mqttUri; +import static org.jastadd.ragconnect.tests.TestUtils.testJaddContainReferenceToJackson; +import static org.jastadd.ragconnect.tests.singleListVariant.AbstractSingleListVariantTest.IntList.list; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Base class for test cases "singleList manual" and "singleList 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 mqttUri) throws IOException; + boolean connectT_Token(String mqttUri) throws IOException; + boolean connectT_OneChild(String mqttUri) throws IOException; + boolean connectT_OneOpt(String mqttUri) throws IOException; + boolean connectT_OneList(String mqttUri) throws IOException; + boolean connectT_TwoChildren(String mqttUri) throws IOException; + boolean connectT_OneOfEach(String mqttUri) throws IOException; + boolean connectT_Abstract(String mqttUri) throws IOException; + + boolean connectMyEmpty(String mqttUri) throws IOException; + + boolean connectEmptyWithAdd(String mqttUri) throws IOException; + boolean connectTokenWithAdd(String mqttUri) throws IOException; + boolean connectOneChildWithAdd(String mqttUri) throws IOException; + boolean connectOneOptWithAdd(String mqttUri) throws IOException; + boolean connectOneListWithAdd(String mqttUri) throws IOException; + boolean connectTwoChildrenWithAdd(String mqttUri) throws IOException; + boolean connectOneOfEachWithAdd(String mqttUri) throws IOException; + boolean connectAbstractWithAdd(String mqttUri) throws IOException; + } + @SuppressWarnings("UnusedReturnValue") + public interface TestWrapperSenderRoot { + boolean connectT_Empty(String mqttUri, boolean writeCurrentValue) throws IOException; + boolean connectT_Token(String mqttUri, boolean writeCurrentValue) throws IOException; + boolean connectT_OneChild(String mqttUri, boolean writeCurrentValue) throws IOException; + boolean connectT_OneOpt(String mqttUri, boolean writeCurrentValue) throws IOException; + boolean connectT_OneList(String mqttUri, boolean writeCurrentValue) throws IOException; + boolean connectT_TwoChildren(String mqttUri, boolean writeCurrentValue) throws IOException; + boolean connectT_OneOfEach(String mqttUri, boolean writeCurrentValue) throws IOException; + boolean connectT_Abstract(String mqttUri, boolean writeCurrentValue) throws IOException; + + TestWrapperSenderRoot setInput(int input); + 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 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)); + } + + @Override + protected void communicateOnlyUpdatedValue() throws 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)); + } + + 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.toList(), receiverRoot.getT_EmptyList(), (e, n) -> {}); + checkList(expectedList.toList(), receiverRoot.getT_TokenList(), + (e, n) -> assertEquals(Integer.toString(abs(e)), n.getValue())); + checkList(expectedList.toList(), receiverRoot.getT_OneChildList(), + (e, n) -> assertEquals(abs(e) + 1, n.getOther().getID())); + checkList(expectedList.toList(), receiverRoot.getT_OneOptList(), + (e, n) -> { + assertEquals(e > 0, n.hasOther()); + if (n.hasOther()) { + assertEquals(abs(e) + 1, n.getOther().getID()); + } + }); + checkList(expectedList.toList(), 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.toList(), receiverRoot.getT_TwoChildrenList(), + (e, n) -> { + assertEquals(abs(e) + 1, n.getLeft().getID()); + assertEquals(abs(e) + 1, n.getRight().getID()); + }); + checkList(expectedList.toList(), 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.toList(), receiverRoot.getT_AbstractList(), + (e, n) -> { + assertEquals(Integer.toString(abs(e)), n.getValueAbstract()); + assertEquals(Integer.toString(abs(e)), n.getValueSub()); + }); + + // check named + checkList(expectedList.toList(), receiverRoot.getMyEmptyList(), (e, n) -> {}); + + // check with add + checkList(expectedWithAddList.toList(), receiverRoot.getEmptyWithAddList(), (e, n) -> {}); + checkList(expectedWithAddList.toList(), receiverRoot.getTokenWithAddList(), + (e, n) -> assertEquals(Integer.toString(abs(e)), n.getValue())); + checkList(expectedWithAddList.toList(), receiverRoot.getOneChildWithAddList(), + (e, n) -> assertEquals(abs(e) + 1, n.getOther().getID())); + checkList(expectedWithAddListForOptAndList.toList(), receiverRoot.getOneOptWithAddList(), + (e, n) -> { + if (n.hasOther()) { + assertEquals(abs(e) + 1, n.getOther().getID()); + } + }); + checkList(expectedWithAddListForOptAndList.toList(), receiverRoot.getOneListWithAddList(), + (e, n) -> { + if (n.getNumOther() > 0) { + assertEquals(abs(e) + 1, n.getOther(0).getID()); + } + }); + checkList(expectedWithAddList.toList(), receiverRoot.getTwoChildrenWithAddList(), + (e, n) -> { + assertEquals(abs(e) + 1, n.getLeft().getID()); + assertEquals(abs(e) + 1, n.getRight().getID()); + }); + checkList(expectedWithAddListForOptAndList.toList(), 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.toList(), receiverRoot.getAbstractWithAddList(), + (e, n) -> { + assertEquals(Integer.toString(abs(e)), n.getValueAbstract()); + assertEquals(Integer.toString(abs(e)), n.getValueSub()); + }); + } + + private <T extends TestWrapperNameable> void checkList(List<Integer> expectedList, TestWrapperJastAddList<T> actualList, BiConsumer<Integer, T> additionalTest) { + assertEquals(expectedList.size(), actualList.getNumChild(), "same list size"); + int index = 0; + for (T element : actualList) { + assertEquals(abs(expectedList.get(index)), element.getID(), "correct ID for A"); + additionalTest.accept(expectedList.get(index), element); + index++; + } + } + + protected static class ReceiverData { + int numberOfElements = 0; + } + + protected static class IntList { + private final List<Integer> integers = newArrayList(); + public IntList(Integer... values) { + addAll(integers, values); + } + + public List<Integer> toList() { + return integers; + } + + public static IntList list(Integer... values) { + return new IntList(values); + } + } + +} 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..9a55da378b93cc3bcc741d1904dc58b3d9ae96bd --- /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 "list 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..c65a09ec65add4afd7fa5ca8a2809b28e0be4df2 --- /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 "list 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 97% 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..ebcccfe9898e374a5dc8d5886d83f999fc0def36 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; 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 95% 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..6722e159ed064ee253dd79c01fbf3e009ebba874 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 { 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 95% 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..d60e8fa0590bc60306997a8896856da99b0a7956 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; 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 97% 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..12b2c99912ac7f2b4401aeefd76a3f91fce06f7b 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"; 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 96% 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..e848ca4059b962aa3431209e23de5ed9ccad4a24 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; 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 96% 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..3903834434d1b5031e1db5a68e6a6d5dbdef9a43 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;