Skip to content
Snippets Groups Projects
Commit ef3679c0 authored by René Schöne's avatar René Schöne
Browse files

Resolve "Feature: Send endpoint for relations"

- added support for sending relation targets
- internally, relations use forwarding
- templates: add null check before mapping application only if endpoint is not a primitive type
- generation: add default send mapping for java.util.List
- Comiler: always print stacktrace upon compiler error
- build: ragconnect.tests:classes depends on ragconnect.base:jar
- test: checking in RelationTest, should be used in all tests, see #32
parent e616deb6
Branches
No related tags found
3 merge requests!39Version 1.1.0,!35Version 1.0.0,!27Resolve "Feature: Send endpoint for relations"
Showing
with 1166 additions and 27 deletions
...@@ -15,6 +15,19 @@ aspect Analysis { ...@@ -15,6 +15,19 @@ aspect Analysis {
} }
return numberOfSameDefs > 1; return numberOfSameDefs > 1;
} }
eq RelationEndpointTarget.isAlreadyDefined() {
// define lookup here, as not used elsewhere
int numberOfSameDefs = 0;
for (EndpointTarget target : ragconnect().givenEndpointTargetList()) {
if (target.isRelationEndpointTarget()) {
RelationEndpointTarget other = target.asRelationEndpointTarget();
if (other.getRole().equals(this.getRole())) {
numberOfSameDefs += 1;
}
}
}
return numberOfSameDefs > 1;
}
eq TokenEndpointTarget.isAlreadyDefined() { eq TokenEndpointTarget.isAlreadyDefined() {
return lookupTokenEndpointDefinitions(getToken()).stream() return lookupTokenEndpointDefinitions(getToken()).stream()
.filter(containingEndpointDefinition()::matchesType) .filter(containingEndpointDefinition()::matchesType)
...@@ -49,7 +62,8 @@ aspect Analysis { ...@@ -49,7 +62,8 @@ aspect Analysis {
return target.primitivePrettyPrint().equals(this.primitivePrettyPrint()); return target.primitivePrettyPrint().equals(this.primitivePrettyPrint());
} }
syn String JavaTypeUse.primitivePrettyPrint() { syn String JavaTypeUse.primitivePrettyPrint() {
switch (getName()) { String name = getName();
switch (name) {
case "boolean": case "boolean":
case "Boolean": case "Boolean":
return "boolean"; return "boolean";
...@@ -78,6 +92,7 @@ aspect Analysis { ...@@ -78,6 +92,7 @@ aspect Analysis {
syn boolean EndpointTarget.hasAttributeResetMethod(); syn boolean EndpointTarget.hasAttributeResetMethod();
eq AttributeEndpointTarget.hasAttributeResetMethod() = false; eq AttributeEndpointTarget.hasAttributeResetMethod() = false;
eq RelationEndpointTarget.hasAttributeResetMethod() = false;
eq TokenEndpointTarget.hasAttributeResetMethod() = getToken().getNTA(); eq TokenEndpointTarget.hasAttributeResetMethod() = getToken().getNTA();
eq TypeEndpointTarget.hasAttributeResetMethod() = getType().getNTA(); eq TypeEndpointTarget.hasAttributeResetMethod() = getType().getNTA();
eq ContextFreeTypeEndpointTarget.hasAttributeResetMethod() = false; eq ContextFreeTypeEndpointTarget.hasAttributeResetMethod() = false;
......
...@@ -59,6 +59,7 @@ aspect SharedMustache { ...@@ -59,6 +59,7 @@ aspect SharedMustache {
case "warn": case "warn":
case "error": case "error":
case "exception": case "exception":
//noinspection DuplicateBranchesInSwitch
return "ASTNode." + logConsoleErr(); return "ASTNode." + logConsoleErr();
default: default:
return "unknownLoggingLevelForConsole_" + level + "_"; return "unknownLoggingLevelForConsole_" + level + "_";
...@@ -74,9 +75,8 @@ aspect SharedMustache { ...@@ -74,9 +75,8 @@ aspect SharedMustache {
} }
} }
syn boolean EndpointTarget.typeIsList() = false; syn boolean EndpointTarget.typeIsList() = false;
eq TypeEndpointTarget.typeIsList() { eq TypeEndpointTarget.typeIsList() = getType().isListComponent();
return getType().isListComponent(); eq RelationEndpointTarget.typeIsList() = getRole().isListRole();
}
syn boolean EndpointTarget.typeIsOpt() = false; syn boolean EndpointTarget.typeIsOpt() = false;
eq TypeEndpointTarget.typeIsOpt() { eq TypeEndpointTarget.typeIsOpt() {
...@@ -215,6 +215,10 @@ aspect MustacheMappingApplicationAndDefinition { ...@@ -215,6 +215,10 @@ aspect MustacheMappingApplicationAndDefinition {
eq MAttributeSendDefinition.preemptiveExpectedValue() = lastValueGetterCall(); eq MAttributeSendDefinition.preemptiveExpectedValue() = lastValueGetterCall();
eq MAttributeSendDefinition.preemptiveReturn() = "return false;"; eq MAttributeSendDefinition.preemptiveReturn() = "return false;";
eq MRelationSendDefinition.firstInputVarName() = getterMethodCall();
eq MRelationSendDefinition.preemptiveExpectedValue() = lastValueGetterCall();
eq MRelationSendDefinition.preemptiveReturn() = "return false;";
eq MTokenReceiveDefinition.firstInputVarName() = "message"; eq MTokenReceiveDefinition.firstInputVarName() = "message";
eq MTokenReceiveDefinition.preemptiveExpectedValue() = getterMethodCall(); eq MTokenReceiveDefinition.preemptiveExpectedValue() = getterMethodCall();
eq MTokenReceiveDefinition.preemptiveReturn() = "return;"; eq MTokenReceiveDefinition.preemptiveReturn() = "return;";
...@@ -425,6 +429,15 @@ aspect MustacheReceiveAndSendAndHandleUri { ...@@ -425,6 +429,15 @@ aspect MustacheReceiveAndSendAndHandleUri {
eq AttributeEndpointTarget.parentTypeName() = getParentTypeDecl().getName(); eq AttributeEndpointTarget.parentTypeName() = getParentTypeDecl().getName();
eq AttributeEndpointTarget.entityName() = getName(); eq AttributeEndpointTarget.entityName() = getName();
eq RelationEndpointTarget.getterMethodName() = forwardingNTA_Name();
eq RelationEndpointTarget.parentTypeName() = getRole().getType().getName();
eq RelationEndpointTarget.entityName() = getRole().getName();
eq RelationEndpointTarget.realGetterMethodName() = "get" + getRole().getterMethodName();
eq RelationEndpointTarget.realGetterMethodCall() = realGetterMethodName() + (containingEndpointDefinition().indexedSend() ? "(index)" : "()");
syn String NavigableRole.getterMethodName() = getName();
eq ListRole.getterMethodName() = getName() + "List";
eq TokenEndpointTarget.getterMethodName() = "get" + getToken().getName(); eq TokenEndpointTarget.getterMethodName() = "get" + getToken().getName();
eq TokenEndpointTarget.parentTypeName() = getToken().containingTypeDecl().getName(); eq TokenEndpointTarget.parentTypeName() = getToken().containingTypeDecl().getName();
eq TokenEndpointTarget.entityName() = getToken().getName(); eq TokenEndpointTarget.entityName() = getToken().getName();
...@@ -481,6 +494,8 @@ aspect MustacheSendDefinition { ...@@ -481,6 +494,8 @@ aspect MustacheSendDefinition {
syn String EndpointDefinition.forwardingNTA_Name() = getEndpointTarget().forwardingNTA_Name(); syn String EndpointDefinition.forwardingNTA_Name() = getEndpointTarget().forwardingNTA_Name();
syn String EndpointDefinition.forwardingNTA_Type() = getEndpointTarget().forwardingNTA_Type(); syn String EndpointDefinition.forwardingNTA_Type() = getEndpointTarget().forwardingNTA_Type();
syn boolean EndpointDefinition.relationEndpointWithListRole() = getEndpointTarget().relationEndpointWithListRole();
syn String EndpointDefinition.senderName() = getEndpointTarget().senderName(); syn String EndpointDefinition.senderName() = getEndpointTarget().senderName();
syn boolean EndpointDefinition.shouldNotResetValue() = getSend() && !getEndpointTarget().hasAttributeResetMethod(); syn boolean EndpointDefinition.shouldNotResetValue() = getSend() && !getEndpointTarget().hasAttributeResetMethod();
...@@ -494,13 +509,17 @@ aspect MustacheSendDefinition { ...@@ -494,13 +509,17 @@ aspect MustacheSendDefinition {
// === attributes needed for computing above ones === // === attributes needed for computing above ones ===
syn boolean EndpointTarget.needForwardingNTA() = false; syn boolean EndpointTarget.needForwardingNTA() = false;
eq TypeEndpointTarget.needForwardingNTA() = containingEndpointDefinition().getSend() && !getType().getNTA(); eq TypeEndpointTarget.needForwardingNTA() = containingEndpointDefinition().getSend() && !getType().getNTA();
eq RelationEndpointTarget.needForwardingNTA() = containingEndpointDefinition().getSend();
syn String EndpointTarget.forwardingNTA_Name() = null; syn String EndpointTarget.forwardingNTA_Name() = null; // only needed, if needForwardingNTA evaluates to true
eq TypeEndpointTarget.forwardingNTA_Name() = ragconnect().internalRagConnectPrefix() + getType().getName(); eq TypeEndpointTarget.forwardingNTA_Name() = ragconnect().internalRagConnectPrefix() + getType().getName();
eq RelationEndpointTarget.forwardingNTA_Name() = ragconnect().internalRagConnectPrefix() + getRole().getName();
syn String EndpointTarget.forwardingNTA_Type() = null; syn String EndpointTarget.forwardingNTA_Type() = null; // only needed, if needForwardingNTA evaluates to true
eq TypeEndpointTarget.forwardingNTA_Type() = getType().forwardingNTA_Type( eq TypeEndpointTarget.forwardingNTA_Type() = getType().forwardingNTA_Type(
containingEndpointDefinition().getIndexBasedListAccess()); containingEndpointDefinition().getIndexBasedListAccess());
eq RelationEndpointTarget.forwardingNTA_Type() = getRole().forwardingNTA_Type(
containingEndpointDefinition().getIndexBasedListAccess());
syn String TypeComponent.forwardingNTA_Type(boolean indexBasedListAccess); syn String TypeComponent.forwardingNTA_Type(boolean indexBasedListAccess);
eq NormalComponent.forwardingNTA_Type(boolean indexBasedListAccess) = getTypeDecl().getName(); eq NormalComponent.forwardingNTA_Type(boolean indexBasedListAccess) = getTypeDecl().getName();
...@@ -510,6 +529,14 @@ aspect MustacheSendDefinition { ...@@ -510,6 +529,14 @@ aspect MustacheSendDefinition {
getTypeDecl().getName() : getTypeDecl().getName() :
ragconnect().configJastAddList() + "<" + getTypeDecl().getName() + ">"; ragconnect().configJastAddList() + "<" + getTypeDecl().getName() + ">";
syn String Role.forwardingNTA_Type(boolean indexBasedListAccess) = oppositeRole().getType().getName();
eq ListRole.forwardingNTA_Type(boolean indexBasedListAccess) = indexBasedListAccess ?
oppositeRole().getType().getName() :
"java.util.List<" + oppositeRole().getType().getName() + ">";
syn boolean EndpointTarget.relationEndpointWithListRole() = false;
eq RelationEndpointTarget.relationEndpointWithListRole() = getRole().isListRole();
syn String EndpointTarget.senderName() = ragconnect().internalRagConnectPrefix() + "_sender_" + entityName(); syn String EndpointTarget.senderName() = ragconnect().internalRagConnectPrefix() + "_sender_" + entityName();
eq ContextFreeTypeEndpointTarget.senderName() = null; eq ContextFreeTypeEndpointTarget.senderName() = null;
...@@ -519,6 +546,9 @@ aspect MustacheSendDefinition { ...@@ -519,6 +546,9 @@ aspect MustacheSendDefinition {
eq MAttributeSendDefinition.updateMethodName() = ragconnect().internalRagConnectPrefix() + "_update_attr_" + getEndpointDefinition().entityName(); eq MAttributeSendDefinition.updateMethodName() = ragconnect().internalRagConnectPrefix() + "_update_attr_" + getEndpointDefinition().entityName();
eq MAttributeSendDefinition.writeMethodName() = ragconnect().internalRagConnectPrefix() + "_writeLastValue_attr_" + getEndpointDefinition().entityName(); eq MAttributeSendDefinition.writeMethodName() = ragconnect().internalRagConnectPrefix() + "_writeLastValue_attr_" + getEndpointDefinition().entityName();
eq MRelationSendDefinition.updateMethodName() = ragconnect().internalRagConnectPrefix() + "_update_" + getEndpointDefinition().entityName();
eq MRelationSendDefinition.writeMethodName() = ragconnect().internalRagConnectPrefix() + "_writeLastValue_" + getEndpointDefinition().entityName();
eq MTokenReceiveDefinition.updateMethodName() = null; eq MTokenReceiveDefinition.updateMethodName() = null;
eq MTokenReceiveDefinition.writeMethodName() = null; eq MTokenReceiveDefinition.writeMethodName() = null;
...@@ -641,6 +671,12 @@ aspect AttributesForMustache { ...@@ -641,6 +671,12 @@ aspect AttributesForMustache {
} }
return new MAttributeSendDefinition(); return new MAttributeSendDefinition();
} }
MEndpointDefinition RelationEndpointTarget.createMEndpointDefinition(boolean isSend) {
if (!isSend) {
throw new IllegalArgumentException("RelationEndpointTarget can only be sent!");
}
return new MRelationSendDefinition();
}
MEndpointDefinition TokenEndpointTarget.createMEndpointDefinition(boolean isSend) { MEndpointDefinition TokenEndpointTarget.createMEndpointDefinition(boolean isSend) {
return isSend ? new MTokenSendDefinition() : new MTokenReceiveDefinition(); return isSend ? new MTokenSendDefinition() : new MTokenReceiveDefinition();
} }
......
...@@ -2,6 +2,7 @@ abstract MEndpointDefinition ::= InnerMappingDefinition:MInnerMappingDefinition* ...@@ -2,6 +2,7 @@ abstract MEndpointDefinition ::= InnerMappingDefinition:MInnerMappingDefinition*
rel MEndpointDefinition.EndpointDefinition -> EndpointDefinition; rel MEndpointDefinition.EndpointDefinition -> EndpointDefinition;
MAttributeSendDefinition : MEndpointDefinition; MAttributeSendDefinition : MEndpointDefinition;
MRelationSendDefinition : MEndpointDefinition;
abstract MTokenEndpointDefinition : MEndpointDefinition; abstract MTokenEndpointDefinition : MEndpointDefinition;
MTokenReceiveDefinition : MTokenEndpointDefinition; MTokenReceiveDefinition : MTokenEndpointDefinition;
MTokenSendDefinition : MTokenEndpointDefinition; MTokenSendDefinition : MTokenEndpointDefinition;
......
aspect DefaultMappings { aspect DefaultMappings {
private String RagConnect.baseDefaultMappingTypeNamePart(String typeName) { private String RagConnect.baseDefaultMappingTypeNamePart(String typeName) {
if (typeName.contains(".")) {
StringBuilder sb = new StringBuilder();
for (String part : typeName.split("\\.")) {
sb.append(baseDefaultMappingTypeNamePart(part));
}
return sb.toString();
} else {
return capitalize(typeName).replace("[]", "s").replace("<", "").replace(">", "List"); return capitalize(typeName).replace("[]", "s").replace("<", "").replace(">", "List");
} }
}
private MappingDefinitionType RagConnect.baseDefaultMappingTypeFromName(String typeName) { private MappingDefinitionType RagConnect.baseDefaultMappingTypeName(String typeName) {
return typeName.endsWith("[]") ? return typeName.endsWith("[]") ?
new JavaArrayMappingDefinitionType(new SimpleJavaTypeUse(typeName.replace("[]", ""))) : new JavaArrayMappingDefinitionType(new SimpleJavaTypeUse(typeName.replace("[]", ""))) :
new JavaMappingDefinitionType(new SimpleJavaTypeUse(typeName)); new JavaMappingDefinitionType(new SimpleJavaTypeUse(typeName));
...@@ -13,9 +21,9 @@ aspect DefaultMappings { ...@@ -13,9 +21,9 @@ aspect DefaultMappings {
private DefaultMappingDefinition RagConnect.createDefaultMappingDefinition(String prefix, String fromTypeName, String toTypeName, String content) { private DefaultMappingDefinition RagConnect.createDefaultMappingDefinition(String prefix, String fromTypeName, String toTypeName, String content) {
DefaultMappingDefinition result = new DefaultMappingDefinition(); DefaultMappingDefinition result = new DefaultMappingDefinition();
result.setID(prefix + baseDefaultMappingTypeNamePart(fromTypeName) + "To" + baseDefaultMappingTypeNamePart(toTypeName) + "Mapping"); result.setID(prefix + baseDefaultMappingTypeNamePart(fromTypeName) + "To" + baseDefaultMappingTypeNamePart(toTypeName) + "Mapping");
result.setFromType(baseDefaultMappingTypeFromName(fromTypeName)); result.setFromType(baseDefaultMappingTypeName(fromTypeName));
result.setFromVariableName("input"); result.setFromVariableName("input");
result.setToType(baseDefaultMappingTypeFromName(toTypeName)); result.setToType(baseDefaultMappingTypeName(toTypeName));
result.setContent(content); result.setContent(content);
return result; return result;
} }
...@@ -67,7 +75,7 @@ aspect DefaultMappings { ...@@ -67,7 +75,7 @@ aspect DefaultMappings {
); );
} }
syn nta DefaultMappingDefinition RagConnect.defaultBytesToListTreeMapping(String typeName) { syn nta DefaultMappingDefinition RagConnect.defaultBytesToListMapping(String typeName) {
return treeDefaultMappingDefinition("byte[]", configJastAddList() + "<" + typeName + ">", return treeDefaultMappingDefinition("byte[]", configJastAddList() + "<" + typeName + ">",
"String content = new String(input);\n" + "String content = new String(input);\n" +
"com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();\n" + "com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();\n" +
...@@ -78,7 +86,7 @@ aspect DefaultMappings { ...@@ -78,7 +86,7 @@ aspect DefaultMappings {
"return result;" "return result;"
); );
} }
syn nta DefaultMappingDefinition RagConnect.defaultListTreeToBytesMapping() { syn nta DefaultMappingDefinition RagConnect.defaultListToBytesMapping() {
return treeDefaultMappingDefinition(configJastAddList(), "byte[]", return treeDefaultMappingDefinition(configJastAddList(), "byte[]",
"java.io.ByteArrayOutputStream outputStream = new java.io.ByteArrayOutputStream();\n" + "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.JsonFactory factory = new com.fasterxml.jackson.core.JsonFactory();\n" +
...@@ -88,6 +96,16 @@ aspect DefaultMappings { ...@@ -88,6 +96,16 @@ aspect DefaultMappings {
"return outputStream.toString().getBytes();" "return outputStream.toString().getBytes();"
); );
} }
syn nta DefaultMappingDefinition RagConnect.defaultJavaUtilListToBytesMapping() {
return treeDefaultMappingDefinition("java.util.List", "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" +
"serializeJavaUtilList(input, generator);\n" +
"generator.flush();\n" +
"return outputStream.toString().getBytes();"
);
}
syn nta DefaultMappingDefinition RagConnect.defaultBooleanToBytesMapping() = baseDefaultMappingDefinition( syn nta DefaultMappingDefinition RagConnect.defaultBooleanToBytesMapping() = baseDefaultMappingDefinition(
"boolean", "byte[]", "return java.nio.ByteBuffer.allocate(1).put((byte) (input ? 1 : 0)).array();"); "boolean", "byte[]", "return java.nio.ByteBuffer.allocate(1).put((byte) (input ? 1 : 0)).array();");
...@@ -165,6 +183,10 @@ aspect Mappings { ...@@ -165,6 +183,10 @@ aspect Mappings {
} }
// --- isPrimitiveType --- // --- isPrimitiveType ---
syn boolean EndpointDefinition.isPrimitiveType() = getEndpointTarget().isPrimitiveType();
syn boolean EndpointTarget.isPrimitiveType() = false;
eq TokenEndpointTarget.isPrimitiveType() = getToken().isPrimitiveType();
eq AttributeEndpointTarget.isPrimitiveType() = new SimpleJavaTypeUse(getTypeName()).isPrimitiveType();
syn boolean TokenComponent.isPrimitiveType() = effectiveJavaTypeUse().isPrimitiveType(); syn boolean TokenComponent.isPrimitiveType() = effectiveJavaTypeUse().isPrimitiveType();
syn boolean JavaTypeUse.isPrimitiveType() = false; syn boolean JavaTypeUse.isPrimitiveType() = false;
eq SimpleJavaTypeUse.isPrimitiveType() { eq SimpleJavaTypeUse.isPrimitiveType() {
...@@ -218,7 +240,7 @@ aspect Mappings { ...@@ -218,7 +240,7 @@ aspect Mappings {
default: default:
try { try {
TypeDecl typeDecl = program().resolveTypeDecl(targetTypeName()); TypeDecl typeDecl = program().resolveTypeDecl(targetTypeName());
return getEndpointTarget().isTypeEndpointTarget() && typeIsList() && !getIndexBasedListAccess() ? ragconnect().defaultBytesToListTreeMapping(typeDecl.getName()) : ragconnect().defaultBytesToTreeMapping(typeDecl.getName()); return getEndpointTarget().isTypeEndpointTarget() && typeIsList() && !getIndexBasedListAccess() ? ragconnect().defaultBytesToListMapping(typeDecl.getName()) : ragconnect().defaultBytesToTreeMapping(typeDecl.getName());
} catch (Exception ignore) { } catch (Exception ignore) {
} }
System.err.println("Could not find suitable default mapping for " + targetTypeName() + " on " + this); System.err.println("Could not find suitable default mapping for " + targetTypeName() + " on " + this);
...@@ -254,7 +276,10 @@ aspect Mappings { ...@@ -254,7 +276,10 @@ aspect Mappings {
return ragconnect().defaultStringToBytesMapping(); return ragconnect().defaultStringToBytesMapping();
default: default:
if (getEndpointTarget().isTypeEndpointTarget() && typeIsList() && !getIndexBasedListAccess()) { if (getEndpointTarget().isTypeEndpointTarget() && typeIsList() && !getIndexBasedListAccess()) {
return ragconnect().defaultListTreeToBytesMapping(); return ragconnect().defaultListToBytesMapping();
}
if (getEndpointTarget().isRelationEndpointTarget() && typeIsList() && !getIndexBasedListAccess()) {
return ragconnect().defaultJavaUtilListToBytesMapping();
} }
try { try {
TypeDecl typeDecl = program().resolveTypeDecl(targetTypeName()); TypeDecl typeDecl = program().resolveTypeDecl(targetTypeName());
...@@ -281,10 +306,14 @@ aspect Mappings { ...@@ -281,10 +306,14 @@ aspect Mappings {
} }
syn String EndpointTarget.targetTypeName(); syn String EndpointTarget.targetTypeName();
eq AttributeEndpointTarget.targetTypeName() = getTypeName(); eq AttributeEndpointTarget.targetTypeName() = getTypeName();
eq RelationEndpointTarget.targetTypeName() = getRole().oppositeRole().targetTypeName();
eq TokenEndpointTarget.targetTypeName() = getToken().effectiveJavaTypeUse().getName(); eq TokenEndpointTarget.targetTypeName() = getToken().effectiveJavaTypeUse().getName();
eq TypeEndpointTarget.targetTypeName() = getType().getTypeDecl().getName(); eq TypeEndpointTarget.targetTypeName() = getType().getTypeDecl().getName();
eq ContextFreeTypeEndpointTarget.targetTypeName() = getTypeDecl().getName(); eq ContextFreeTypeEndpointTarget.targetTypeName() = getTypeDecl().getName();
syn String Role.targetTypeName() = getType().getName();
eq ListRole.targetTypeName() = "java.util.List<" + getType().getName() + ">";
// eq ReceiveFromRestDefinition.suitableDefaultMapping() { // eq ReceiveFromRestDefinition.suitableDefaultMapping() {
// String typeName = getMappingList().isEmpty() ? // String typeName = getMappingList().isEmpty() ?
// getToken().getJavaTypeUse().getName() : // getToken().getJavaTypeUse().getName() :
...@@ -360,9 +389,10 @@ aspect Mappings { ...@@ -360,9 +389,10 @@ aspect Mappings {
for (TypeDecl typeDecl : getProgram().typeDecls()) { for (TypeDecl typeDecl : getProgram().typeDecls()) {
result.add(defaultBytesToTreeMapping(typeDecl.getName())); result.add(defaultBytesToTreeMapping(typeDecl.getName()));
result.add(defaultTreeToBytesMapping(typeDecl.getName())); result.add(defaultTreeToBytesMapping(typeDecl.getName()));
result.add(defaultBytesToListTreeMapping(typeDecl.getName())); result.add(defaultBytesToListMapping(typeDecl.getName()));
} }
result.add(defaultListTreeToBytesMapping()); result.add(defaultListToBytesMapping());
result.add(defaultJavaUtilListToBytesMapping());
// // string conversion // // string conversion
// result.add(defaultStringToBooleanMapping()); // result.add(defaultStringToBooleanMapping());
// result.add(defaultStringToIntMapping()); // result.add(defaultStringToIntMapping());
......
...@@ -146,4 +146,39 @@ aspect RagConnectNameResolution { ...@@ -146,4 +146,39 @@ aspect RagConnectNameResolution {
return null; return null;
} }
// rel ___ -> Navigable
refine RefResolverStubs eq ASTNode.globallyResolveNavigableRoleByToken(String id) {
NavigableRole result = tryGloballyResolveNavigableRoleByToken(id);
if (result == null) {
System.err.println("Could not resolve role '" + id + "'.");
}
return result;
}
syn NavigableRole ASTNode.tryGloballyResolveNavigableRoleByToken(String id) {
// id is of the form 'type_name + "." + role_name'
int dotIndex = id.indexOf(".");
String typeName = id.substring(0, dotIndex);
String roleName = id.substring(dotIndex + 1);
for (Relation relation : program().relations()) {
if (relation.isDirectedRelation()) {
if (relation.asDirectedRelation().getSource().matches(typeName, roleName)) {
return relation.asDirectedRelation().getSource();
}
} else {
if (relation.asBidirectionalRelation().getLeft().matches(typeName, roleName)) {
return relation.asBidirectionalRelation().getLeft();
}
if (relation.asBidirectionalRelation().getRight().matches(typeName, roleName)) {
return relation.asBidirectionalRelation().getRight();
}
}
}
return null;
}
syn boolean Role.matches(String typeName, String roleName) = false;
eq NavigableRole.matches(String typeName, String roleName) {
return getType().getName().equals(typeName) && getName().equals(roleName);
}
} }
aspect NewStuff { aspect GeneratedNavigation {
/** Tests if EndpointTarget is a TokenEndpointTarget. /** Tests if EndpointTarget is a TokenEndpointTarget.
* @return 'true' if this is a TokenEndpointTarget, otherwise 'false' * @return 'true' if this is a TokenEndpointTarget, otherwise 'false'
...@@ -30,6 +30,12 @@ aspect NewStuff { ...@@ -30,6 +30,12 @@ aspect NewStuff {
syn boolean EndpointTarget.isAttributeEndpointTarget() = false; syn boolean EndpointTarget.isAttributeEndpointTarget() = false;
eq AttributeEndpointTarget.isAttributeEndpointTarget() = true; eq AttributeEndpointTarget.isAttributeEndpointTarget() = true;
/** Tests if EndpointTarget is a RelationEndpointTarget.
* @return 'true' if this is a RelationEndpointTarget, otherwise 'false'
*/
syn boolean EndpointTarget.isRelationEndpointTarget() = false;
eq RelationEndpointTarget.isRelationEndpointTarget() = true;
/** casts a EndpointTarget into a TokenEndpointTarget if possible. /** casts a EndpointTarget into a TokenEndpointTarget if possible.
* @return 'this' cast to a TokenEndpointTarget or 'null' * @return 'this' cast to a TokenEndpointTarget or 'null'
*/ */
...@@ -64,6 +70,13 @@ aspect NewStuff { ...@@ -64,6 +70,13 @@ aspect NewStuff {
syn AttributeEndpointTarget EndpointTarget.asAttributeEndpointTarget(); syn AttributeEndpointTarget EndpointTarget.asAttributeEndpointTarget();
eq EndpointTarget.asAttributeEndpointTarget() = null; eq EndpointTarget.asAttributeEndpointTarget() = null;
eq AttributeEndpointTarget.asAttributeEndpointTarget() = this; eq AttributeEndpointTarget.asAttributeEndpointTarget() = this;
/** casts a EndpointTarget into a RelationEndpointTarget if possible.
* @return 'this' cast to a RelationEndpointTarget or 'null'
*/
syn RelationEndpointTarget EndpointTarget.asRelationEndpointTarget();
eq EndpointTarget.asRelationEndpointTarget() = null;
eq RelationEndpointTarget.asRelationEndpointTarget() = this;
} }
aspect RagConnectNavigation { aspect RagConnectNavigation {
...@@ -121,6 +134,13 @@ aspect RagConnectNavigation { ...@@ -121,6 +134,13 @@ aspect RagConnectNavigation {
// --- effectiveJavaTypeUse (should be in preprocessor) --- // --- effectiveJavaTypeUse (should be in preprocessor) ---
syn lazy JavaTypeUse TokenComponent.effectiveJavaTypeUse() = hasJavaTypeUse() ? getJavaTypeUse() : new SimpleJavaTypeUse("String"); syn lazy JavaTypeUse TokenComponent.effectiveJavaTypeUse() = hasJavaTypeUse() ? getJavaTypeUse() : new SimpleJavaTypeUse("String");
// --- oppositeRole ---
inh Role Role.oppositeRole();
eq DirectedRelation.getSource().oppositeRole() = getTarget();
eq DirectedRelation.getTarget().oppositeRole() = getSource();
eq BidirectionalRelation.getLeft().oppositeRole() = getRight();
eq BidirectionalRelation.getRight().oppositeRole() = getLeft();
// --- isDefaultMappingDefinition --- // --- isDefaultMappingDefinition ---
syn boolean MappingDefinition.isDefaultMappingDefinition() = false; syn boolean MappingDefinition.isDefaultMappingDefinition() = false;
eq DefaultMappingDefinition.isDefaultMappingDefinition() = true; eq DefaultMappingDefinition.isDefaultMappingDefinition() = true;
......
...@@ -13,12 +13,11 @@ TypeEndpointTarget : EndpointTarget; ...@@ -13,12 +13,11 @@ TypeEndpointTarget : EndpointTarget;
rel TypeEndpointTarget.Type <-> TypeComponent.TypeEndpointTarget*; rel TypeEndpointTarget.Type <-> TypeComponent.TypeEndpointTarget*;
ContextFreeTypeEndpointTarget : EndpointTarget; ContextFreeTypeEndpointTarget : EndpointTarget;
rel ContextFreeTypeEndpointTarget.TypeDecl <-> TypeDecl.ContextFreeTypeEndpointTarget*; rel ContextFreeTypeEndpointTarget.TypeDecl <-> TypeDecl.ContextFreeTypeEndpointTarget*;
UntypedEndpointTarget : EndpointTarget ::= <TypeName> <ChildName> <IsAttribute:boolean>; // only used by parser
// to be integrated:
AttributeEndpointTarget : EndpointTarget ::= <Name> <TypeName> ; AttributeEndpointTarget : EndpointTarget ::= <Name> <TypeName> ;
rel AttributeEndpointTarget.ParentTypeDecl <-> TypeDecl.AttributeEndpointTarget*; rel AttributeEndpointTarget.ParentTypeDecl <-> TypeDecl.AttributeEndpointTarget*;
//RelationEndpointTarget : EndpointTarget ; RelationEndpointTarget : EndpointTarget ;
//rel RelationEndpointTarget.Role <-> Role.RelationEndpointTarget* ; rel RelationEndpointTarget.Role <-> NavigableRole.RelationEndpointTarget* ;
UntypedEndpointTarget : EndpointTarget ::= <TypeName> <ChildName> <IsAttribute:boolean>; // only used by parser
DependencyDefinition ::= <ID>; DependencyDefinition ::= <ID>;
rel DependencyDefinition.Source <-> TokenComponent.DependencySourceDefinition*; rel DependencyDefinition.Source <-> TokenComponent.DependencySourceDefinition*;
......
...@@ -16,6 +16,14 @@ aspect ParserRewrites { ...@@ -16,6 +16,14 @@ aspect ParserRewrites {
return result; return result;
} }
when (getChildName() != null && tryGloballyResolveNavigableRoleByToken(combinedName()) != null)
to RelationEndpointTarget {
RelationEndpointTarget result = new RelationEndpointTarget();
result.copyOtherValuesFrom(this);
result.setRole(NavigableRole.createRef(this.combinedName()));
return result;
}
when (getChildName() == "") when (getChildName() == "")
to ContextFreeTypeEndpointTarget { to ContextFreeTypeEndpointTarget {
ContextFreeTypeEndpointTarget result = new ContextFreeTypeEndpointTarget(); ContextFreeTypeEndpointTarget result = new ContextFreeTypeEndpointTarget();
......
...@@ -124,9 +124,7 @@ public class Compiler extends AbstractCompiler { ...@@ -124,9 +124,7 @@ public class Compiler extends AbstractCompiler {
compiler.run(args); compiler.run(args);
} catch (CompilerException e) { } catch (CompilerException e) {
System.err.println(e.getMessage()); System.err.println(e.getMessage());
if (compiler.isVerbose()) {
e.printStackTrace(); e.printStackTrace();
}
System.exit(1); System.exit(1);
} }
} }
......
...@@ -11,6 +11,19 @@ public void {{configJastAddList}}.serialize(com.fasterxml.jackson.core.JsonGener ...@@ -11,6 +11,19 @@ public void {{configJastAddList}}.serialize(com.fasterxml.jackson.core.JsonGener
} }
} }
protected static <T extends ASTNode> void ASTNode.serializeJavaUtilList(
java.util.List<T> input, com.fasterxml.jackson.core.JsonGenerator g) throws SerializationException {
try {
g.writeStartArray();
for (T child : input) {
child.serialize(g);
}
g.writeEndArray();
} catch (java.io.IOException e) {
throw new SerializationException("unable to serialize list", e);
}
}
{{#typesForReceivingListEndpoints}} {{#typesForReceivingListEndpoints}}
public static {{configJastAddList}}<{{Name}}> {{Name}}.deserializeList(com.fasterxml.jackson.databind.node.ArrayNode node) throws DeserializationException { public static {{configJastAddList}}<{{Name}}> {{Name}}.deserializeList(com.fasterxml.jackson.databind.node.ArrayNode node) throws DeserializationException {
{{configJastAddList}}<{{Name}}> result = new {{configJastAddList}}<>(); {{configJastAddList}}<{{Name}}> result = new {{configJastAddList}}<>();
......
{{#Send}}
{{^PrimitiveType}}
if ({{firstInputVarName}} == null) {
{{preemptiveReturn}}
}
{{/PrimitiveType}}
{{/Send}}
{{{lastDefinitionToType}}} {{lastResult}}; {{{lastDefinitionToType}}} {{lastResult}};
try { try {
{{#innerMappingDefinitions}} {{#innerMappingDefinitions}}
......
...@@ -14,7 +14,13 @@ public boolean {{parentTypeName}}.{{connectMethodName}}(String {{connectParamete ...@@ -14,7 +14,13 @@ public boolean {{parentTypeName}}.{{connectMethodName}}(String {{connectParamete
{{#configLoggingEnabledForWrites}} {{#configLoggingEnabledForWrites}}
{{logDebug}}("[Send] {{entityName}} = {{log_}} -> {{log_}}", {{getterMethodCall}}, {{connectParameterName}}); {{logDebug}}("[Send] {{entityName}} = {{log_}} -> {{log_}}", {{getterMethodCall}}, {{connectParameterName}});
{{/configLoggingEnabledForWrites}} {{/configLoggingEnabledForWrites}}
if ({{lastValueGetterCall}} != null) {
handler.publish(topic, {{lastValueGetterCall}}); handler.publish(topic, {{lastValueGetterCall}});
{{#configLoggingEnabledForWrites}}
} else {
{{logWarn}}("[Send] {{entityName}} -> {{log_}}: can't send null.", {{connectParameterName}});
{{/configLoggingEnabledForWrites}}
}
}{{#IndexBasedListAccess}}, index{{/IndexBasedListAccess}}, connectToken); }{{#IndexBasedListAccess}}, index{{/IndexBasedListAccess}}, connectToken);
{{updateMethodName}}({{#IndexBasedListAccess}}index{{/IndexBasedListAccess}}); {{updateMethodName}}({{#IndexBasedListAccess}}index{{/IndexBasedListAccess}});
if (writeCurrentValue) { if (writeCurrentValue) {
...@@ -117,5 +123,20 @@ protected void {{parentTypeName}}.{{writeMethodName}}({{#IndexBasedListAccess}}i ...@@ -117,5 +123,20 @@ protected void {{parentTypeName}}.{{writeMethodName}}({{#IndexBasedListAccess}}i
} }
{{#needForwardingNTA}} {{#needForwardingNTA}}
syn {{{forwardingNTA_Type}}} {{parentTypeName}}.{{forwardingNTA_Name}}({{#IndexBasedListAccess}}int index{{/IndexBasedListAccess}}) = {{realGetterMethodCall}}.{{touchedTerminalsMethodName}}(); syn {{{forwardingNTA_Type}}} {{parentTypeName}}.{{forwardingNTA_Name}}({{#IndexBasedListAccess}}int index{{/IndexBasedListAccess}}) {
{{#relationEndpointWithListRole}}
// for (var element : {{realGetterMethodCall}}) {
// element.{{touchedTerminalsMethodName}}();
// }
{{realGetterMethodCall}}.stream().forEach(element -> element.{{touchedTerminalsMethodName}}());
return {{realGetterMethodCall}};
{{/relationEndpointWithListRole}}
{{^relationEndpointWithListRole}}
{{{forwardingNTA_Type}}} result = {{realGetterMethodCall}};
if (result == null) {
return null;
}
return result.{{touchedTerminalsMethodName}}();
{{/relationEndpointWithListRole}}
}
{{/needForwardingNTA}} {{/needForwardingNTA}}
...@@ -151,6 +151,8 @@ def JASTADD_INCREMENTAL_OPTIONS_TRACING_FULL = JASTADD_INCREMENTAL_OPTIONS.clone ...@@ -151,6 +151,8 @@ def JASTADD_INCREMENTAL_OPTIONS_TRACING_FULL = JASTADD_INCREMENTAL_OPTIONS.clone
JASTADD_INCREMENTAL_OPTIONS_TRACING_FULL.set(0, '--tracing=all') JASTADD_INCREMENTAL_OPTIONS_TRACING_FULL.set(0, '--tracing=all')
JASTADD_INCREMENTAL_OPTIONS_TRACING_FULL.set(1, '--incremental=param,debug') JASTADD_INCREMENTAL_OPTIONS_TRACING_FULL.set(1, '--incremental=param,debug')
classes.dependsOn(':ragconnect.base:jar')
// --- Test: Example --- // --- Test: Example ---
task compileExampleTest(type: RagConnectTest) { task compileExampleTest(type: RagConnectTest) {
ragconnect { ragconnect {
...@@ -657,7 +659,30 @@ task compileAttributeIncremental(type: RagConnectTest) { ...@@ -657,7 +659,30 @@ task compileAttributeIncremental(type: RagConnectTest) {
extraOptions = JASTADD_INCREMENTAL_OPTIONS_TRACING_FULL extraOptions = JASTADD_INCREMENTAL_OPTIONS_TRACING_FULL
} }
} }
compileAttributeIncremental.outputs.upToDateWhen { false }
// --- Test: relation-incremental ---
task compileRelationIncremental(type: RagConnectTest) {
ragconnect {
outputDir = file('src/test/02-after-ragconnect/relationInc')
inputFiles = [file('src/test/01-input/relation/Test.relast'),
file('src/test/01-input/relation/Test.connect')]
rootNode = 'Root'
logWrites = true
extraOptions = defaultRagConnectOptionsAnd(['--experimental-jastadd-329'])
}
relast {
useJastAddNames = true
grammarName = 'src/test/03-after-relast/relationInc/relationInc'
serializer = 'jackson'
}
jastadd {
jastAddList = 'JastAddList'
packageName = 'relationInc.ast'
inputFiles = [file('src/test/01-input/relation/Test.jadd')]
extraOptions = JASTADD_INCREMENTAL_OPTIONS_TRACING_FULL
}
}
compileRelationIncremental.outputs.upToDateWhen { false }
static ArrayList<String> defaultRagConnectOptionsAnd(ArrayList<String> options = []) { static ArrayList<String> defaultRagConnectOptionsAnd(ArrayList<String> options = []) {
if (!options.contains('--logTarget=slf4j')) { if (!options.contains('--logTarget=slf4j')) {
......
# Relation
Idea: Use send definitions for relations.
send SenderRoot.MyA;
send SenderRoot.OptionalA;
send SenderRoot.ManyA;
send SenderRoot.BiMyA;
send SenderRoot.BiOptionalA;
send SenderRoot.BiManyA;
send SenderRoot.MyB using ConcatValues;
send SenderRoot.OptionalB using ConcatValues;
send SenderRoot.ManyB using ConcatValueList;
send SenderRoot.BiMyB using ConcatValues;
send SenderRoot.BiOptionalB using ConcatValues;
send SenderRoot.BiManyB using ConcatValueList;
ConcatValues maps B b to String {:
return b.getValue() + "+" + b.getInner().getInnerValue();
:}
ConcatValueList maps java.util.List<B> list to String {:
StringBuilder sb = new StringBuilder();
for (B b : list) {
sb.append(b.getValue() + "+" + b.getInner().getInnerValue()).append(";");
}
return sb.toString();
:}
receive ReceiverRoot.FromMyA;
receive ReceiverRoot.FromOptionalA;
receive ReceiverRoot.FromManyA;
receive ReceiverRoot.FromBiMyA;
receive ReceiverRoot.FromBiOptionalA;
receive ReceiverRoot.FromBiManyA;
receive ReceiverRoot.FromMyB;
receive ReceiverRoot.FromOptionalB;
receive ReceiverRoot.FromManyB;
receive ReceiverRoot.FromBiMyB;
receive ReceiverRoot.FromBiOptionalB;
receive ReceiverRoot.FromBiManyB;
aspect Computation {
}
aspect MakeCodeCompile {
}
aspect MakeCodeWork {
}
aspect NameResolution {
// overriding customID guarantees to produce the same JSON representation for equal lists
// otherwise, the value for id is different each time
@Override
protected String A.customID() {
return getClass().getSimpleName() + getValue();
}
@Override
protected String B.customID() {
return getClass().getSimpleName() + getValue();
}
@Override
protected String Inner.customID() {
return getClass().getSimpleName() + getInnerValue();
}
// override resolving of SenderRoot for As in ReceiverRoot
refine RefResolverStubs eq ASTNode.globallyResolveSenderRootByToken(String id) = getParent() != null && !getParent().isListWithoutParent() ? resolveSenderRootInh(id) : null;
syn boolean ASTNode.isList() = false;
eq JastAddList.isList() = true;
syn boolean ASTNode.isListWithoutParent() = isList() && getParent() == null;
inh SenderRoot ASTNode.resolveSenderRootInh(String id);
eq SenderRoot.getChild().resolveSenderRootInh(String id) = globallyResolveSenderRootByToken(id);
eq ReceiverRoot.getChild().resolveSenderRootInh(String id) = getFakeSenderRoot(id);
syn nta SenderRoot ReceiverRoot.getFakeSenderRoot(String id) = new SenderRoot();
}
Root ::= SenderRoot* ReceiverRoot;
SenderRoot ::= A* B* ;
rel SenderRoot.MyA -> A;
rel SenderRoot.OptionalA? -> A;
rel SenderRoot.ManyA* -> A;
rel SenderRoot.BiMyA <-> A.ToMyA?;
rel SenderRoot.BiOptionalA? <-> A.ToOptionalA?;
rel SenderRoot.BiManyA* <-> A.ToManyA?;
rel SenderRoot.MyB -> B;
rel SenderRoot.OptionalB? -> B;
rel SenderRoot.ManyB* -> B;
rel SenderRoot.BiMyB <-> B.ToMyB?;
rel SenderRoot.BiOptionalB? <-> B.ToOptionalB?;
rel SenderRoot.BiManyB* <-> B.ToManyB?;
ReceiverRoot ::=
FromMyA:A FromOptionalA:A FromManyA:A*
FromBiMyA:A FromBiOptionalA:A FromBiManyA:A*
<FromMyB:String> <FromOptionalB:String> <FromManyB:String>
<FromBiMyB:String> <FromBiOptionalB:String> <FromBiManyB:String>
;
A ::= <Value> Inner ;
B ::= <Value> Inner ;
Inner ::= <InnerValue> ;
...@@ -3,6 +3,7 @@ package org.jastadd.ragconnect.tests; ...@@ -3,6 +3,7 @@ package org.jastadd.ragconnect.tests;
import com.fasterxml.jackson.core.JsonEncoding; import com.fasterxml.jackson.core.JsonEncoding;
import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonGenerator;
import org.assertj.core.groups.Tuple;
import org.awaitility.Awaitility; import org.awaitility.Awaitility;
import org.awaitility.core.ConditionFactory; import org.awaitility.core.ConditionFactory;
import org.jastadd.ragconnect.compiler.Compiler; import org.jastadd.ragconnect.compiler.Compiler;
...@@ -20,6 +21,8 @@ import java.nio.file.Paths; ...@@ -20,6 +21,8 @@ import java.nio.file.Paths;
import java.util.*; import java.util.*;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.function.Predicate;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
...@@ -155,6 +158,134 @@ public class TestUtils { ...@@ -155,6 +158,134 @@ public class TestUtils {
event, node, attribute, params, value); event, node, attribute, params, value);
} }
public static class TestChecker {
static class ActualAndExpected<T> {
Callable<T> actual;
T expected;
BiConsumer<String, T> customCheck;
ActualAndExpected(String key) {
expected = null;
}
void check(String name) {
if (customCheck != null) {
customCheck.accept(name, expected);
return;
}
if (actual == null) {
fail("No actual getter defined for " + name);
}
T actualValue = null;
try {
actualValue = this.actual.call();
} catch (Exception e) {
fail(e);
}
assertThat(actualValue).as(name).isEqualTo(expected);
}
void checkAwait(String name) {
if (customCheck != null) {
logger.warn("Custom check set for {}. Can't await for that.", name);
customCheck.accept(name, expected);
return;
}
if (actual == null) {
fail("No actual getter defined for " + name);
}
awaitMqtt().alias(name).until(actual, Predicate.isEqual(expected));
}
}
private final static String NUMBER_OF_VALUES = "numberOfValues";
private final Map<String, ActualAndExpected<String>> stringValues = new HashMap<>();
private final Map<String, ActualAndExpected<Tuple>> tupleValues = new HashMap<>();
private final Map<String, ActualAndExpected<Integer>> intValues = new HashMap<>();
private boolean needManualWait = true;
public TestChecker incNumberOfValues() {
return addToNumberOfValues(1);
}
public TestChecker addToNumberOfValues(int increment) {
// if there is at least one call to this, we do not need to manually wait in the next check()
needManualWait = false;
Integer currentExpected = intValues.computeIfAbsent(NUMBER_OF_VALUES, ActualAndExpected::new).expected;
return put(NUMBER_OF_VALUES, currentExpected + increment);
}
public TestChecker setActualNumberOfValues(Callable<Integer> actual) {
setActualInteger(NUMBER_OF_VALUES, actual);
intValues.get(NUMBER_OF_VALUES).expected = 0;
return this;
}
public TestChecker setActualString(String name, Callable<String> actual) {
stringValues.computeIfAbsent(name, ActualAndExpected::new).actual = actual;
return this;
}
public TestChecker setCheckForString(String name, BiConsumer<String, String> check) {
stringValues.computeIfAbsent(name, ActualAndExpected::new).customCheck = check;
return this;
}
public TestChecker put(String name, String expected) {
stringValues.computeIfAbsent(name, ActualAndExpected::new).expected = expected;
return this;
}
public TestChecker setActualTuple(String name, Callable<Tuple> actual) {
tupleValues.computeIfAbsent(name, ActualAndExpected::new).actual = actual;
return this;
}
public TestChecker setCheckForTuple(String name, BiConsumer<String, Tuple> check) {
tupleValues.computeIfAbsent(name, ActualAndExpected::new).customCheck = check;
return this;
}
public TestChecker put(String name, Tuple expected) {
tupleValues.computeIfAbsent(name, ActualAndExpected::new).expected = expected;
return this;
}
public TestChecker setActualInteger(String name, Callable<Integer> actual) {
intValues.computeIfAbsent(name, ActualAndExpected::new).actual = actual;
return this;
}
public TestChecker setCheckForInteger(String name, BiConsumer<String, Integer> check) {
intValues.computeIfAbsent(name, ActualAndExpected::new).customCheck = check;
return this;
}
public TestChecker put(String name, Integer expected) {
intValues.computeIfAbsent(name, ActualAndExpected::new).expected = expected;
return this;
}
public void check() {
if (needManualWait) {
try {
waitForMqtt();
} catch (InterruptedException e) {
fail(e);
}
}
intValues.get(NUMBER_OF_VALUES).checkAwait(NUMBER_OF_VALUES);
stringValues.forEach((name, aae) -> aae.check(name));
tupleValues.forEach((name, aae) -> aae.check(name));
intValues.forEach((name, aae) -> {
if (!name.equals(NUMBER_OF_VALUES)) {
aae.check(name);
}
});
needManualWait = true;
}
}
public static class IntList { public static class IntList {
private final List<Integer> integers = newArrayList(); private final List<Integer> integers = newArrayList();
public IntList(Integer... values) { public IntList(Integer... values) {
......
package org.jastadd.ragconnect.tests.relation;
import org.jastadd.ragconnect.tests.AbstractMqttTest;
import org.jastadd.ragconnect.tests.TestUtils;
import org.junit.jupiter.api.Tag;
import relationInc.ast.*;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.groups.Tuple.tuple;
import static org.jastadd.ragconnect.tests.TestUtils.TestChecker;
import static org.jastadd.ragconnect.tests.TestUtils.mqttUri;
import static org.junit.jupiter.api.Assertions.*;
/**
* Test case "relation".
*
* @author rschoene - Initial contribution
*/
@Tag("Incremental")
@Tag("New")
public class RelationTest extends AbstractMqttTest {
private static final String TOPIC_WILDCARD = "rel/#";
private static final String TOPIC_MY_A = "rel/my_a";
private static final String TOPIC_OPTIONAL_A = "rel/optional_a";
private static final String TOPIC_MANY_A = "rel/many_a";
private static final String TOPIC_BI_MY_A = "rel/bi_my_a";
private static final String TOPIC_BI_OPTIONAL_A = "rel/bi_optional_a";
private static final String TOPIC_BI_MANY_A = "rel/bi_many_a";
private static final String TOPIC_MY_B = "rel/my_b";
private static final String TOPIC_OPTIONAL_B = "rel/optional_b";
private static final String TOPIC_MANY_B = "rel/many_b";
private static final String TOPIC_BI_MY_B = "rel/bi_my_b";
private static final String TOPIC_BI_OPTIONAL_B = "rel/bi_optional_b";
private static final String TOPIC_BI_MANY_B = "rel/bi_many_b";
private MqttHandler handler;
private ReceiverData data;
private TestChecker checker;
private Root model;
private SenderRoot senderUni;
private SenderRoot senderBi;
private ReceiverRoot receiverRoot;
@Override
protected void createModel() {
model = new Root();
// model.trace().setReceiver(TestUtils::logEvent);
senderUni = createSenderRoot("uni");
senderUni.setMyA(uniA(1));
senderUni.setMyB(uniB(1));
senderBi = createSenderRoot("bi");
senderBi.setBiMyA(biA(1));
senderBi.setBiMyB(biB(1));
receiverRoot = new ReceiverRoot();
model.addSenderRoot(senderUni);
model.addSenderRoot(senderBi);
model.setReceiverRoot(receiverRoot);
}
private SenderRoot createSenderRoot(String name) {
SenderRoot result = new SenderRoot();
A dummyA = createA(name + "-dummyA");
result.addA(dummyA);
result.addA(createA(name + "-a1"));
result.addA(createA(name + "-a2"));
result.addA(createA(name + "-a3"));
B dummyB = createB(name + "-dummyB");
result.addB(dummyB);
result.addB(createB(name + "-b1"));
result.addB(createB(name + "-b2"));
result.addB(createB(name + "-b3"));
result.setMyA(dummyA);
result.setBiMyA(dummyA);
result.setMyB(dummyB);
result.setBiMyB(dummyB);
return result;
}
private A createA(String value) {
return new A().setValue(value)
.setInner(new Inner().setInnerValue("inner-" + value));
}
private B createB(String value) {
return new B().setValue(value)
.setInner(new Inner().setInnerValue("inner-" + value));
}
private A uniA(int index) {
return senderUni.getA(index);
}
private B uniB(int index) {
return senderUni.getB(index);
}
private A biA(int index) {
return senderBi.getA(index);
}
private B biB(int index) {
return senderBi.getB(index);
}
@Override
protected void setupReceiverAndConnect() throws IOException, InterruptedException {
model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS);
handler = new MqttHandler().setHost(TestUtils.getMqttHost()).dontSendWelcomeMessage();
assertTrue(handler.waitUntilReady(2, TimeUnit.SECONDS));
data = new ReceiverData();
assertTrue(handler.newConnection(TOPIC_WILDCARD, bytes -> data.numberOfValues += 1));
checker = new TestChecker();
checker.setActualNumberOfValues(() -> data.numberOfValues).setCheckForString(TOPIC_MY_A,
(name, expected) -> assertNullOrA(expected, receiverRoot.getFromMyA(), name))
.setCheckForString(TOPIC_OPTIONAL_A,
(name, expected) -> assertNullOrA(expected, receiverRoot.getFromOptionalA(), name))
.setCheckForTuple(TOPIC_MANY_A,
(name, expected) -> assertListEqualsForA(expected.toList(), receiverRoot.getFromManyAList(), name))
.setCheckForString(TOPIC_BI_MY_A,
(name, expected) -> assertNullOrA(expected, receiverRoot.getFromBiMyA(), name))
.setCheckForString(TOPIC_BI_OPTIONAL_A,
(name, expected) -> assertNullOrA(expected, receiverRoot.getFromBiOptionalA(), name))
.setCheckForTuple(TOPIC_BI_MANY_A,
(name, expected) -> assertListEqualsForA(expected.toList(), receiverRoot.getFromBiManyAList(), name))
.setCheckForString(TOPIC_MY_B,
(name, expected) -> assertNullOrB(expected, receiverRoot.getFromMyB(), name))
.setCheckForString(TOPIC_OPTIONAL_B,
(name, expected) -> assertNullOrB(expected, receiverRoot.getFromOptionalB(), name))
.setCheckForTuple(TOPIC_MANY_B,
(name, expected) -> assertListEqualsForB(expected.toList(), receiverRoot.getFromManyB(), name))
.setCheckForString(TOPIC_BI_MY_B,
(name, expected) -> assertNullOrB(expected, receiverRoot.getFromBiMyB(), name))
.setCheckForString(TOPIC_BI_OPTIONAL_B,
(name, expected) -> assertNullOrB(expected, receiverRoot.getFromBiOptionalB(), name))
.setCheckForTuple(TOPIC_BI_MANY_B,
(name, expected) -> assertListEqualsForB(expected.toList(), receiverRoot.getFromBiManyB(), name));
// connect receive
assertTrue(receiverRoot.connectFromMyA(mqttUri(TOPIC_MY_A)));
assertTrue(receiverRoot.connectFromOptionalA(mqttUri(TOPIC_OPTIONAL_A)));
assertTrue(receiverRoot.connectFromManyAList(mqttUri(TOPIC_MANY_A)));
assertTrue(receiverRoot.connectFromBiMyA(mqttUri(TOPIC_BI_MY_A)));
assertTrue(receiverRoot.connectFromBiOptionalA(mqttUri(TOPIC_BI_OPTIONAL_A)));
assertTrue(receiverRoot.connectFromBiManyAList(mqttUri(TOPIC_BI_MANY_A)));
assertTrue(receiverRoot.connectFromMyB(mqttUri(TOPIC_MY_B)));
assertTrue(receiverRoot.connectFromOptionalB(mqttUri(TOPIC_OPTIONAL_B)));
assertTrue(receiverRoot.connectFromManyB(mqttUri(TOPIC_MANY_B)));
assertTrue(receiverRoot.connectFromBiMyB(mqttUri(TOPIC_BI_MY_B)));
assertTrue(receiverRoot.connectFromBiOptionalB(mqttUri(TOPIC_BI_OPTIONAL_B)));
assertTrue(receiverRoot.connectFromBiManyB(mqttUri(TOPIC_BI_MANY_B)));
// connect send, and wait to receive (if writeCurrentValue is set)
assertTrue(senderUni.connectMyA(mqttUri(TOPIC_MY_A), isWriteCurrentValue()));
assertTrue(senderUni.connectOptionalA(mqttUri(TOPIC_OPTIONAL_A), isWriteCurrentValue()));
assertTrue(senderUni.connectManyA(mqttUri(TOPIC_MANY_A), isWriteCurrentValue()));
assertTrue(senderBi.connectBiMyA(mqttUri(TOPIC_BI_MY_A), isWriteCurrentValue()));
assertTrue(senderBi.connectBiOptionalA(mqttUri(TOPIC_BI_OPTIONAL_A), isWriteCurrentValue()));
assertTrue(senderBi.connectBiManyA(mqttUri(TOPIC_BI_MANY_A), isWriteCurrentValue()));
assertTrue(senderUni.connectMyB(mqttUri(TOPIC_MY_B), isWriteCurrentValue()));
assertTrue(senderUni.connectOptionalB(mqttUri(TOPIC_OPTIONAL_B), isWriteCurrentValue()));
assertTrue(senderUni.connectManyB(mqttUri(TOPIC_MANY_B), isWriteCurrentValue()));
assertTrue(senderBi.connectBiMyB(mqttUri(TOPIC_BI_MY_B), isWriteCurrentValue()));
assertTrue(senderBi.connectBiOptionalB(mqttUri(TOPIC_BI_OPTIONAL_B), isWriteCurrentValue()));
assertTrue(senderBi.connectBiManyB(mqttUri(TOPIC_BI_MANY_B), isWriteCurrentValue()));
}
@Override
protected void communicateSendInitialValue() throws IOException {
checker.addToNumberOfValues(8)
.put(TOPIC_MY_A, "uni-a1")
.put(TOPIC_OPTIONAL_A, (String) null)
.put(TOPIC_MANY_A, tuple())
.put(TOPIC_BI_MY_A, "bi-a1")
.put(TOPIC_BI_OPTIONAL_A, (String) null)
.put(TOPIC_BI_MANY_A, tuple())
.put(TOPIC_MY_B, "uni-b1")
.put(TOPIC_OPTIONAL_B, (String) null)
.put(TOPIC_MANY_B, tuple())
.put(TOPIC_BI_MY_B, "bi-b1")
.put(TOPIC_BI_OPTIONAL_B, (String) null)
.put(TOPIC_BI_MANY_B, tuple());
communicateBoth();
}
@Override
protected void communicateOnlyUpdatedValue() throws IOException, InterruptedException {
checker.put(TOPIC_MY_A, (String) null)
.put(TOPIC_OPTIONAL_A, (String) null)
.put(TOPIC_MANY_A, tuple())
.put(TOPIC_BI_MY_A, (String) null)
.put(TOPIC_BI_OPTIONAL_A, (String) null)
.put(TOPIC_BI_MANY_A, tuple())
.put(TOPIC_MY_B, (String) null)
.put(TOPIC_OPTIONAL_B, (String) null)
.put(TOPIC_MANY_B, tuple())
.put(TOPIC_BI_MY_B, (String) null)
.put(TOPIC_BI_OPTIONAL_B, (String) null)
.put(TOPIC_BI_MANY_B, tuple());
communicateBoth();
}
protected void communicateBoth() throws IOException {
checker.check();
// myA -> uni-a1, myB -> uni-b1
// --- testing unmapped unidirectional normal role --- //
uniA(1).setValue("test-1");
checker.incNumberOfValues().put(TOPIC_MY_A, "test-1:inner-uni-a1").check();
senderUni.setMyA(uniA(2));
checker.incNumberOfValues().put(TOPIC_MY_A, "uni-a2").check();
// changing something that was previously the relation target must not trigger a message
uniA(1).setValue("test-2-ignored");
checker.check();
uniA(2).setValue("test-3");
checker.incNumberOfValues().put(TOPIC_MY_A, "test-3:inner-uni-a2").check();
uniA(2).getInner().setInnerValue("test-4");
checker.incNumberOfValues().put(TOPIC_MY_A, "test-3:test-4").check();
// setting a new relation target resulting in the same serialization must not trigger a message
uniA(1).setValue("test-3");
uniA(1).getInner().setInnerValue("test-4");
senderUni.setMyA(uniA(1));
checker.check();
// --- testing unmapped unidirectional optional role --- //
// reset a2
uniA(2).setValue("uni-a2");
uniA(2).getInner().setInnerValue("inner-uni-a2");
senderUni.setOptionalA(uniA(2));
checker.incNumberOfValues().put(TOPIC_OPTIONAL_A, "uni-a2").check();
uniA(2).setValue("test-5");
checker.incNumberOfValues().put(TOPIC_OPTIONAL_A, "test-5:inner-uni-a2").check();
senderUni.setOptionalA(uniA(1));
checker.incNumberOfValues().put(TOPIC_OPTIONAL_A, "test-3:test-4").check();
// change a nonterminal target of two relations must trigger two messages
uniA(1).getInner().setInnerValue("test-6");
checker.addToNumberOfValues(2)
.put(TOPIC_MY_A, "test-3:test-6")
.put(TOPIC_OPTIONAL_A, "test-3:test-6")
.check();
// setting an optional relation to null is allowed, but must not trigger a message
senderUni.setOptionalA(null);
checker.check();
// setting the previous nonterminal as relation target again won't trigger a message
senderUni.setOptionalA(uniA(1));
checker.check();
// --- testing unmapped unidirectional list role --- //
senderUni.addManyA(uniA(3));
checker.incNumberOfValues().put(TOPIC_MANY_A, tuple("uni-a3")).check();
uniA(3).setValue("test-7");
checker.incNumberOfValues().put(TOPIC_MANY_A, tuple("test-7:inner-uni-a3")).check();
senderUni.addManyA(uniA(2));
checker.incNumberOfValues().put(TOPIC_MANY_A, tuple("test-7:inner-uni-a3", "test-5:inner-uni-a2")).check();
senderUni.addManyA(uniA(1));
checker.incNumberOfValues().put(TOPIC_MANY_A, tuple("test-7:inner-uni-a3", "test-5:inner-uni-a2", "test-3:test-6")).check();
uniA(2).getInner().setInnerValue("test-8");
checker.incNumberOfValues().put(TOPIC_MANY_A, tuple("test-7:inner-uni-a3", "test-5:test-8", "test-3:test-6")).check();
senderUni.removeManyA(uniA(2));
checker.incNumberOfValues().put(TOPIC_MANY_A, tuple("test-7:inner-uni-a3", "test-3:test-6")).check();
// disconnect my-a, optional-a, many-a - resetting afterwards must not trigger a message
senderUni.disconnectMyA(mqttUri(TOPIC_MY_A));
senderUni.disconnectOptionalA(mqttUri(TOPIC_OPTIONAL_A));
senderUni.disconnectManyA(mqttUri(TOPIC_MANY_A));
uniA(1).setValue("a1");
uniA(1).getInner().setInnerValue("inner-a1");
uniA(2).setValue("a2");
uniA(2).getInner().setInnerValue("inner-a2");
uniA(3).setValue("a3");
uniA(3).getInner().setInnerValue("inner-a3");
checker.check();
// "reset" values in receiver-root to make check method call shorted
receiverRoot.setFromMyA(createA("uni-a1"));
receiverRoot.setFromOptionalA(null);
receiverRoot.setFromManyAList(new JastAddList<>());
checker.put(TOPIC_MY_A, "uni-a1")
.put(TOPIC_OPTIONAL_A, (String) null)
.put(TOPIC_MANY_A, tuple());
checker.check();
// biMyA -> bi-a1, biMyB -> bi-b1
// --- testing unmapped bidirectional normal role --- //
biA(1).setValue("test-9");
checker.incNumberOfValues().put(TOPIC_BI_MY_A, "test-9:inner-bi-a1").check();
// set opposite role of relation must trigger message
biA(2).setToMyA(senderBi);
checker.incNumberOfValues().put(TOPIC_BI_MY_A, "bi-a2").check();
// changing something that was previously the relation target must not trigger a message
biA(1).setValue("test-9-ignored");
checker.check();
biA(2).setValue("test-10");
checker.incNumberOfValues().put(TOPIC_BI_MY_A, "test-10:inner-bi-a2").check();
biA(2).getInner().setInnerValue("test-11");
checker.incNumberOfValues().put(TOPIC_BI_MY_A, "test-10:test-11").check();
// setting a new relation target resulting in the same serialization must not trigger a message
biA(1).setValue("test-10");
biA(1).getInner().setInnerValue("test-11");
biA(1).setToMyA(senderBi);
checker.check();
// --- testing unmapped bidirectional optional role --- //
// reset a2
biA(2).setValue("bi-a2");
biA(2).getInner().setInnerValue("inner-bi-a2");
senderBi.setBiOptionalA(biA(2));
checker.incNumberOfValues().put(TOPIC_BI_OPTIONAL_A, "bi-a2").check();
biA(2).setValue("test-12");
checker.incNumberOfValues().put(TOPIC_BI_OPTIONAL_A, "test-12:inner-bi-a2").check();
// set opposite role of relation must trigger message
biA(1).setToOptionalA(senderBi);
checker.incNumberOfValues().put(TOPIC_BI_OPTIONAL_A, "test-10:test-11").check();
// change a nonterminal target of two relations must trigger two messages
biA(1).getInner().setInnerValue("test-13");
checker.addToNumberOfValues(2)
.put(TOPIC_BI_MY_A, "test-10:test-13")
.put(TOPIC_BI_OPTIONAL_A, "test-10:test-13").check();
// setting an optional relation to null is allowed, but must not trigger a message
senderBi.setBiOptionalA(null);
checker.check();
// setting the previous nonterminal as relation target again won't trigger a message
biA(1).setToOptionalA(senderBi);
checker.check();
// --- testing unmapped bidirectional list role --- //
biA(3).setToManyA(senderBi);
checker.incNumberOfValues().put(TOPIC_BI_MANY_A, tuple("bi-a3")).check();
biA(3).setValue("test-14");
checker.incNumberOfValues().put(TOPIC_BI_MANY_A, tuple("test-14:inner-bi-a3")).check();
senderBi.addBiManyA(biA(2));
checker.incNumberOfValues().put(TOPIC_BI_MANY_A, tuple("test-14:inner-bi-a3", "test-12:inner-bi-a2")).check();
biA(1).setToManyA(senderBi);
checker.incNumberOfValues().put(TOPIC_BI_MANY_A, tuple("test-14:inner-bi-a3", "test-12:inner-bi-a2", "test-10:test-13")).check();
biA(2).getInner().setInnerValue("test-15");
// currently, an additional message is sent at bi_optional_a for biA1 as the serialization includes relations from A to SenderRoot and the relation to ToManyA was added for biA1.
// this appears to be a bug in either jastadd or ragconnect
// numberOfValues should actually be only 37 here.
checker.addToNumberOfValues(2).put(TOPIC_BI_MANY_A, tuple("test-14:inner-bi-a3", "test-12:test-15", "test-10:test-13")).check();
senderBi.removeBiManyA(biA(2));
// the bug from above does not occur here, although the situation is similar
// so, only one message is sent
checker.incNumberOfValues().put(TOPIC_BI_MANY_A, tuple("test-14:inner-bi-a3", "test-10:test-13")).check();
biA(3).setToManyA(null);
checker.incNumberOfValues().put(TOPIC_BI_MANY_A, tuple("test-10:test-13")).check();
// change a nonterminal target of three relations must trigger three messages
biA(1).setValue("test-16");
checker.addToNumberOfValues(3)
.put(TOPIC_BI_MY_A, "test-16:test-13")
.put(TOPIC_BI_OPTIONAL_A, "test-16:test-13")
.put(TOPIC_BI_MANY_A, tuple("test-16:test-13"))
.check();
// disconnect bi-my-a, bi-optional-a, bi-many-a - resetting afterwards must not trigger a message
senderBi.disconnectBiMyA(mqttUri(TOPIC_BI_MY_A));
senderBi.disconnectBiOptionalA(mqttUri(TOPIC_BI_OPTIONAL_A));
senderBi.disconnectBiManyA(mqttUri(TOPIC_BI_MANY_A));
biA(1).setValue("bi-a1");
biA(1).getInner().setInnerValue("inner-bi-a1");
biA(2).setValue("bi-a2");
biA(2).getInner().setInnerValue("inner-bi-a2");
biA(3).setValue("bi-a3");
biA(3).getInner().setInnerValue("inner-bi-a3");
checker.check();
// "reset" values in receiver-root to make check method call shorted
receiverRoot.setFromBiMyA(createA("bi-a1"));
receiverRoot.setFromBiOptionalA(null);
receiverRoot.setFromBiManyAList(new JastAddList<>());
checker.put(TOPIC_BI_MY_A, "bi-a1")
.put(TOPIC_BI_OPTIONAL_A, (String) null)
.put(TOPIC_BI_MANY_A, tuple())
.check();
// --- testing transformed unidirectional normal role --- //
uniB(1).setValue("test-17");
checker.incNumberOfValues().put(TOPIC_MY_B, "test-17:inner-uni-b1").check();
senderUni.setMyB(uniB(2));
checker.incNumberOfValues().put(TOPIC_MY_B, "uni-b2").check();
// changing something that was previously the relation target must not trigger a message
uniB(1).setValue("test-17-ignored");
checker.check();
uniB(2).setValue("test-18");
checker.incNumberOfValues().put(TOPIC_MY_B, "test-18:inner-uni-b2").check();
uniB(2).getInner().setInnerValue("test-19");
checker.incNumberOfValues().put(TOPIC_MY_B, "test-18:test-19").check();
// setting a new relation target resulting in the same serialization must not trigger a message
uniB(1).setValue("test-18");
uniB(1).getInner().setInnerValue("test-19");
senderUni.setMyB(uniB(1));
checker.check();
// --- testing transformed unidirectional optional role --- //
// reset a2
uniB(2).setValue("uni-b2");
uniB(2).getInner().setInnerValue("inner-uni-b2");
senderUni.setOptionalB(uniB(2));
checker.incNumberOfValues().put(TOPIC_OPTIONAL_B, "uni-b2").check();
uniB(2).setValue("test-20");
checker.incNumberOfValues().put(TOPIC_OPTIONAL_B, "test-20:inner-uni-b2").check();
senderUni.setOptionalB(uniB(1));
checker.incNumberOfValues().put(TOPIC_OPTIONAL_B, "test-18:test-19").check();
// change a nonterminal target of two relations must trigger two messages
uniB(1).getInner().setInnerValue("test-21");
checker.addToNumberOfValues(2)
.put(TOPIC_MY_B, "test-18:test-21")
.put(TOPIC_OPTIONAL_B, "test-18:test-21")
.check();
// setting an optional relation to null is allowed, but must not trigger a message
senderUni.setOptionalB(null);
checker.check();
// setting the previous nonterminal as relation target again won't trigger a message
senderUni.setOptionalB(uniB(1));
checker.check();
// --- testing transformed unidirectional list role --- //
senderUni.addManyB(uniB(3));
checker.incNumberOfValues().put(TOPIC_MANY_B, tuple("uni-b3")).check();
uniB(3).setValue("test-22");
checker.incNumberOfValues().put(TOPIC_MANY_B, tuple("test-22:inner-uni-b3")).check();
senderUni.addManyB(uniB(2));
checker.incNumberOfValues().put(TOPIC_MANY_B, tuple("test-22:inner-uni-b3", "test-20:inner-uni-b2")).check();
senderUni.addManyB(uniB(1));
checker.incNumberOfValues().put(TOPIC_MANY_B, tuple("test-22:inner-uni-b3", "test-20:inner-uni-b2", "test-18:test-21")).check();
uniB(2).getInner().setInnerValue("test-23");
checker.incNumberOfValues().put(TOPIC_MANY_B, tuple("test-22:inner-uni-b3", "test-20:test-23", "test-18:test-21")).check();
senderUni.removeManyB(uniB(2));
checker.incNumberOfValues().put(TOPIC_MANY_B, tuple("test-22:inner-uni-b3", "test-18:test-21")).check();
// disconnect my-b, optional-b, many-b - resetting afterwards must not trigger a message
senderUni.disconnectMyB(mqttUri(TOPIC_MY_B));
senderUni.disconnectOptionalB(mqttUri(TOPIC_OPTIONAL_B));
senderUni.disconnectManyB(mqttUri(TOPIC_MANY_B));
uniB(1).setValue("b1");
uniB(1).getInner().setInnerValue("inner-b1");
uniB(2).setValue("b2");
uniB(2).getInner().setInnerValue("inner-b2");
uniB(3).setValue("b3");
uniB(3).getInner().setInnerValue("inner-b3");
// "reset" values in receiver-root to make check method call shorted
receiverRoot.setFromMyB("uni-b1+inner-uni-b1");
receiverRoot.setFromOptionalB(null);
receiverRoot.setFromManyB(null);
checker.put(TOPIC_MY_B, "uni-b1")
.put(TOPIC_OPTIONAL_B, (String) null)
.put(TOPIC_MANY_B, tuple())
.check();
// --- testing transformed bidirectional normal role --- //
biB(1).setValue("test-24");
checker.incNumberOfValues().put(TOPIC_BI_MY_B, "test-24:inner-bi-b1").check();
// set opposite role of relation must trigger message
biB(2).setToMyB(senderBi);
checker.incNumberOfValues().put(TOPIC_BI_MY_B, "bi-b2").check();
// changing something that was previously the relation target must not trigger a message
biB(1).setValue("test-24-ignored");
checker.check();
biB(2).setValue("test-25");
checker.incNumberOfValues().put(TOPIC_BI_MY_B, "test-25:inner-bi-b2").check();
biB(2).getInner().setInnerValue("test-26");
checker.incNumberOfValues().put(TOPIC_BI_MY_B, "test-25:test-26").check();
// setting a new relation target resulting in the same serialization must not trigger a message
biB(1).setValue("test-25");
biB(1).getInner().setInnerValue("test-26");
biB(1).setToMyB(senderBi);
checker.check();
// --- testing transformed bidirectional optional role --- //
// reset b2
biB(2).setValue("bi-b2");
biB(2).getInner().setInnerValue("inner-bi-b2");
senderBi.setBiOptionalB(biB(2));
checker.incNumberOfValues().put(TOPIC_BI_OPTIONAL_B, "bi-b2").check();
biB(2).setValue("test-27");
checker.incNumberOfValues().put(TOPIC_BI_OPTIONAL_B, "test-27:inner-bi-b2").check();
// set opposite role of relation must trigger message
biB(1).setToOptionalB(senderBi);
checker.incNumberOfValues().put(TOPIC_BI_OPTIONAL_B, "test-25:test-26").check();
// change a nonterminal target of two relations must trigger two messages
biB(1).getInner().setInnerValue("test-28");
checker.addToNumberOfValues(2)
.put(TOPIC_BI_MY_B, "test-25:test-28")
.put(TOPIC_BI_OPTIONAL_B, "test-25:test-28")
.check();
// setting an optional relation to null is allowed, but must not trigger a message
senderBi.setBiOptionalB(null);
checker.check();
// setting the previous nonterminal as relation target again won't trigger a message
biB(1).setToOptionalB(senderBi);
checker.check();
// --- testing transformed bidirectional list role --- //
biB(3).setToManyB(senderBi);
checker.incNumberOfValues().put(TOPIC_BI_MANY_B, tuple("bi-b3")).check();
biB(3).setValue("test-29");
checker.incNumberOfValues().put(TOPIC_BI_MANY_B, tuple("test-29:inner-bi-b3")).check();
senderBi.addBiManyB(biB(2));
checker.incNumberOfValues().put(TOPIC_BI_MANY_B, tuple("test-29:inner-bi-b3", "test-27:inner-bi-b2")).check();
biB(1).setToManyB(senderBi);
checker.incNumberOfValues().put(TOPIC_BI_MANY_B, tuple("test-29:inner-bi-b3", "test-27:inner-bi-b2", "test-25:test-28")).check();
biB(2).getInner().setInnerValue("test-30");
// the bug appearing in the unmapped bidirectional list case does not appear here, because here only a string representation of value + inner value is sent. so changes to relations do not trigger a message
checker.incNumberOfValues().put(TOPIC_BI_MANY_B, tuple("test-29:inner-bi-b3", "test-27:test-30", "test-25:test-28")).check();
senderBi.removeBiManyB(biB(2));
// the bug from above does not occur here, although the situation is similar
// so, only one message is sent
checker.incNumberOfValues().put(TOPIC_BI_MANY_B, tuple("test-29:inner-bi-b3", "test-25:test-28")).check();
biB(3).setToManyB(null);
checker.incNumberOfValues().put(TOPIC_BI_MANY_B, tuple("test-25:test-28")).check();
// change a nonterminal target of three relations must trigger three messages
biB(1).setValue("test-31");
checker.addToNumberOfValues(3)
.put(TOPIC_BI_MY_B, "test-31:test-28")
.put(TOPIC_BI_OPTIONAL_B, "test-31:test-28")
.put(TOPIC_BI_MANY_B, tuple("test-31:test-28"))
.check();
// disconnect bi-my-a, bi-optional-a, bi-many-a - resetting afterwards must not trigger a message
senderBi.disconnectBiMyB(mqttUri(TOPIC_BI_MY_B));
senderBi.disconnectBiOptionalB(mqttUri(TOPIC_BI_OPTIONAL_B));
senderBi.disconnectBiManyB(mqttUri(TOPIC_BI_MANY_B));
biB(1).setValue("bi-b1");
biB(1).getInner().setInnerValue("inner-bi-b1");
biB(2).setValue("bi-b2");
biB(2).getInner().setInnerValue("inner-bi-b2");
biB(3).setValue("bi-b3");
biB(3).getInner().setInnerValue("inner-bi-b3");
checker.check();
}
private void assertNullOrA(String expectedValue, A actual, String alias) {
if (expectedValue == null) {
assertNull(actual, alias);
return;
}
final String expectedInner;
if (expectedValue.contains(":")) {
String[] tokens = expectedValue.split(":");
assertEquals(2, tokens.length);
expectedValue = tokens[0];
expectedInner = tokens[1];
} else {
expectedInner = "inner-" + expectedValue;
}
assertThat(actual.getValue()).describedAs(alias + ".Value").isEqualTo(expectedValue);
assertThat(actual.getInner()).describedAs(alias + ".inner != null").isNotNull();
assertThat(actual.getInner().getInnerValue()).describedAs(alias + ".inner.Value").isEqualTo(expectedInner);
}
private void assertListEqualsForA(List<Object> expected, JastAddList<A> actual, String alias) {
assertEquals(expected.size(), actual.getNumChild(), alias + ".size");
for (int i = 0, expectedSize = expected.size(); i < expectedSize; i++) {
String s = (String) expected.get(i);
assertNullOrA(s, actual.getChild(i), alias + "[" + i + "]");
}
}
private void assertNullOrB(String expectedValue, String actual, String alias) {
final String expectedTransformed;
if (expectedValue == null) {
expectedTransformed = "";
} else {
final String expectedInner;
if (expectedValue.contains(":")) {
String[] tokens = expectedValue.split(":");
assertEquals(2, tokens.length);
expectedValue = tokens[0];
expectedInner = tokens[1];
} else {
expectedInner = "inner-" + expectedValue;
}
expectedTransformed = expectedValue + "+" + expectedInner;
}
assertEquals(expectedTransformed, actual, alias);
}
private void assertListEqualsForB(List<Object> expected, String actual, String alias) {
String[] actualTokens = actual.isEmpty() ? new String[0] : actual.split(";");
assertEquals(expected.size(), actualTokens.length, alias + ".size");
for (int i = 0, expectedSize = expected.size(); i < expectedSize; i++) {
String s = (String) expected.get(i);
assertNullOrB(s, actualTokens[i], alias + "[" + i + "]");
}
}
@Override
protected void closeConnections() {
if (handler != null) {
handler.close();
}
if (model != null) {
model.ragconnectCloseConnections();
}
}
private static class ReceiverData {
int numberOfValues = 0;
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment